17、自己写一个SelectMany方法
在EF的join查询里,通常查出来一对多关系的数据,也就是一个用户对应多个数据,想单独把这些数据分为一对一关系就要用到selectMany方法,效果就如下图一样,一个用户的多个科目给拆开,使一个用户对应一个科目,当然,这里拆了之后用户可以出现重复的。
一对一的拆的方法如下,
List<AllUserScoreViewModel> AllUserScoreViewModelList = fHZMEntities.UserInfo.GroupJoin(fHZMEntities.Score, a => a.Id, b => b.UserId, (user, myscore) => new UserScoreViewModel
{
UserName = user.UserName,
userScoreList = myscore,
}).SelectMany(a => a.userScoreList.DefaultIfEmpty(), (a, b) => new AllUserScoreViewModel
{
UserName = a.UserName,
sub = b.sub,
userScore = b.userScore
}).ToList();
主要就是用了selectMary,
selectManry的作用就是将一对多转为一对一。
SelectMany原理分析
List<AllUserScoreViewModel> extandsList = new List<AllUserScoreViewModel>();
foreach (UserScoreViewModel item in AllUserScoreViewModelList)
{
foreach (Score item2 in item.userScoreList.DefaultIfEmpty())
{
AllUserScoreViewModel ausvm2 = new AllUserScoreViewModel();
ausvm2.UserName = item.UserName;
if (item2 != null)
{
ausvm2.sub = item2.sub;
ausvm2.userScore = item2.userScore;
}
extandsList.Add(ausvm2);
}
}
就如下图:将一个对象里的一个对象集合类型的字段转为单独的类型字段,也就将集合拆开,因为有集合在,还是一对多关系,把这个集合拆开,拆分为多个属性,然后一个用户实例为多个想同的用户即可,这样就变为了一对一。如图:
所以,根据这个原理我们就可以封装自己的方法了
,但是,需要注意一个问题,如果有些字段为空的值,类型却又是整型,那么不做为空处理就会报错,如下图:
可以看到有一条数据是没有在成绩表里插入数据的,
这时候就有空值,赋值了就报错
所以要进行判断
所以,我们为自己的扩展方法MySelectMany就必须要有为空验证,也可以不需要,如下图:用户自己在调用方法的时候自己验证,
验证方式1:用户外部自己验证
var AllUserScoreViewModelList = fHZMEntities.UserInfo.GroupJoin(fHZMEntities.Score, a => a.Id, b => b.UserId, (user, myscore) => new UserScoreViewModel
{
UserName = user.UserName,
userScoreList = myscore,
}).ToList().MySelectMany(a => a.userScoreList.DefaultIfEmpty(), (a, b) =>
{
AllUserScoreViewModel s = new AllUserScoreViewModel()
{
UserName = a.UserName,
};
if (b != null)
{
s.sub = b.sub;
s.userScore = b.userScore;
}
return s;
}
).ToList();
不过这样就很麻烦,用户还需要自己验证,最好是在扩展方法里验证,这样用户用的时候就不用考虑为空验证了。
验证方式2:方法里验证
//这种方法有限制,就是有个泛型的笔下有个构造函数
public static IEnumerable<TResult> MySelectMany<TSource, TCollection, TResult>
(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) where TCollection : new()
{
List<TResult> extandsList = new List<TResult>();
foreach (TSource item in source)
{
foreach (TCollection score in collectionSelector(item).DefaultIfEmpty())
{
if (score == null)
{
TCollection score2 = new TCollection();
TResult tr = resultSelector(item, score2);
extandsList.Add(tr);
}
else
{
TResult tr = resultSelector(item, score);
extandsList.Add(tr);
}
}
}
return extandsList;
}
上面用到了泛型来创建泛型的实例。
上面的方法需要注意,一开始泛型实例会报错。因为它不能实例化,但是给了限制即可了,但是这种方法就有局限了,规定了这个泛型必须要有实例了。
验证方式3:方法里验证
这个方法和上面的一致,只是优化了泛型约束,使泛型没有了约束,但是又能创建泛型的实例。
这是通过反射实现的。
public static IEnumerable<TResult> MySelectMany<TSource, TCollection, TResult>
(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
{
List<TResult> extandsList = new List<TResult>();
foreach (TSource item in source)
{
foreach (TCollection score in collectionSelector(item).DefaultIfEmpty())
{
if (score == null)
{
TCollection score2 = Activator.CreateInstance<TCollection>();
TResult tr = resultSelector(item, score2);
extandsList.Add(tr);
}
else
{
TResult tr = resultSelector(item, score);
extandsList.Add(tr);
}
}
}
return extandsList;
}
最后,说一说DefaultIfEmpty()方法
不加DefaultIfEmpty()方法,如果上面的对象里的集合属性里没有数据,则不会执行下面循环,结果会少一条数据
加了则会进入循环,即使为空,它也会返回一个对象,虽然对象里的值为空。
第一种,不使用DefaultIfEmpty()方法结果会少一条数据,因为有一条数据是没有学科等关系的。
第二种,使用DefaultIfEmpty()方法,就能查出全部数据。
而且,不是用DefaultIfEmpty()方法下面的判断毫无意义,因为没有数据都不会循环,