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

时间:2017-11-02 09:36:29

标签: c# linq lambda expression

enter image description here如何动态创建以下linq表达式。

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

{'*.en': 1 }

实际数据:

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

如果我对OtherAddress字段进行排序,则第0个索引表示仅对Customer字段进行排序。我需要根据OtherAddress字段对整个订单数据进行排序。

我尝试过以下方式:

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

方法调用: PerformComplexDataOperation(datasource,“customer.0.OtherAddress”)

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

但是我无法获得 dataSource.Select中的值(a =&gt; new {a,TempData = property});

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

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

这是正确的解决方案吗?

2 个答案:

答案 0 :(得分:2)

XY问题?

这感觉就像the XY problem的情况一样。您的解决方案是人为的(没有违法行为),并且通过观察您提出的解决方案,您尝试解决的问题并不明显。

但是,我认为当您阅读代码意图而不是描述的意图时,您的问题有技术价值。

冗余步骤

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")
  • 字符串是容易入侵的,允许您绕过其他强类型系统。虽然您的strign使用在技术上有用,但非惯用并且打开了无法阅读和/或错误代码的大门
  • 使用字符串会导致人为的字符串解析逻辑。查看您的方法定义,并计算只需要解析字符串并将其转换为实际代码的行数。
  • 字符串也意味着你没有智能感知,这可能导致看不见的错误。

所以我们不要使用字符串。让我们回顾一下我最初如何重写`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)表示B的类型为string
    • OrderBy(x => x.Customer)表示B的类型为Customer

一般来说,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方法有所不同。

您的期望

你对目标的描述让我觉得你期待太多了。

  

我需要排序&#34; customer.0.OtherAddress&#34; 嵌套文件与整个基础数据相比。但它仅针对该领域进行了排序。在这种情况下,我找到该字段值并将其存储到TempData。然后排序TempData字段。

     

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

无法根据 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根据相关地址对订单列表进行排序(无论您是否希望从数据库中检索地址)或不!)您并不是要求它对客户或地址进行排序。您只是要求它对订单进行排序。

你的排序逻辑对我来说有点脏。您正在向Address方法提供OrderBy实体,而不指定其任何(值类型)属性。但是你怎么期望你的地址被排序?按字母顺序排列?按数据库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-to-SQL时会发生的情况。
  • 枚举之后,应该在之后完成对嵌套列表的排序,因为这些列表的顺序与原始IQueryable<Order>无关,原始Customer仅侧重于对订单进行排序,而不是其嵌套的相关实体(在枚举期间,检索包含的实体,例如Address和{{1}},而不对它们进行排序。)

答案 1 :(得分:1)

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

使用新表达式:

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

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

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

使用Expression如下:

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"), parma) }),
                             parmx);