底部是左联接实现的示例。它适用于普通列表和数组,但由于我使用Expression.Invoke()
,因此不适用于LINQ实体。
我想要实现的是修改/包装resultSelector
输入以接受匿名类'a
的单个实例(由leftJoin
可查询使用)而不是两个单独的参数。
检查resultSelector
,似乎我想创建它的修改版本,该版本具有'a
类型的一个参数和从Outer
的属性Inner
和'a
提取的两个参数。
我该如何进行此修改?如何更改Arguments
?
[TestClass]
public class LeftJoinTests
{
public class Outer
{
public int Key { get; }
public int? ForeignKey { get; }
public Outer(int key, int? foreignKey)
{
Key = key;
ForeignKey = foreignKey;
}
}
public class Inner
{
public int Key { get; }
public string Data { get; }
public Inner(int key, string data)
{
Key = key;
Data = data;
}
}
[TestMethod]
public void LeftJoinTest()
{
var outers = new []
{
new Outer(1, 1),
new Outer(2, 2),
new Outer(3, 3),
new Outer(4, null),
};
var inners = new []
{
new Inner(5, "5"),
new Inner(2, "2"),
new Inner(1, "1")
};
var leftJoin = LeftJoin(outers.AsQueryable(), inners.AsQueryable(), o => o.ForeignKey, i => i.Key, (oooo, iiii) => new { Outer = oooo, Inner = iiii }).ToArray();
Assert.AreEqual(4, leftJoin.Length);
Assert.AreSame(outers[0], leftJoin[0].Outer);
Assert.AreSame(outers[1], leftJoin[1].Outer);
Assert.AreSame(outers[2], leftJoin[2].Outer);
Assert.AreSame(outers[3], leftJoin[3].Outer);
Assert.AreSame(inners[2], leftJoin[0].Inner);
Assert.AreSame(inners[1], leftJoin[1].Inner);
Assert.IsNull(leftJoin[2].Inner);
Assert.IsNull(leftJoin[3].Inner);
}
public IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
IQueryable<TOuter> outer,
IQueryable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
var leftJoin = outer.GroupJoin(
inner,
outerKeySelector,
innerKeySelector,
(o, i) => new
{
Outer = o,
Inners = i
}).SelectMany(
oi => oi.Inners.DefaultIfEmpty(),
(oi, i) => new
{
oi.Outer,
Inner = i
}
);
// Break the anonymous type of the left join into the two parameters needed for the resultSelector
var anonymousType = leftJoin.GetType().GetGenericArguments()[0];
var parameter = Expression.Parameter(anonymousType, "oi");
var outerProperty = Expression.Property(parameter, "Outer");
var outerLambda = Expression.Lambda(outerProperty, parameter);
var innerProperty = Expression.Property(parameter, "Inner");
var innerLambda = Expression.Lambda(innerProperty, parameter);
var wrapper = Expression.Lambda(Expression.Invoke(resultSelector, new Expression[] {
Expression.Invoke(outerLambda, parameter),
Expression.Invoke(innerLambda, parameter)
}), parameter);
Expression<Func<TAnonymous, TResult>> Cast<TAnonymous>(Expression expression, IQueryable<TAnonymous> queryable)
{
return expression as Expression<Func<TAnonymous, TResult>>;
}
var typeSafeWrapper = Cast(wrapper, leftJoin);
return leftJoin.Select(typeSafeWrapper);
}
}
使用ExpressionVisitor
,您可以在仅替换参数时替换Expression.Invoke
,实际上是用新表达式替换参数的出现。
这里是Replace
方法-它用另一个表达式替换(引用)相等表达式。
public static class ExpressionExt {
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
使用此功能,您可以用新的所需表达式替换正文中的参数来替换Expression.Invoke
:
public IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
IQueryable<TOuter> outer,
IQueryable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector) {
var leftJoin = outer.GroupJoin(
inner,
outerKeySelector,
innerKeySelector,
(o, i) => new {
Outer = o,
Inners = i
}).SelectMany(
oi => oi.Inners.DefaultIfEmpty(),
(oi, i) => new {
oi.Outer,
Inner = i
}
);
// Break the anonymous type of the left join into the two parameters needed for the resultSelector
var anonymousType = leftJoin.GetType().GetGenericArguments()[0];
var parameter = Expression.Parameter(anonymousType, "oi");
// oi.Outer
var outerProperty = Expression.Property(parameter, "Outer");
// oi.Inner
var innerProperty = Expression.Property(parameter, "Inner");
// resultSelector = (o,i) => expr(o,i)
// o
var resultOuterParm = resultSelector.Parameters[0];
// i
var resultInnerParm = resultSelector.Parameters[1];
// expr(o,i) --> expr(oi.Outer, oi.Inner)
var newBody = resultSelector.Body.Replace(resultOuterParm, outerProperty).Replace(resultInnerParm, innerProperty);
// oi => expr(oi.Outer, oi.Inner)
Expression<Func<TAnonymous, TResult>> typeSafeLambda<TAnonymous>(IQueryable<TAnonymous> _) =>
Expression.Lambda<Func<TAnonymous, TResult>>(newBody, parameter);
var wrapper = typeSafeLambda(leftJoin);
return leftJoin.Select(wrapper);
}