如何针对分层对象列表动态构建和存储复杂的 linq 查询?

Posted

技术标签:

【中文标题】如何针对分层对象列表动态构建和存储复杂的 linq 查询?【英文标题】:How to dynamically build and store complex linq queries against a list of hierarchical objects? 【发布时间】:2017-08-22 09:04:20 【问题描述】:

我有一个分层结构的对象列表。我想根据客户公司设置并存储在数据库中的“条件”针对该对象列表构建复杂的 LINQ 查询。所以我需要在运行时构建它们,但是因为每当客户端的用户更新或刷新他们的数据时它们都会重复运行,所以我想将 LINQ 查询存储在对象中,而不是每次都重新构建它们。

我看过 ScottGu 关于Dynamic LINQ 的博客。 还有这篇关于使用expression trees的文章。 这些似乎都没有提供足够的解决方案,但我可能没有充分理解它们。恐怕我应该考虑其他选项时尝试使用 LINQ。

我的对象层次结构:

WorkOrder[]
    Field[]
    Task[]
        Field[]

这是一个我想存储和执行的 LINQ 查询示例。我可以根据定义条件的数据库记录合理地构建这种格式。

var query =
from wo in WorkOrders
from woF in wo.Fields
from task in wo.Tasks
from taskF in task.Fields
from taskF2 in task.Fields
where woF.Name == "System Status"
    && woF.Value.Contains("SETC")
    && taskF.Name == "Material"
    && taskF.Value == "Y"
    && taskF2.Name == "Planner"
    && taskF2.Value == "GR5259"
select new

    wo_id = wo.ID,
    task_id = task.ID
;

一些注意事项。

根据用户定义条件的复杂性,我可能需要也可能不需要从不同的对象列表中提取:“froms”是动态的。 请注意,在此示例中,我从 task.fields[] 中提取了两次,因此我为它设置了两次别名。 示例 LINQ 结构允许我使用复杂的 AND、OR、括号等,我认为这些对于动态链接或表达式树是不实用的。

在我的代码中我设想:

//1) Retrieve business rules from DB. I can do this.

//2) Iterate through the business rules to build the linq queries.
foreach (BusinessRule br in BusinessRules) 
    //Grab the criteria for the rule from the DB. 

    //Create a linq to object query based on the criteria just built.
    //Add this query to a list for later use.


...Elsewhere in application.

//Iterate through and execute the linq queries in order to apply business rules to data cached in the application.
foreach (LinqQuery q in LinqQueries) 
    //Execute the query

    //Apply business rule to the results.

非常感谢您的想法、努力和想法。

【问题讨论】:

“将它们存储在对象中”是什么意思?存储什么?查询的结果?使用缓存。表达方式?你会储存什么?这是一种表达。您是说要动态撰写查询吗? 感谢您的提问。理想情况下,当应用程序开始时,我可以动态构建所需的 LINQ 查询并将它们存储在静态类中,然后引用它们以供重复使用。我的 WorkOrder 对象列表会经常更改,我想针对它执行 LINQ 查询,以便针对这些特定对象执行业务规则。 明确一点:我想存储 LINQ 查询定义,而不是结果。 您的查询对象是存储的 LINQ 查询。 “商店”到底是什么意思? 您尝试过 PredicateBuilder 吗? albahari.com/nutshell/predicatebuilder.aspx 【参考方案1】:

从技术上讲,您只需使用 LINQ 即可实现所需的功能,但 PredicateBuilder 是一个不错的实用程序类:

public enum AndOr

    And,
    Or


public enum QueryableObjects

    WorkOrderField,
    TaskField


public class ClientCondition

    public AndOr AndOr;
    public QueryableObjects QueryableObject;
    public string PropertyName;
    public string PropertyValue;


