过滤器设计中的 Linq 铸造

Posted

技术标签:

【中文标题】过滤器设计中的 Linq 铸造【英文标题】:Linq casting in a Filter design 【发布时间】:2022-01-09 11:23:31 【问题描述】:

我正在尝试创建一个可以传递给存储库的规范类。该规范有 2 个部分,Where 子句(效果很好)和过滤器,每当您按类型不是字符串的属性进行排序时,它都会给我一个错误。

    public class Specification<T> where T : class
    
        public Expression<Func<T, bool>>? Where  get; init; 

        public Filter<T>? Filter  get; init; 
    

    public class Filter<T> 
            where T : class
    
        public IOrderBySelector<T>? OrderBy  get; init; 

        public IList<IOrderBySelector<T>> ThenBy  get;  = new List<IOrderBySelector<T>>();

        public bool Paginated  get; set; 

        public int ItemsPerPage  get; init; 

        public int CurrentPage  get; init; 
    

我已将错误原因隔离到 OrderSelector。过滤器将选择器表达式 'Expression>' 转换为 LambdaExpression,以便调用者可以创建他们想要的任何类型的 OrderBySelector。但是,当您在 Linq 查询中调用选择器时,它会忘记 TKey 的类型并失败。

    public interface IOrderBySelector<T> where T : class
    
        OrderByDirection Direction  get; 
        LambdaExpression? Selector  get; 
        Type? SelectorType  get; 
    

    public class OrderBySelector<T, TKey> : IOrderBySelector<T>
        where T : class
        where TKey : notnull
    

        public OrderByDirection Direction  get; init; 

        public Expression<Func<T, TKey>>? Selector  get; init; 

        public Type? SelectorType
        
            get
            
                var body = Selector.Body;
                // Unwrap the conversion to object, if there is one.
                if (body.NodeType == ExpressionType.Convert)
                
                    body = ((UnaryExpression)body).Operand;
                
                return body.Type;
            
        

        LambdaExpression? IOrderBySelector<T>.Selector
        
            get
            
                if (this.Selector == null) return null;
                return (LambdaExpression)this.Selector;
            
        
    

我能做的最好的事情就是在 Linq 查询中尝试使用选择器并将其转换回其真实类型时对其进行询问。

评估规范的方法的一部分:

foreach (IOrderBySelector<Company> thenOrderBy in spec.Filter.ThenBy)
                    
                        switch (thenOrderBy.SelectorType.FullName)
                        
                            case "System.String":
                                query = orderdQuery.ThenBy<Company, string>((Expression<Func<Company, string>>)thenOrderBy.Selector);
                                break;
                            case "System.Int32":
                                query = orderdQuery.ThenBy<Company, int>((Expression<Func<Company, int>>)thenOrderBy.Selector);
                                break;
                            case "System.Int64":
                                query = orderdQuery.ThenBy<Company, long>((Expression<Func<Company, long>>)thenOrderBy.Selector);
                                break;
                            case "System.Guid":
                                query = orderdQuery.ThenBy<Company, Guid>((Expression<Func<Company, Guid>>)thenOrderBy.Selector);
                                break;
                            case "System.Decimal":
                                query = orderdQuery.ThenBy<Company, decimal>((Expression<Func<Company, decimal>>)thenOrderBy.Selector);
                                break;
                            default:                                
                                throw new NotImplementedException($"thenOrderBy.SelectorType.FullName");
                        
                    

如何在执行之前重建表达式,或者在执行查询之前将 Selector 的类型从对象更改回适当的类型。

欢迎任何帮助。

谢谢

【问题讨论】:

【参考方案1】:

您可以通过构建表达式树来做到这一点。我已将Company 替换为通用参数T

// just extract expression
var queryExpression = query.Expression;

foreach (var thenOrderBy in spec.Filter.ThenBy)

    var orderLambda = thenOrderBy.Selector;

    // here we dynamically create ThenBy
    var queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.ThenBy), 
        new [] typeof(T), orderLambda.ReturnType, 
        queryExpression, orderLambda);


// apply to query
query = query.Provider.CreateQuery<T>(queryExpression);

【讨论】:

以上是关于过滤器设计中的 Linq 铸造的主要内容,如果未能解决你的问题,请参考以下文章

是否有使用 Linq 动态创建过滤器的模式?

使用 Distinct() 过滤 Linq 中的重复记录

嵌套字典LINQ中的过滤元素

如何在 LINQ C# 中仅过滤 2 列分组中的最后一个值

减少用于过滤的 linq 查询

如何使用 Linq 根据另一个列表过滤列表?