LINQ - Full Outer Join

后端 未结 16 1876
既然无缘
既然无缘 2020-11-21 22:45

I have a list of people\'s ID and their first name, and a list of people\'s ID and their surname. Some people don\'t have a first name and some don\'t have a surname; I\'d l

16条回答
  •  夕颜
    夕颜 (楼主)
    2020-11-21 23:07

    I decided to add this as a separate answer as I am not positive it is tested enough. This is a re-implementation of the FullOuterJoin method using essentially a simplified, customized version of LINQKit Invoke/Expand for Expression so that it should work the Entity Framework. There's not much explanation as it is pretty much the same as my previous answer.

    public static class Ext {
        private static Expression> CastSMBody(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression>)ex;
    
        public static IQueryable LeftOuterJoin(
            this IQueryable leftItems,
            IQueryable rightItems,
            Expression> leftKeySelector,
            Expression> rightKeySelector,
            Expression> resultSelector) {
    
            // (lrg,r) => resultSelector(lrg.left, r)
            var sampleAnonLR = new { left = default(TLeft), rightg = default(IEnumerable) };
            var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lrg");
            var parmC = Expression.Parameter(typeof(TRight), "r");
            var argLeft = Expression.PropertyOrField(parmP, "left");
            var newleftrs = CastSMBody(Expression.Lambda(resultSelector.Apply(argLeft, parmC), parmP, parmC), sampleAnonLR, default(TRight), default(TResult));
    
            return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
        }
    
        public static IQueryable RightOuterJoin(
            this IQueryable leftItems,
            IQueryable rightItems,
            Expression> leftKeySelector,
            Expression> rightKeySelector,
            Expression> resultSelector) {
    
            // (lgr,l) => resultSelector(l, lgr.right)
            var sampleAnonLR = new { leftg = default(IEnumerable), right = default(TRight) };
            var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lgr");
            var parmC = Expression.Parameter(typeof(TLeft), "l");
            var argRight = Expression.PropertyOrField(parmP, "right");
            var newrightrs = CastSMBody(Expression.Lambda(resultSelector.Apply(parmC, argRight), parmP, parmC), sampleAnonLR, default(TLeft), default(TResult));
    
            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                             .SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
        }
    
        private static Expression> CastSBody(LambdaExpression ex, TParm unusedP, TResult unusedRes) => (Expression>)ex;
    
        public static IQueryable RightAntiSemiJoin(
            this IQueryable leftItems,
            IQueryable rightItems,
            Expression> leftKeySelector,
            Expression> rightKeySelector,
            Expression> resultSelector) where TLeft : class where TRight : class where TResult : class {
    
            // newrightrs = lgr => resultSelector(default(TLeft), lgr.right)
            var sampleAnonLgR = new { leftg = (IEnumerable)null, right = default(TRight) };
            var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
            var argLeft = Expression.Constant(default(TLeft), typeof(TLeft));
            var argRight = Expression.PropertyOrField(parmLgR, "right");
            var newrightrs = CastSBody(Expression.Lambda(resultSelector.Apply(argLeft, argRight), parmLgR), sampleAnonLgR, default(TResult));
    
            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
        }
    
        public static IQueryable FullOuterJoin(
            this IQueryable leftItems,
            IQueryable rightItems,
            Expression> leftKeySelector,
            Expression> rightKeySelector,
            Expression> resultSelector)  where TLeft : class where TRight : class where TResult : class {
    
            return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
        }
    
        public static Expression Apply(this LambdaExpression e, params Expression[] args) {
            var b = e.Body;
    
            foreach (var pa in e.Parameters.Cast().Zip(args, (p, a) => (p, a))) {
                b = b.Replace(pa.p, pa.a);
            }
    
            return b.PropagateNull();
        }
    
        public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
        public class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor {
            public readonly Expression from;
            public readonly Expression to;
    
            public ReplaceVisitor(Expression _from, Expression _to) {
                from = _from;
                to = _to;
            }
    
            public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
        }
    
        public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);
        public class NullVisitor : System.Linq.Expressions.ExpressionVisitor {
            public override Expression Visit(Expression node) {
                if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
                    return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
                else
                    return base.Visit(node);
            }
        }
    
        public static Type GetMemberType(this MemberInfo member) {
            switch (member) {
                case FieldInfo mfi:
                    return mfi.FieldType;
                case PropertyInfo mpi:
                    return mpi.PropertyType;
                case EventInfo mei:
                    return mei.EventHandlerType;
                default:
                    throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
            }
        }
    }
    

提交回复
热议问题