如何动态构建()=> new {x.prop} lambda表达式?

在这里输入图像描述 如何动态创建下面的linq表达式。

IQueryable abc = QueryData.Select(a => new {a,TempData = a.customer.Select(b => b.OtherAddress).ToList()[0]})。OrderBy(a => a.TempData).Select (a => aa);

public class Orders
{
    public long OrderID { get; set; }
    public string CustomerID { get; set; }
    public int EmployeeID { get; set; }
    public double Freight { get; set; }
    public string ShipCountry { get; set; }
    public string ShipCity { get; set; }

    public Customer[] customer {get; set;}
}

public class Customer
{
    public string OtherAddress { get; set; }
    public int CustNum { get; set; }
}

实际数据:

List<Orders> order = new List<Orders>();
Customer[] cs = { new Customer { CustNum = 5, OtherAddress = "Hello" }, new 
Customer { CustNum = 986, OtherAddress = "Other" } };
Customer[] cso = { new Customer { OtherAddress = "T", CustNum = 5 }, new 
Customer { CustNum = 777, OtherAddress = "other" } };
order.Add(new Orders(code + 1, "ALFKI", i + 0, 2.3 * i, "Mumbari", "Berlin", cs));
order.Add(new Orders(code + 2, "ANATR", i + 2, 3.3 * i, "Sydney", "Madrid", cso));
order.Add(new Orders(code + 3, "ANTON", i + 1, 4.3 * i, "NY", "Cholchester", cs));
order.Add(new Orders(code + 4, "BLONP", i + 3, 5.3 * i, "LA", "Marseille", cso));
order.Add(new Orders(code + 5, "BOLID", i + 4, 6.3 * i, "Cochin", "Tsawassen", cs));



public Orders(long OrderId, string CustomerId, int EmployeeId, double Freight, string ShipCountry, string ShipCity, Customer[] Customer = null)
    {
        this.OrderID = OrderId;
        this.CustomerID = CustomerId;
        this.EmployeeID = EmployeeId;
        this.Freight = Freight;
        this.ShipCountry = ShipCountry;
        this.ShipCity = ShipCity;
        this.customer = Customer;
    }

如果我排序OtherAddress字段第0个索引意味着客户字段仅排序。 我需要根据OtherAddress字段对整个订单数据进行排序。

我尝试了以下方法:

private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)
    {
        string[] selectArr = select.Split('.');
        ParameterExpression param = Expression.Parameter(typeof(T), "a");
        Expression property = param;
        for (int i = 0; i < selectArr.Length; i++)
        {
            int n;
            if (int.TryParse(selectArr[i + 1], out n))
            {
                int index = Convert.ToInt16(selectArr[i + 1]);
                property = Expression.PropertyOrField(Expression.ArrayIndex(Expression.PropertyOrField(property, selectArr[i]), Expression.Constant(index)), selectArr[i + 2]);
                i = i + 2;
            }
            else property = Expression.PropertyOrField(property, selectArr[i]);
        }
        var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param));

       IQueryable<object> data = dataSource.Select(a => new { a, TempData = property});// Expression.Lambda<Func<T, object>>(property, param) });
        return data;
    }

方法调用: PerformComplexDataOperation(数据源,“customer.0.OtherAddress”)

我可以从这一行获得值: var TempData = dataSource.Select(Expression.Lambda>(property,param));

但我无法获取dataSource.Select(a => new {a,TempData = property})中的值;

当我们使用下面的代码时它正在工作:

    var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param)).ToList();
    IQueryable<object> data = dataSource.Select((a, i) => new { a, TempData = TempData[i] });

这是合适的解决方案吗


XY问题?

这感觉就像是XY问题。 您的解决方案是人为设计的(无意冒犯),并且您试图解决的问题并不是通过观察您提出的解决方案而显而易见的。

但是,当我阅读代码的意图而不是你描述的意图时,我确实认为你的问题有技术上的优点。


冗余步骤

IQueryable abc = QueryData
                    .Select(a => new { 
                             a, 
                             TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] })
                    .OrderBy(a => a.TempData)
                    .Select(a => a.a);

首先,当你将这个内联插入单链命令时, TempData成为一个冗余步骤。 您可以直接将第一个TempData逻辑(从第一个Select )直接转换为OrderBy lambda:

IQueryable abc = QueryData
                    .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
                    .AsQueryable();

正如你所看到的,这也意味着你不再需要第二个Select (因为它只存在于撤销前面的Select


参数化和方法抽象

你提到你正在寻找类似于以下用法的用法:

PerformComplexDataOperation(datasource, "customer.0.OtherAddress")

然而,这并不合理,因为你已经定义了一个扩展方法

private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)

