3

我需要在不同数量的表上构建一个非常动态的 Linq 查询。例如,我有相关的表:
Table_A
- ID
- Name
- Desc

Table_B
- ID
- Table_A_ID
- 名称
- 描述

Table_C
- ID
- Table_B_ID
- 名称
- 描述

我有一个字典,其中包含有关表依赖项的信息:
  ​​tableName、parentTableName、foreignKey、parentPK
示例:
  “Table_B”、“Table_A”、“Table_A_ID”、“ID”
  、“Table_C”、“Table_B”、“Table_B_ID”、“ID” "

-> tableInfo["Table_B"].ForeignKey 将返回 "Table_A_ID" 等。

现在用户可以选择他想要查看的列。
例子:

  • Table_B.Name、Table_C.Desc
     或
  • Table_A.Name、Table_B.Name
     或
  • Table_A.Name、Table_B.Name、Table_B.Desc、Table_C.Name

    此选择将在另一个列表中可用:
    例如,对于选择 3:
    viewInfo["Table_A"] 包含 "Name"
    viewInfo["Table_B"] 包含 "Name","Desc"
    viewInfo["Table_C"] 包含 "Name"

    如何仅使用所需的表和字段动态创建查询以获得所需的结果?

  • 4

    3 回答 3

    4

    我为我正在处理的项目做了同样的事情,其中​​查询是在运行时根据用户在 UI 中所做的选择完全创建的。

    我使用命名空间中的类使用表达式树构造 LINQ 查询System.Linq.Expressions。它非常强大,但学习曲线陡峭。

    您可以使用LINQPad编写查询,然后转储表达式以查看树下面的样子,以便您知道如何自己构建查询。

    例如,在 LINQPad 中运行以下代码将生成表达式树的转储。

    var query = from p in Puzzles
    select p;
    
    query.Expression.Dump(20);
    

    LINQPad 截图

    那么,如何真正编写动态创建简单 LINQ 查询的代码呢?

    考虑以下最简单的查询示例:

    var query = from person in data
       select person;
    

    以下代码将即时生成等效查询。

    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace TestLinqGenerator
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Set up dummy data
                var data = new[]
                               {
                                   new {Name = "Fred"},
                                   new {Name = "Simon"}
                               }.AsQueryable();
                var dataType = data.ElementType;
    
                // IQueryable: data
                var source = Expression.Constant(data);
    
                // Parameter: person
                var parameter = Expression.Parameter(dataType, "person");
    
                // person => person
                var lambda = Expression.Lambda(parameter, parameter);
    
                // Expression: data.Select(person => person)
                var callSelect = Expression.Call(GetSelect().MakeGenericMethod(dataType, dataType), source, Expression.Quote(lambda));
    
                // IQueryable: data.Select(person => person)
                var query = data.Provider.CreateQuery(callSelect);
    
                // Execute query
                var results = query.Cast<object>().ToList();
    
            }
    
            private static MethodInfo GetSelect()
            {
                // Get MethodInfo of the following method from System.Linq.Queryable:
                // public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
                return typeof(System.Linq.Queryable).GetMethods().Where(
                    method => method.Name == "Select" && method.GetParameters().Length == 2 &&
                              method.GetParameters()[1].ParameterType.GetGenericArguments()[0].Name == typeof(Func<,>).Name).Single();
            }
    
        }
    }
    

    您应该能够通过将其粘贴到控制台应用程序来运行此代码。使用调试器逐步了解每个步骤的作用。

    额外信息

    查看Queryable.Select使用反射器的实现有助于理解动态编写查询时需要发生什么。我在下面复制了它:

    public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, int, TResult>> selector)
    {
        if (source == null)
        {
            throw Error.ArgumentNull("source");
        }
        if (selector == null)
        {
            throw Error.ArgumentNull("selector");
        }
        return source.Provider.CreateQuery<TResult>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
    }
    

    有趣的是, 的实现Queryable.Select只是创建了一个调用自身的 LINQ 表达式表示。LINQ 提供程序实际上将该表达式转换为其他内容 - TSQL。该Select方法本身实际上并不执行选择。

    您的代码应该做同样的事情 - 创建 LINQ 表达式。

    一旦您熟悉了如何进行简单的选择,您就可以考虑添加Queryable.WhereLINQ 查询的混合和其他功能。我建议保留预测(select new {x, y, z}等),因为它们非常困难。您需要在运行时生成类型,就像编译器为您生成匿名类型一样。System.Reflection.Emit是您完成工作的工具。

    这种方法的好处之一是您可以将它与任何 LINQ 提供程序一起使用,例如 LINQ to Entities、LINQ to SQL、Mindscape Lightspeed 以及由AsQueryable.

    我的生成 LINQ 表达式的代码将接受 IQueryable,并且在运行时这当前由 Mindscape Lightspeed IQueryables 提供,但也可能是其他代码之一。然后在我的单元测试中,我使用对象数组创建测试数据,然后将其转换为传递给 LINQ 表达式生成器的IQueryableusing 。AsQueryable然后,我的单元测试可以生成所有范围的复杂查询,但无需数据库即可轻松测试。上面的示例显示了如何做到这一点。

    于 2010-08-17T14:11:55.893 回答
    3

    有一个名为Dynamic LINQ的项目可以帮助您动态构建查询。我认为你应该看看这个项目。

    除此之外,还可以通过查询 LINQ 查询来创建部分查询。您可以在代码中放置条件语句,如果遵循某个分支,则可以通过再次查询从现有查询创建新查询。在您请求结果之前不会执行查询,因此在性能方面,如果您以小块的形式构建查询或从一开始就进行一个巨大的查询,这并不重要。使用这种技术,您可以(基于输入的值)构建结构上不同的查询,这些查询共享一些公共部分,同时具有静态类型和智能感知的好处。

    于 2010-08-17T13:59:13.707 回答
    1

    我使用 Codeplex 上非常有趣的框架NLinq解决了我的问题。您只需要构建一个包含“正常”Linq 查询的字符串!

    来自项目描述的引用:

    NLinq 是一个专注于通过提供 Linq 语法解析器和“Linq To Objects”执行环境来重新实现 Visual Studio .Net 2003 和 Visual Studio 2005(C# 和 VB .Net)中的 Linq 功能的框架。使用 NLinq,您现在可以利用 C# 3.0 的主要功能,而无需它。

    例子:

    Data sources used for the samples
            Person[] people = new Person[] { 
                new Person("Bill", 31), 
                new Person("John", 30), 
                new Person("Cindy", 25), 
                new Person("Sue", 29) 
            };
    
            // For testing physical links
            people[0].Friends.Add(people[0]);
            people[0].Friends.Add(people[1]);
            people[1].Friends.Add(people[2]);
            people[2].Friends.Add(people[3]);
            people[3].Friends.Add(people[0]);
    
            // For testing logical links
            Address[] addresses = new Address[] {
                new Address("Bill", "Redmon"),
                new Address("Bill", "Boston"),
                new Address("Cindy", "New York")
            };
    
    Projections query = new NLinqQuery(
                    @"  from c in people 
                        from d in people
                        where c.Age > d.Age
                        select new NLinq.Play.Person ( c.Firstname, d.Age )");
    
            linq = new LinqToMemory(query);
            linq.AddSource("people", people);
    
    
    Result:
    Sue (25)
    John (25)
    John (29)
    Bill (30)
    Bill (25)
    Bill (29)
    
    于 2010-08-23T13:51:45.467 回答