C# LINQ:动态选择

C# LINQ:动态选择,c#,linq,dynamic-linq,C#,Linq,Dynamic Linq,假设我们有这个类: 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; } } 如何动态选择指定列?大概是这样的

假设我们有这个类:

    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; }

}
如何动态选择指定列?大概是这样的:

  var list = new List<Data>();

  var result= list.Select("Field1,Field2"); // How ?
var list=newlist();
变量结果=列表。选择(“字段1,字段2”);//怎样?
这是唯一的解决方案=>?

所选字段在编译时未知。它们将在运行时使用反射和表达式指定,bulid可以执行您所说的。
var result = from g in list.AsEnumerable()
                select new {F1 = g.Field1,F2  = g.Field2};
例如:

var list=newlist();
//创建表达式树以创建参数
ParameterExpression param=表达式参数(typeof(Data),“d”);
//bulid表达式树:data.Field1
表达式选择器=Expression.Property(param,typeof(Data).GetProperty(“Field1”);
Expression pred=Expression.Lambda(选择器,参数);
//bulid表达式树:选择(d=>d.Field1)
Expression expr=Expression.Call(typeof(Queryable),“选择”,
新类型[]{typeof(数据),typeof(字符串)},
Expression.Constant(list.AsQueryable()),pred);
//创建动态查询
IQueryable query=list.AsQueryable().Provider.CreateQuery(expr);
var result=query.ToList();

您可以通过动态创建传递给
选择:

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();
}

您必须使用反射来获取和设置属性值及其名称

  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);
         }
      }
  }

除了Nicholas Butler和Matt评论中的提示(使用
T
作为输入类的类型),我还对Nicholas的答案进行了改进,它动态生成实体的属性,并且函数不需要将
字段作为参数发送

如需使用,请按如下方式添加类:

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();
    }
}
 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();
            }
s => new Shipment {
    Sender = new Address {
        CityId = s.Sender.CityId,
        CityName = s.Sender.CityName
    }
}

(假设您有一个名为
AppDbContext
DbContext
,上下文有一个名为
SampleEntity
的实体)

我使用的另一种方法是嵌套三元运算符:

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);

三元运算符要求每个字段都是相同的类型,因此您需要对任何非字符串列调用.ToString()。

我已经生成了自己的类,用于相同的用途

github要点:

它为给定字符串生成动态选择lambda,还支持两级嵌套属性

用法示例如下:

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();
你也可以在这里找到我的问题和答案:

公共类SelectLambdaBuilder
{
//出于性能考虑,我缓存了已经计算过的类型属性
私有静态字典_typePropertyInfoMappings=new Dictionary();
私有只读类型_typeOfBaseClass=typeof(T);
专用字典GetFieldMapping(字符串字段)
{
var selectedFieldsMap=新字典();
foreach(字段中的变量s.Split(','))
{
var nestedFields=s.Split('.')。选择(f=>f.Trim()).ToArray();
var nestedValue=nestedFields.Length>1?nestedFields[1]:null;
if(selectedFieldsMap.Keys.Any(key=>key==nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]]。添加(nestedValue);
}
其他的
{
selectedFieldsMap.Add(nestedFields[0],新列表{nestedValue});
}
}
返回selectedFieldsMap;
}
公共函数CreateNewStatement(字符串字段)
{
ParameterExpression xParameter=表达式参数(_typeOfBaseClass,“s”);
NewExpression xNew=Expression.New(_typeOfBaseClass);
var selectFields=GetFieldMapping(字段);
var shpNestedPropertyBindings=新列表();
foreach(selectFields中的var keyValuePair)
{
PropertyInfo[]propertyInfos;
if(!\u typePropertyInfoMappings.TryGetValue(\u typeOfBaseClass,out propertyInfos))
{
var properties=_typeOfBaseClass.GetProperties();
propertyInfos=属性;
_添加(_typeOfBaseClass,properties);
}
var propertyType=propertyInfo
.FirstOrDefault(p=>p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.属性类型;
if(propertyType.IsClass)
{
PropertyInfo-objClassPropInfo=\u-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);
返回绑定2;
});
MemberInitExpression nestedInit=Expression.MemberInit(innerObjNew,nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo,nestedInit));
}
其他的
{
表达式mbr=X参数;
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=表达式.lambda(xInit,xParameter);
返回lambda.Compile();
}

我在下面一行中编写了这个方法,因为您可以利用Nicholas Butler和Ali使用嵌套字段

您可以使用此方法动态创建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;
    }
//
///格式1:“字段1”
///格式2:“嵌套1.Field1”
///格式3:“字段1:Field1Alias”
/// 
公共静态表达式DynamicSelectGenerator(参数字符串[]字段)
{
字符串[]EntityFields=字段;
if(Fields==null | | Fields.Length==0)
//获取T的属性
全部
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();
s => new Shipment {
    Sender = new Address {
        CityId = s.Sender.CityId,
        CityName = s.Sender.CityName
    }
}
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();
}
    /// <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;
    }
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; }
}
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);
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(); 
public object CreateShappedObject(object obj, List<string> lstFields)
{
    if (!lstFields.Any())
    {
        return obj;
    }
    else
    {
        ExpandoObject objectToReturn = new ExpandoObject();
        foreach (var field in lstFields)
        {
            var fieldValue = obj.GetType()
                .GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
                .GetValue(obj, null);

            ((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
        }

        return objectToReturn;
    }
}
public IHttpActionResult Get(string fields = null)
{
    try
    {
        List<string> lstFields = new List<string>();
        if (fields != null)
        {
            lstFields = fields.ToLower().Split(',').ToList();
        }
   
        // Custom query
        var result = db.data.Select(i => CreateShappedObject(new Data()
        , lstFields)).ToList();

        return Ok(result);

    }
    catch(Exception)
    {
        return InternalServerError();
    }
}