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()方法下面的判断毫无意义,因为没有数据都不会循环,

posted @ 2022-01-11 14:41  青仙  阅读(115)  评论(0编辑  收藏  举报