C# LINQ:动态选择
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16516971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
LINQ : Dynamic select
提问by Unforgiven
Consider we have this class :
考虑我们有这个类:
public class Data
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public string Field5 { get; set; }
}
How do I dynamically select for specify columns ? something like this :
如何动态选择指定的列?像这样:
var list = new List<Data>();
var result= list.Select("Field1,Field2"); // How ?
Is this the only solution => Dynamic LINQ?
Selected fields are not known at compile time. They would be specified at runtime
这是唯一的解决方案 =>动态 LINQ吗?
所选字段在编译时未知。它们将在运行时指定
采纳答案by Nicholas Butler
You can do this by dynamically creating the lambda you pass to Select:
您可以通过动态创建传递给的 lambda 来做到这一点 Select:
Func<Data,Data> CreateNewStatement( string fields )
{
// input parameter "o"
var xParameter = Expression.Parameter( typeof( Data ), "o" );
// new statement "new Data()"
var xNew = Expression.New( typeof( Data ) );
// create initializers
var bindings = fields.Split( ',' ).Select( o => o.Trim() )
.Select( o => {
// property "Field1"
var mi = typeof( Data ).GetProperty( o );
// original value "o.Field1"
var xOriginal = Expression.Property( xParameter, mi );
// set value "Field1 = o.Field1"
return Expression.Bind( mi, xOriginal );
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit( xNew, bindings );
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<Data,Data>>( xInit, xParameter );
// compile to Func<Data, Data>
return lambda.Compile();
}
Then you can use it like this:
然后你可以像这样使用它:
var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
回答by Rajeev Kumar
var result = from g in list.AsEnumerable()
select new {F1 = g.Field1,F2 = g.Field2};
回答by Jarvan
Using Reflection and Expression bulid can do what you say. Example:
使用 Reflection 和 Expression bulid 可以做到你所说的。例子:
var list = new List<Data>();
//bulid a expression tree to create a paramter
ParameterExpression param = Expression.Parameter(typeof(Data), "d");
//bulid expression tree:data.Field1
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1"));
Expression pred = Expression.Lambda(selector, param);
//bulid expression tree:Select(d=>d.Field1)
Expression expr = Expression.Call(typeof(Queryable), "Select",
new Type[] { typeof(Data), typeof(string) },
Expression.Constant(list.AsQueryable()), pred);
//create dynamic query
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr);
var result=query.ToList();
回答by Azade
You must use reflection to get and set property value with it's name.
您必须使用反射来获取和设置属性值及其名称。
var result = new List<Data>();
var data = new Data();
var type = data.GetType();
var fieldName = "Something";
for (var i = 0; i < list.Count; i++)
{
foreach (var property in data.GetType().GetProperties())
{
if (property.Name == fieldName)
{
type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null);
result.Add(data);
}
}
}
And here is GetPropValue() method
这是 GetPropValue() 方法
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
回答by Ali
In addition for Nicholas Butler and the hint in comment of Matt(that use Tfor type of input class), I put an improve to Nicholas answer that generate the property of entity dynamically and the function does not need to send fieldas parameter.
除了尼古拉斯巴特勒和马特评论中的提示(T用于输入类的类型),我对尼古拉斯的答案进行了改进,动态生成实体的属性,函数不需要field作为参数发送。
For Use add class as below:
对于使用添加类如下:
public static class Helpers
{
public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "")
{
string[] EntityFields;
if (Fields == "")
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
else
EntityFields = Fields.Split(',');
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = EntityFields.Select(o => o.Trim())
.Select(o =>
{
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
}
}
The DynamicSelectGeneratormethod get entity with type T, this method have optional input parameter Fieldsthat if you want to slect special field from entity send as a string such as "Field1, Field2"and if you dont send anything to methid, it return all of the fields of entity, you could use this method as below:
该DynamicSelectGenerator类型GET方法的实体T,这种方法具有可选的输入参数Fields,如果你想slect从实体发送特殊字段作为一个字符串,如"Field1, Field2",如果你不发送任何内容methid,它返回所有实体的字段,你可以使用这种方法如下:
using (AppDbContext db = new AppDbContext())
{
//select "Field1, Field2" from entity
var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList();
//select all field from entity
var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList();
}
(Assume that you have a DbContextwith name AppDbContextand the context have an entity with name SampleEntity)
(假设您有一个DbContextwith nameAppDbContext并且上下文有一个 name 的实体SampleEntity)
回答by Mike Grasso
Another approach I've used is a nested ternary operator:
我使用的另一种方法是嵌套三元运算符:
string col = "Column3";
var query = table.Select(i => col == "Column1" ? i.Column1 :
col == "Column2" ? i.Column2 :
col == "Column3" ? i.Column3 :
col == "Column4" ? i.Column4 :
null);
The ternary operator requires that each field be the same type, so you'll need to call .ToString() on any non-string columns.
三元运算符要求每个字段的类型相同,因此您需要在任何非字符串列上调用 .ToString()。
回答by Emre Savc?
I have generate my own class for same purpose of usage.
我已经为相同的使用目的生成了自己的类。
github gist : https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
github 要点:https: //gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
It generates dynamic select lambda for given string and also support for two level nested properties.
它为给定的字符串生成动态选择 lambda,还支持两级嵌套属性。
Example of usage is :
用法示例是:
class Shipment {
// other fields...
public Address Sender;
public Address Recipient;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
// in the service method
var shipmentDtos = _context.Shipments.Where(s => request.ShipmentIdList.Contains(s.Id))
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields)) // request.Fields = "Sender.CityName,Sender.CityId"
.ToList();
It compiles the lambda as below:
它编译 lambda 如下:
s => new Shipment {
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
}
}
You can also find my quesion and answer here :c# - Dynamically generate linq select with nested properties
您还可以在这里找到我的问题和答案:c# - Dynamically generate linq select with nested properties
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
回答by kursat sonmez
I writing the method in following line for you can work with nested fields taking advantage of Nicholas Butler and Ali.
我在以下行中编写方法,以便您可以利用 Nicholas Butler 和 Ali 来处理嵌套字段。
You can use this method for dynamically creating to lambda for pass to selectand also works for nested fields. You can also work with IQueryablecases.
您可以使用此方法动态创建到 lambda 以传递给select并且也适用于嵌套字段。您还可以处理IQueryable案例。
/// <param name="Fields">
/// Format1: "Field1"
/// Format2: "Nested1.Field1"
/// Format3: "Field1:Field1Alias"
/// </param>
public static Expression<Func<T, TSelect>> DynamicSelectGenerator<T, TSelect>(params string[] Fields)
{
string[] EntityFields = Fields;
if (Fields == null || Fields.Length == 0)
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "x"
var xParameter = Expression.Parameter(typeof(T), "x");
// new statement "new Data()"
var xNew = Expression.New(typeof(TSelect));
// create initializers
var bindings = EntityFields
.Select(x =>
{
string[] xFieldAlias = x.Split(":");
string field = xFieldAlias[0];
string[] fieldSplit = field.Split(".");
if (fieldSplit.Length > 1)
{
// original value "x.Nested.Field1"
Expression exp = xParameter;
foreach (string item in fieldSplit)
exp = Expression.PropertyOrField(exp, item);
// property "Field1"
PropertyInfo member2 = null;
if (xFieldAlias.Length > 1)
member2 = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member2 = typeof(T).GetProperty(fieldSplit[fieldSplit.Length - 1]);
// set value "Field1 = x.Nested.Field1"
var res = Expression.Bind(member2, exp);
return res;
}
// property "Field1"
var mi = typeof(T).GetProperty(field);
PropertyInfo member;
if (xFieldAlias.Length > 1)
member = typeof(TSelect).GetProperty(xFieldAlias[1]);
else member = typeof(TSelect).GetProperty(field);
// original value "x.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = x.Field1"
return Expression.Bind(member, xOriginal);
}
);
// initialization "new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "x => new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var lambda = Expression.Lambda<Func<T, TSelect>>(xInit, xParameter);
return lambda;
}
Usage:
用法:
var s = DynamicSelectGenerator<SalesTeam, SalesTeamSelect>(
"Name:SalesTeamName",
"Employee.FullName:SalesTeamExpert"
);
var res = _context.SalesTeam.Select(s);
public class SalesTeam
{
public string Name {get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
}
public class SalesTeamSelect
{
public string SalesTeamName {get; set; }
public string SalesTeamExpert {get; set; }
}
回答by HeyJude
The OP mentioned Dynamic Linq library, so I'd like to lay out an explanation on its usage.
OP 提到了 Dynamic Linq 库,所以我想解释一下它的用法。
1. Dynamic Linq Built-In Select
1.动态Linq内置 Select
Dynamic Linq has a built-in Selectmethod, which can be used as follows:
Dynamic Linq 有一个内置Select方法,可以如下使用:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the "it" keyword functions as the lambda parameter,
// so essentialy it's like calling: numbers.Select(num => num)
var selectedNumbers = numbers.Select("it");
// the following is the equivalent of calling: wrapped.Select(num => num.Value)
var selectedValues = wrapped.Select("Value");
// the following is the equivalent of calling: numbers.Select(num => new { Value = num })
var selectedObjects = numbers.Select("new(it as Value)");
foreach (int num in selectedNumbers) Console.WriteLine(num);
foreach (int val in selectedValues) Console.WriteLine(val);
foreach (dynamic obj in selectedObjects) Console.WriteLine(obj.Value);
The Downside
不足之处
There's somewhat a downside using the built-in Select:
使用内置的有一些缺点Select:
Since it's an IQueryable- not IQueryable<T>- extension method, with IQueryableas its return type, common materialization methods - like ToListor FirstOrDefault- can't be used. This is why the above example uses foreach- it's simply the only convenient way of materializing the results.
因为它是一个IQueryable- 不是IQueryable<T>- 扩展方法,IQueryable作为它的返回类型,不能使用常见的物化方法 - 像ToListor FirstOrDefault- 。这就是为什么上面的例子使用foreach- 它只是实现结果的唯一方便的方法。
So to make things more convenient, let's support these methods.
所以为了让事情更方便,让我们支持这些方法。
2. Supporting Select<T>in Dynamic Linq (to enable using ToListand alike)
2.Select<T>在Dynamic Linq中支持(以启用使用ToList等)
To support Select<T>, it needs to be added into the Dynamic Linq file. The simple steps for doing that are explained in this answerand in my commenton it.
要支持Select<T>,需要将其添加到动态 Linq 文件中。在这个答案和我的评论中解释了这样做的简单步骤。
After doing so, it can be used in the following way:
这样做后,它可以通过以下方式使用:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the following is the equivalent of calling: numbers.Select(num => num).ToList()
var selectedNumbers = numbers.Select<int>("it").ToList();
// the following is the equivalent of calling: wrapped.Select(num => num.Value).ToList()
var selectedValues = wrapped.Select<int>("Value").ToList();
// the following is the equivalent of calling: numbers.Select(num => new { Value = num }).ToList()
var selectedObjects = numbers.Select<object>("new(it as Value)").ToList();
The Downside
不足之处
Arguably, this implementation introduces yet another kind of downside: By having to explicitly parameterize the Select<T>call (e.g., having to call Select<int>), we're losing the dynamic nature of the library.
可以说,这种实现引入了另一种缺点:通过显式参数化Select<T>调用(例如,必须调用Select<int>),我们失去了库的动态特性。
Nevertheless, since we can now call any materialization Linq method, this usage may still be quite useful.
尽管如此,由于我们现在可以调用任何物化 Linq 方法,因此这种用法可能仍然非常有用。

