以 LINQ to Entities 安全方式将 lambda Expression<Func<TOuter, TInner, TResult>> 修改为 Expression<Func<a', TResult>
Modifying lambda Expression<Func<TOuter, TInner, TResult>> to Expression<Func<a', TResult> in an LINQ to Entities safe way
底部是左连接实现的示例。它适用于普通列表和数组,但不适用于 LINQ to entities,因为我使用 Expression.Invoke()
.
我想要实现的是 modifying/wrapping resultSelector
输入接受匿名 class 'a
的单个实例(由 leftJoin
可查询)而不是两个单独的参数。
检查 resultSelector
看来我想创建它的一个修改版本,它有一个 'a
类型的参数和从属性 Outer
和 [=20 中提取的两个参数=] 在 '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);
}
底部是左连接实现的示例。它适用于普通列表和数组,但不适用于 LINQ to entities,因为我使用 Expression.Invoke()
.
我想要实现的是 modifying/wrapping resultSelector
输入接受匿名 class 'a
的单个实例(由 leftJoin
可查询)而不是两个单独的参数。
检查 resultSelector
看来我想创建它的一个修改版本,它有一个 'a
类型的参数和从属性 Outer
和 [=20 中提取的两个参数=] 在 '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);
}