public void PredicateBuilderExample()

    var conditions = new List<ClientCondition> 
    
        new ClientCondition  AndOr = LINQ.AndOr.And,
            QueryableObject = QueryableObjects.WorkOrderField,
            PropertyName = "System Status",
            PropertyValue = "SETC"
        
    ,
    
        new ClientConditionAndOr = AndOr.And,
            QueryableObject = QueryableObjects.TaskField,
            PropertyName = "Material",
            PropertyValue = "Y"
        
    ,
    
        new ClientConditionAndOr = AndOr.Or,
            QueryableObject = QueryableObjects.TaskField,
            PropertyName = "Planner",
            PropertyValue = "GR5259"
        
    
    ;

    //Obviously this WorkOrder object is empty so it will always return empty lists when queried.
    //Populate this yourself.
    var WorkOrders = new List<WorkOrder>();

    var wofPredicateBuilder = PredicateBuilder.True<WorkOrderField>();
    var tfPredicateBuilder = PredicateBuilder.True<TaskField>();

    foreach (var condition in conditions)
    
        if (condition.AndOr == AndOr.And)
        
            if (condition.QueryableObject == QueryableObjects.WorkOrderField)
            
                wofPredicateBuilder = wofPredicateBuilder.And(
                    wof => wof.Name == condition.PropertyName &&
                        wof.Value.Contains(condition.PropertyValue));
            
        
        if (condition.AndOr == AndOr.Or)
        
            if (condition.QueryableObject == QueryableObjects.TaskField)
            
                tfPredicateBuilder = tfPredicateBuilder.Or(
                    tf => tf.Name = condition.PropertyName &&
                        tf.Value.Contains(condition.PropertyValue));
            
        
        //And so on for each condition type.
    

    var query = from wo in WorkOrders
                from woF in wo.Fields.AsQueryable().Where(wofPredicateBuilder)
                from task in wo.Tasks
                from taskF in task.Fields.AsQueryable().Where(tfPredicateBuilder)
                select new
                
                    wo_id = wo.ID,
                    task_id = task.ID
                ;

请注意,我使用枚举来限制您的客户可以发送给您的可能条件。要拥有一个真正动态的可查询引擎,您需要使用反射来确保您收到的对象名称是有效的。这似乎是一个相当大的范围,此时我建议研究一种不同的方法,例如ElasticSearch。

还要注意 And 和 Ors 的顺序很重要。从本质上讲,您允许您的客户针对您的数据构建 SQL 查询,而这通常以泪水告终。将它们限制在它们应该查询的适当条件集是您的工作。

【讨论】:

是的,但请再走一步。例如,我不断从任务字段中遇到多个标准的障碍。在我提供的示例中,我展示了 2。我该怎么做?如果我理解您的代码,您只需加入任务字段一次。我可能有 0 或 X 个要过滤的条件。 我为您的任务字段添加了 OR 条件。作为练习,尝试为任务字段添加 And 条件。您的代码应该放在我的评论//等等每个...的位置。 我已经更新了原始问题,添加了我对代码的设想。 我可能遗漏了一些东西,但它不适用于任务字段的第二个标准。因为我它最终是“PropertyName==Material && PropertyValue==Y && PropertyName==Planner && PropertyValue==GR5259。这永远不会是真的。再次感谢Guillaume 我修改了查询以第二次加入 task.fields 并且它有效。那么如何动态添加“from”语句呢? var query = from wo in WorkOrders from woF in wo.Fields.AsQueryable().Where(wofPredicateBuilder) from task in wo.Tasks from taskF in task.Fields.AsQueryable().Where(tfPredicateBuilder) from taskF2 in task.Fields。 AsQueryable().Where(f2 => f2.Name == "Planner" && f2.Value == "WE5259") 选择新 wo_id = wo.ID, task_id = task.ID;【参考方案2】:

根据与 Guillaume 的讨论,我只建议在使用高级动态查询生成时注意结果查询的类型。如果您正在更改通过SelectAggregate 或其他方法之一返回的内容的形状,您会期望您的内部类型会相应更改。如果您只是使用 Where 进行过滤,除非您想要 OR 行为,否则您可以继续添加尽可能多的其他案例,那么 PredicateBuilder 之类的东西会有所帮助。如果您想通过JoinZip、...获取更多数据,那么您要么进行过滤,要么添加到返回的行,并可能更改数据的形状。

我过去做过很多这样的工作,并且最成功的是专注于特定的帮助方法,这些方法允许我需要的常见情况,然后依靠 linq 表达式树和模式(例如访问者模式)来允许构建自定义表达式在运行时。

【讨论】:

以上是关于如何针对分层对象列表动态构建和存储复杂的 linq 查询?的主要内容,如果未能解决你的问题,请参考以下文章

动态 Linq 查询 - 如何构建 select 子句?

Linq2Sql - 存储复杂的Linq查询以便将来动态执行 - 原始文本 - 可能吗?

通过 linq 到 xml 的复杂类型映射

动态对象,可持久保存到 Azure 并可通过 Dynamic Linq 查询

具有匿名类型的 C# LINQ 构建表达式

linq-to-sql 是不是处理动态查询?