如何创建一个非常动态的LinqToEntity查询?

时间:2010-08-17 13:56:55

标签: linq-to-entities dynamic-sql dynamic-tables

我需要在不同数量的表上构建一个非常动态的Linq查询。 例如,我有相关的表格:
表-A
  - ID
  - 姓名
  - 描述

表-B
  - ID
  - Table_A_ID
  - 姓名
  - 描述

Table_C
  - ID
  - 表_BID   - 姓名
  - 描述

我有一个字典,其中包含有关表依赖项的信息,其中包含:
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”]包含“名称”
    viewInfo [“Table_B”]包含“名称”,“描述”
    viewInfo [“Table_C”]包含“名称”

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

  • 3 个答案:

    答案 0 :(得分:4)

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

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

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

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

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

    LINQPad Screenshot

    那么如何实际编写动态创建简单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();
            }
    
        }
    }
    

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

    额外信息

    使用Reflector查看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.Where添加到LINQ查询的混合和其他功能中。我建议将预测(select new {x, y, z}等)留待,因为它们非常困难。您需要在运行时生成类型,就像编译器为您生成匿名类型一样。 System.Reflection.Emit是您工作的工具。

    这种方法的一个好处是你可以将它用于任何LINQ提供程序,例如LINQ to Entities,LINQ to SQL,Mindscape Lightspeed以及AsQueryable提供的内存LINQ提供程序实现。

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

    答案 1 :(得分:3)

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

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

    答案 2 :(得分: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)