我需要在不同数量的表上构建一个非常动态的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”等。
现在,用户可以选择他想要查看的列 例子:
此选项将在另一个列表中提供:
例如,选择3:
viewInfo [“Table_A”]包含“名称”
viewInfo [“Table_B”]包含“名称”,“描述”
viewInfo [“Table_C”]包含“名称”
如何使用所需的表和字段动态创建查询以获得所需的结果?
答案 0 :(得分:4)
我为一个正在处理的项目做了同样的事情,根据用户在UI中做出的选择,在运行时完全创建了查询。
我使用表达式树构建LINQ查询,方法是使用System.Linq.Expressions
命名空间中的类。它非常强大,但学习曲线陡峭。
您可以使用LINQPad编写查询,然后转储表达式以查看树下面的内容,以便您自己知道如何构建查询。
例如,在LINQPad中运行以下代码将生成表达式树的转储。
var query = from p in Puzzles
select p;
query.Expression.Dump(20);
那么如何实际编写动态创建简单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)