我认为您需要重新考虑您的预期用法,以及目前定义的方法。

  • 小调,该方法的返回类型应该是IQueryable<T>而不是IQueryable 。 否则,你会失去LINQ倾向于依赖的泛型类型定义。
  • 基于方法签名,您的预期使用应该是myData = myData.PerformComplexDataOperation("customer.0.OtherAddress")
  • 字符串很容易让你绕过一个强类型的系统。 尽管您的严格使用在技术上是功能性的,但它是非惯用的 ,它打开了无法读取和/或错误代码的大门
  • 使用字符串会导致人为的字符串解析逻辑。 看看你的方法定义,然后统计有多少行只是为了解析字符串并将其再次转换为实际代码。
  • 字符串也意味着你没有任何智能感知,这可能会导致未知的错误进一步下降。
  • 所以我们不要使用字符串。 让我们回顾一下最初如何重写'OrderBy:

    .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
    

    当你认为OrderBy是一个普通的方法,与我们可以开发的任何自定义方法没有什么不同,那么你应该明白a => a.customer.Select(b => b.OtherAddress).ToList()[0]是无非是一个正在传递的参数

    该参数的类型是Func<A,B> ,其中:

  • A等于您实体的类型。 所以在这种情况下, A与您现有方法中的T相同。
  • B等于您的排序值的类型。
  • OrderBy(x => x.MyIntProp)表示B的类型为int
  • OrderBy(x => x.MyStringProp)表示Bstring类型。
  • OrderBy(x => x.Customer)表示BCustomer类型。
  • 一般来说, B的类型对你来说并不重要(因为它只会被LINQ的内部订购方法使用)。

    我们来看一个使用OrderBy 参数的非常简单的扩展方法:

        public static IQueryable<A> OrderData<A, B>(this IQueryable<A> data, Func<A, B> orderbyClause)
        {
            return data
                    .OrderBy(orderbyClause)
                    .AsQueryable();
        }
    

    使用该方法如下所示:

    IQueryable<MyEntity> myData = GetData(); //assume this returns a correct value
    
    myData = myData.OrderData(x => x.MyIntProperty);
    

    请注意,在调用方法时,我不需要指定任何泛型类型参数。

  • A已知为MyEntity ,因为我们正在对IQueryable<MyEntity>类型的对象调用该方法。
  • B已知是int ,因为使用的lambda方法返回int类型的值(来自MyIntProperty
  • 就目前来看,我的示例方法只是一个无聊的包装,与现有的OrderBy方法没什么不同。 但是你可以改变方法的逻辑以适应你的需要,并且实际上使它与现有的OrderBy方法有很大的不同。


    你的期望

    你对你的目标的描述让我觉得你的期望太高了。

    我需要将 “customer.0.OtherAddress” 嵌套文件与整个基础数据进行比较 。 但它只为那个领域排序。 对于这种情况,我找到该字段值并将其存储到TempData。 然后排序TempData字段。

    我需要单独排序父节点而不是兄弟姐妹。 QueryData.Select(a => new {a,TempData = a.customer.Select(b => b.OtherAddress).ToList()[0]})。OrderBy(a => a.TempData).Select(a = > aa); 我根据临时数据对原始数据进行排序。 然后我单独分割原始数据。

    基于一个OrderBy调用不可能对整个嵌套数据结构进行排序。 OrderBy只对您调用Orderby的集合进行Orderby ,没有别的。

    如果您有一个Customer实体列表,每个实体都有一个Adress实体列表,那么您正在处理许多列表(客户列表和多个地址列表)。 OrderBy只会对您要求排序的列表进行排序,它不会查找任何嵌套列表。

    你提到你的TempData解决方案有效。 实际上,我写了一个完整的答案来反驳这个概念(它应该和我提出的替代方法在功能上类似,它应该总是排序原始列表,而不是任何嵌套列表),直到我注意到你已经使其工作了一个非常阴险和不明显的原因:

    .Select(a => new { 
                    a, 
                    TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] 
            })
    

    你正在调用.ToList() ,它改变了代码的行为。 你从一个IQueryable<> ,这意味着LINQ在枚举数据准备了一个SQL命令来检索数据。
    这是IQueryable<>的目标。 取而代之的是将所有数据提取到内存中,然后根据您的规范对其进行过滤,而不是构造复杂的SQL查询,然后只需执行一个(构造的)查询。

    当您尝试访问数据时,会执行构建的查询(显然,如果要访问数据,需要提取数据)。 这样做的一种常见方法是将IQueryable<> 枚举IEnumerable<>

    这就是你在Select lambda中所做的。 不要求LINQ列举您的订单列表,而是要求它列出订单列表中每个订单的每个客户的每个地址列表。

    但为了知道哪些地址需要枚举,LINQ必须首先知道应该从哪些客户那里获得地址。 为了找出它需要的客户,它必须首先弄清楚你正在使用哪些订单。 它可以把所有这些都列出来的唯一方法就是列举所有的东西

    我最初的建议,你应该避免使用TempData解决方案,仍然有效。 这是一个冗余的步骤,没有任何功能的目的。 但是, 枚举也可能对您有用,因为它会稍微改变LINQ的行为。 你声称它可以解决你的问题,所以我将在表面上看你的陈述,并假设LINQ-to-SQL和LINQ-to-Entities之间略有不同的行为解决了你的问题。

    您可以保留枚举并仍然省略TempData解决方法:

    IQueryable abc = QueryData
                        .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
                        .AsEnumerable()
                        .AsQueryable();
    

    一些脚注:

  • 你可以使用ToList()而不是AsEnumerable() ,结果是一样的。
  • 当你使用First()Single() ,枚举将固有地发生,所以你不需要事先调用AsEnumerable()
  • 请注意,我将结果转换为IEnumerable<> ,但是我立即将其重新转换为IQueryable<>枚举集合后,其中的任何进一步操作将发生在内存中 。 将其重新转换为IQueryable<>不会改变集合已被列举的事实。

  • 但它有效吗?

    现在,我认为这仍然不会通过一次调用来对所有嵌套列表进行排序。 但是, 你声称它确实如此 。 如果你仍然相信它,那么你不需要阅读(因为你的问题已经解决了)。 否则,以下内容可能对您有用。

    SQL以及LINQ扩展使得可以根据列表中未找到的信息对列表进行排序。 这基本上就是你在做什么,你要求LINQ根据相关地址对订单列表进行排序(不管你是否希望从数据库中检索地址!)你并没有要求它对客户或地址进行排序。 你只是要求它对订单进行排序。

    你的排序逻辑对我来说有点肮脏。 您正在为您的OrderBy方法提供一个Address实体,而无需指定其任何(值类型)属性。 但你如何期待你的地址被分类? 按字母顺序排列街道名称? 通过数据库ID? ...

    我希望你更清楚你想要什么,例如OrderBy(x => x.Address.Street).ThenBy(x => x.Address.HouseNumber) (这是一个简化的例子)。

    枚举后,由于所有(相关)数据都在内存中,因此可以开始对所有嵌套列表进行排序。 例如:

    foreach(var order in myOrders)
    {
        order.Customer.Addresses = order.Customer.Addresses.OrderBy(x => x.Street).ToList();
    }
    

    这将命令所有地址列表。 它不会更改订单列表的顺序

    请记住,如果您想要在内存中订购数据,您确实需要将数据存在内存中。 如果您从未加载客户的地址,则不能使用地址作为排序参数。

  • 订单列表的订单应该在枚举之前完成。 通过SQL数据库处理通常会更快,这正是您使用LINQ到SQL时的情况。
  • 排序后嵌套列表应该在枚举后完成,因为这些列表的顺序与原始IQueryable<Order>无关,后者仅着重于排序顺序,而不是其嵌套的相关实体(在枚举过程中,所包含的实体,例如CustomerAddress没有命令他们检索)。

  • 你可以转换你的OrderBy所以你不需要匿名类型(虽然我喜欢Perl / Lisp Schwartzian变换),然后它很容易动态创建(虽然我不确定你的意思是多么动态)。

    使用新的表达式:

    var abc = QueryData.OrderBy(a => a.customer[0].OtherAddress);
    

    不确定你的动态是什么意思,你可以创建lambda

    x => x.OrderBy(a => a.customer[0].Otheraddress)
    

    使用表达式如下:

    var parmx = Expression.Parameter(QueryData.GetType(), "x");
    var parma = Expression.Parameter(QueryData[0].GetType(), "a");
    var abc2 = Expression.Lambda(Expression.Call(MyExtensions.GetMethodInfo((IEnumerable<Orders> x)=>x.OrderBy(a => a.customer[0].OtherAddress)),
                                                 new Expression[] { parmx,
                                                                    Expression.Lambda(Expression.Property(Expression.ArrayIndex(Expression.Property(parma, "customer"), Expression.Constant(0)), "OtherAddress"), new[] { parma }) }),
                                 new[] { parmx });
    
    链接地址: http://www.djcxy.com/p/68303.html

    上一篇: How to build () => new { x.prop} lambda expression dynamically?

    下一篇: Dynamic lambda using an expression builder for select