我如何为 LINQ 中的许多类似连接执行 DRY?
How can i do DRY for many similar joins in LINQ?
我有一个有十个字段的接口,我经常加入这个接口的两个Queryables。大多数情况下,所有十个字段都必须相等,但有时不相等。现在我通过大量静态扩展方法解决了这个问题,但它们看起来都差不多,而且我添加的每个变体我担心我会添加错误(错误的字段等...)。
public static class MyJoins
{
public static IQueryable<T> JoinOnAll<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF2<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF4F7<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F5, a.F6, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F5, b.F6, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
//and many more of this
}
我有办法将要比较的字段作为参数传递吗? (我认为没有)
有没有其他方法可以解决大量几乎重复的代码?
-----我会如何使用这个------------------------
我主要以拆分查询、连接它们然后连接它们的方式使用它:
IQueryable a = ......
IQueryable b = ......
var firstPart = a.Where(MyExpression).JoinOnAll(b);
var secondPart = a.Where(OtherExpression).JoinOnAllButF2(b);
var thirdPart = a.Where(AnotherExpression).JoinOnAllButF1F2F8(b);
var joinResult = firstPart.Concat(secondpart).Concat(thirdPart);
var joinResultFiltered = joinResult.WHere(AndAnotherExpression);
return joinResultFiltered;
我有很多这样的函数,但是表达式和连接总是不同的。
额外信息,因为 Commetns 中有人提问:
接口基本就是这样
public interface IMyInterface{
public string F1 {get;set;}
public string F2 {get;set;}
// up to F10
}
这样的事情怎么样:
public static class MyJoins
{
private sealed class ReplacementVisitor : ExpressionVisitor
{
public ReplacementVisitor(LambdaExpression source, Expression toFind, Expression replaceWith)
{
SourceParameters = source.Parameters;
ToFind = toFind;
ReplaceWith = replaceWith;
}
private IReadOnlyCollection<ParameterExpression> SourceParameters { get; }
private Expression ToFind { get; }
private Expression ReplaceWith { get; }
private Expression ReplaceNode(Expression node) => node == ToFind ? ReplaceWith : node;
protected override Expression VisitConstant(ConstantExpression node) => ReplaceNode(node);
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
private static Expression<Func<T, object>> BuildJoinFields<T>(LambdaExpression fn) where T : IMyInterface
{
var p = Expression.Parameter(typeof(T), "p");
var visitor = new ReplacementVisitor(fn, fn.Parameters[0], p);
var body = visitor.Visit(fn.Body);
return Expression.Lambda<Func<T, object>>(body, p);
}
public static IQueryable<T> JoinOn<T, TA, TB>(
this IQueryable<TA> query,
IQueryable<TB> otherQuery,
Expression<Func<IMyInterface, object>> fieldsToJoinOn)
where TA : IMyInterface
where TB : IMyInterface,
where T : class, IJoinInterface<TA, TB>, new()
{
Expression<Func<TA, object>> aFields = BuildJoinFields<TA>(fieldsToJoinOn);
Expression<Func<TB, object>> bFields = BuildJoinFields<TB>(fieldsToJoinOn);
return query.Join(otherQuery, aFields, bFields, (a, b) => new T { AA = a, BB = b });
}
}
query.JoinOn(otherQuery, x => new { x.F1, x.F2, x.F3, ... })
尽管我不相信编译器能够推断类型参数的类型 T
。
另一种解决方案。它不关心接口,连接键基于外部类型。如果 属性 没有找到内部类型,它会抛出异常。
public static class QueryableExtensions
{
public interface IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
class JoinHandler<TA, TB> : IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
public static IQueryable<IJoinInterface<TA, TB>> JoinOn<TA, TB, TKey>(
this IQueryable<TA> outer,
IQueryable<TB> inner,
Expression<Func<TA, TKey>> joinKey)
{
var innerParam = Expression.Parameter(typeof(TB), "inner");
var innerKey = BuildKey(joinKey, innerParam);
Expression<Func<TA, TB, IJoinInterface<TA, TB>>> resultExpression = (a, b) => new JoinHandler<TA, TB> {AA = a, BB = b};
var queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Join),
new[] { typeof(TA), typeof(TB), typeof(TKey), typeof(IJoinInterface<TA, TB>) }, outer.Expression, inner.Expression,
joinKey, innerKey, resultExpression);
return outer.Provider.CreateQuery<IJoinInterface<TA, TB>>(queryExpression);
}
private static LambdaExpression BuildKey(LambdaExpression source, ParameterExpression param)
{
var body = new MemberReplacer(source.Parameters[0], param).Visit(source.Body);
return Expression.Lambda(body, param);
}
class MemberReplacer : ExpressionVisitor
{
public MemberReplacer(ParameterExpression sourceParam, ParameterExpression destParam)
{
SourceParam = sourceParam;
DestParam = destParam;
}
public ParameterExpression SourceParam { get; }
public ParameterExpression DestParam { get; }
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == SourceParam)
{
if (DestParam.Type == SourceParam.Type)
return node.Update(DestParam);
var destProp = DestParam.Type.GetProperty(node.Member.Name);
if (destProp == null)
throw new ArgumentException($"Type '{DestParam.Type.Name}' has no property '{node.Member.Name}'.");
return Expression.MakeMemberAccess(DestParam, destProp);
}
return base.VisitMember(node);
}
}
}
方案更接近上一个答案,但更通用,不关心具体的接口实现。
我有一个有十个字段的接口,我经常加入这个接口的两个Queryables。大多数情况下,所有十个字段都必须相等,但有时不相等。现在我通过大量静态扩展方法解决了这个问题,但它们看起来都差不多,而且我添加的每个变体我担心我会添加错误(错误的字段等...)。
public static class MyJoins
{
public static IQueryable<T> JoinOnAll<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF2<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF4F7<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F5, a.F6, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F5, b.F6, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
//and many more of this
}
我有办法将要比较的字段作为参数传递吗? (我认为没有) 有没有其他方法可以解决大量几乎重复的代码?
-----我会如何使用这个------------------------
我主要以拆分查询、连接它们然后连接它们的方式使用它:
IQueryable a = ......
IQueryable b = ......
var firstPart = a.Where(MyExpression).JoinOnAll(b);
var secondPart = a.Where(OtherExpression).JoinOnAllButF2(b);
var thirdPart = a.Where(AnotherExpression).JoinOnAllButF1F2F8(b);
var joinResult = firstPart.Concat(secondpart).Concat(thirdPart);
var joinResultFiltered = joinResult.WHere(AndAnotherExpression);
return joinResultFiltered;
我有很多这样的函数,但是表达式和连接总是不同的。
额外信息,因为 Commetns 中有人提问:
接口基本就是这样
public interface IMyInterface{
public string F1 {get;set;}
public string F2 {get;set;}
// up to F10
}
这样的事情怎么样:
public static class MyJoins
{
private sealed class ReplacementVisitor : ExpressionVisitor
{
public ReplacementVisitor(LambdaExpression source, Expression toFind, Expression replaceWith)
{
SourceParameters = source.Parameters;
ToFind = toFind;
ReplaceWith = replaceWith;
}
private IReadOnlyCollection<ParameterExpression> SourceParameters { get; }
private Expression ToFind { get; }
private Expression ReplaceWith { get; }
private Expression ReplaceNode(Expression node) => node == ToFind ? ReplaceWith : node;
protected override Expression VisitConstant(ConstantExpression node) => ReplaceNode(node);
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
private static Expression<Func<T, object>> BuildJoinFields<T>(LambdaExpression fn) where T : IMyInterface
{
var p = Expression.Parameter(typeof(T), "p");
var visitor = new ReplacementVisitor(fn, fn.Parameters[0], p);
var body = visitor.Visit(fn.Body);
return Expression.Lambda<Func<T, object>>(body, p);
}
public static IQueryable<T> JoinOn<T, TA, TB>(
this IQueryable<TA> query,
IQueryable<TB> otherQuery,
Expression<Func<IMyInterface, object>> fieldsToJoinOn)
where TA : IMyInterface
where TB : IMyInterface,
where T : class, IJoinInterface<TA, TB>, new()
{
Expression<Func<TA, object>> aFields = BuildJoinFields<TA>(fieldsToJoinOn);
Expression<Func<TB, object>> bFields = BuildJoinFields<TB>(fieldsToJoinOn);
return query.Join(otherQuery, aFields, bFields, (a, b) => new T { AA = a, BB = b });
}
}
query.JoinOn(otherQuery, x => new { x.F1, x.F2, x.F3, ... })
尽管我不相信编译器能够推断类型参数的类型 T
。
另一种解决方案。它不关心接口,连接键基于外部类型。如果 属性 没有找到内部类型,它会抛出异常。
public static class QueryableExtensions
{
public interface IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
class JoinHandler<TA, TB> : IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
public static IQueryable<IJoinInterface<TA, TB>> JoinOn<TA, TB, TKey>(
this IQueryable<TA> outer,
IQueryable<TB> inner,
Expression<Func<TA, TKey>> joinKey)
{
var innerParam = Expression.Parameter(typeof(TB), "inner");
var innerKey = BuildKey(joinKey, innerParam);
Expression<Func<TA, TB, IJoinInterface<TA, TB>>> resultExpression = (a, b) => new JoinHandler<TA, TB> {AA = a, BB = b};
var queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Join),
new[] { typeof(TA), typeof(TB), typeof(TKey), typeof(IJoinInterface<TA, TB>) }, outer.Expression, inner.Expression,
joinKey, innerKey, resultExpression);
return outer.Provider.CreateQuery<IJoinInterface<TA, TB>>(queryExpression);
}
private static LambdaExpression BuildKey(LambdaExpression source, ParameterExpression param)
{
var body = new MemberReplacer(source.Parameters[0], param).Visit(source.Body);
return Expression.Lambda(body, param);
}
class MemberReplacer : ExpressionVisitor
{
public MemberReplacer(ParameterExpression sourceParam, ParameterExpression destParam)
{
SourceParam = sourceParam;
DestParam = destParam;
}
public ParameterExpression SourceParam { get; }
public ParameterExpression DestParam { get; }
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == SourceParam)
{
if (DestParam.Type == SourceParam.Type)
return node.Update(DestParam);
var destProp = DestParam.Type.GetProperty(node.Member.Name);
if (destProp == null)
throw new ArgumentException($"Type '{DestParam.Type.Name}' has no property '{node.Member.Name}'.");
return Expression.MakeMemberAccess(DestParam, destProp);
}
return base.VisitMember(node);
}
}
}
方案更接近上一个答案,但更通用,不关心具体的接口实现。