从linq到实体查询动态构建选择列表
我正在寻找一种从iQueryable对象动态创建选择列表的方法。
具体的例子,我想做类似以下的事情:
public void CreateSelectList(IQueryable(of EntityModel.Core.User entities), string[] columns) { foreach(var columnID in columns) { switch(columnID) { case "Type": SelectList.add(e => e.UserType); break; case "Name": SelectList.add(e => e.Name); break; etc.... } } var selectResult = (from u in entities select objSelectList); }
因此,所有属性都是已知的,但我事先并不知道要选择哪些属性。 这将通过columns参数传递。
我知道我将遇到selectResult类型的问题,因为当选择列表是动态的时,编译器不知道匿名类型的属性需要是什么。
如果上述情况不可能:我需要的方案如下:
我正在尝试创建一个可以实现的类来显示分页/过滤的数据列表。 这些数据可以是任何东西(取决于实现)。使用的linq是实体的linq。 所以它们直接链接到sql数据。 现在我只想选择我实际在列表中显示的实体的列。 因此我希望select是动态的。 我的实体可能有一百个属性,但如果列表中只显示了3个属性,我不想生成一个选择所有100列数据的查询,然后只使用其中的3个。 如果有一种我没有想到的不同方法,我会接受各种想法
编辑:
关于约束的一些澄清:
– 查询需要使用linq到实体(请参阅问题主题)
– 一个实体可能包含100列,因此选择所有列然后只读取我需要的列不是一个选项。
– 最终用户决定显示哪些列,因此要在运行时确定要选择的列
– 我需要创建一个SINGLE选择,有多个select语句意味着在数据库上有多个查询,这是我不想要的
动态选择表达式到编译时已知类型可以使用Expression.MemberInit
方法轻松构建,其中MemberBinding
是使用Expression.Bind
方法创建的。
这是一个自定义扩展方法,它执行以下操作:
public static class QueryableExtensions { public static IQueryable Select (this IQueryable source, string[] columns) { var sourceType = source.ElementType; var resultType = typeof(TResult); var parameter = Expression.Parameter(sourceType, "e"); var bindings = columns.Select(column => Expression.Bind( resultType.GetProperty(column), Expression.PropertyOrField(parameter, column))); var body = Expression.MemberInit(Expression.New(resultType), bindings); var selector = Expression.Lambda(body, parameter); return source.Provider.CreateQuery ( Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType }, source.Expression, Expression.Quote(selector))); } }
唯一的问题是什么是TResult
类型。 在EF Core中,您可以传递实体类型(如EntityModel.Core.User
中的EntityModel.Core.User
),它将起作用。 在EF 6及更早版本中,您需要一个单独的非实体类型,否则您将获得NotSupportedException
– 无法在LINQ to Entities查询中构造实体或复杂类型 。
更新:如果你想删除字符串列,我建议你用以下类替换扩展方法:
public class SelectList { private List members = new List (); public SelectList Add(Expression> selector) { var member = ((MemberExpression)selector.Body).Member; members.Add(member); return this; } public IQueryable Select (IQueryable source) { var sourceType = typeof(TSource); var resultType = typeof(TResult); var parameter = Expression.Parameter(sourceType, "e"); var bindings = members.Select(member => Expression.Bind( resultType.GetProperty(member.Name), Expression.MakeMemberAccess(parameter, member))); var body = Expression.MemberInit(Expression.New(resultType), bindings); var selector = Expression.Lambda>(body, parameter); return source.Select(selector); } }
样本用法:
var selectList = new SelectList(); selectList.Add(e => e.UserType); selectList.Add(e => e.Name); var selectResult = selectList.Select(entities);
你想要的是可能的,但这并不简单。 您可以使用System.Linq.Expressions命名空间中的方法和类动态构建EF查询。
有关如何动态构建Select表达式的一个很好的示例,请参阅此问题 。
我相信这就是你所需要的:
var entities = new List(); entities.Add(new User { Name = "First", Type = "TypeA" }); entities.Add(new User { Name = "Second", Type = "TypeB" }); string[] columns = { "Name", "Type" }; var selectResult = new List(); foreach (var columnID in columns) { selectResult.AddRange(entities.Select(e => e.GetType().GetProperty(columnID).GetValue(e, null).ToString())); } foreach (var result in selectResult) { Console.WriteLine(result); }
此代码输出:
- 第一
- 第二
- 类型A
- 的TypeB
更新(根据评论)
// initialize alist of entities (User) var entities = new List(); entities.Add(new User { Name = "First", Type = "TypeA", SomeOtherField="abc" }); entities.Add(new User { Name = "Second", Type = "TypeB", SomeOtherField = "xyz" }); // set the wanted fields string[] columns = { "Name", "Type" }; // create a set of properties of the User class by the set of wanted fields var properties = typeof(User).GetProperties() .Where(p => columns.Contains(p.Name)) .ToList(); // Get it with a single select (by use of the Dynamic object) var selectResult = entities.Select(e => { dynamic x = new ExpandoObject(); var temp = x as IDictionary; foreach (var property in properties) temp.Add(property.Name, property.GetValue(e)); return x; }); // itterate the results foreach (var result in selectResult) { Console.WriteLine(result.Name); Console.WriteLine(result.Type); }
此代码输出:
- 第一
- 类型A
- 第二
- 的TypeB