【问题标题】:How to build () => new { x.prop} lambda expression dynamically?如何动态构建 () => new { x.prop} lambda 表达式?
【发布时间】:2017-11-02 09:36:29
【问题描述】:

如何动态创建下面的 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 个索引进行排序,则表示仅对 Customer 字段进行排序。我需要根据 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(datasource, "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] });

这是正确的解决方案吗?

【问题讨论】:

  • 为什么? ..........
  • 哎呀,您想在不付出任何努力的情况下实现这一点就不能认真吗?了解表达式的工作原理。还有谁在他的正确头脑中支持这样的问题?
  • 不确定,但我相信您正在搜索类似 AutoMapper github.com/AutoMapper/AutoMapper
  • 嗨@RandRandom 我也需要将这个概念用于其他一些数据,这就是我问的原因
  • 你不能在运行时创建匿名类型。但是为什么你需要它,如果你使用像 QueryData.OrderBy(a =&gt; a.customer.Select(b =&gt; b.OtherAddress).FirstOrDefault()) 这样没有匿名类型的等效简化版本,我想你应该没有问题动态构建表达式。

标签: c# linq lambda expression


【解决方案1】:

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&lt;T&gt;而不是IQueryable。否则,您将失去 LINQ 倾向于依赖的泛型类型定义。
  • 根据方法签名,您的预期用法应该是myData = myData.PerformComplexDataOperation("customer.0.OtherAddress")
  • 字符串是一种简单的技巧,可让您绕过其他强类型系统。虽然您的 strign 用法在技术上是实用的,但它非惯用语并且它为不可读和/或错误代码打开了大门
  • 使用字符串会导致人为的字符串解析逻辑。查看您的方法定义,并计算有多少行只是为了解析字符串并再次将其转换为实际代码。
  • 字符串还意味着您无法获得 Intellisense,这可能会导致后续出现不可见的错误。

所以我们不要使用字符串。让我们回顾一下我最初是如何重写 `OrderBy:

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

当您将OrderBy 视为普通方法时,与您和我可以开发的任何自定义方法没有什么不同,那么您应该了解a =&gt; a.customer.Select(b =&gt; b.OtherAddress).ToList()[0] 只不过是一个正在传递的参数 .

该参数的类型为Func&lt;A,B&gt;,其中:

  • A 等于您的实体的类型。所以在这种情况下,A 与您现有方法中的T 相同。
  • B 等于您的排序值的类型。
    • OrderBy(x =&gt; x.MyIntProp) 表示 B 的类型为 int
    • OrderBy(x =&gt; x.MyStringProp) 表示 B 的类型为 string
    • OrderBy(x =&gt; 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&lt;MyEntity&gt; 类型的对象上调用该方法。
  • 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 的集合进行排序,没有别的。

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

您提到您的TempData 解决方案有效。实际上,我写了一个完整的答案来反驳这个概念(它应该在功能上与我建议的替代方案相似,并且它应该始终对原始列表进行排序,而不是任何嵌套列表),直到我注意到你已经使它适用于一个非常阴险和不明显的原因:

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

您正在调用.ToList(),这会改变代码的行为方式。您从 IQueryable&lt;&gt; 开始,这意味着 LINQ 正在准备一个 SQL 命令来检索数据当您枚举它时
这是IQueryable&lt;&gt; 的目标。它不是将所有数据拉入内存,然后根据您的规范对其进行过滤,而是构造一个复杂的 SQL 查询,然后只需要执行一个(构造的)查询。

当您尝试访问数据时会执行该构造查询(显然,如果您想访问数据,则需要获取数据)。这样做的一种常见方法是枚举IQueryable&lt;&gt;IEnumerable&lt;&gt;

这就是您在 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&lt;&gt;,但随后我立即将其重新转换为 IQueryable&lt;&gt;一旦枚举了一个集合,对它的任何进一步操作都将在内存中进行。将其转换回 IQueryable&lt;&gt; 不会改变集合已被枚举的事实。

但它有效吗?

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

SQL 以及扩展的 LINQ 使得根据列表中未找到的信息对列表进行排序成为可能。这实际上就是您正在做的事情,您要求 LINQ 根据相关地址对 订单列表 进行排序(无论您是否希望从数据库中检索地址!)您不是要求它对客户或地址进行排序。您只是要求它对订单进行排序。

我觉得你的排序逻辑有点脏。您正在为您的 OrderBy 方法提供一个 Address 实体,而不指定它的任何(值类型)属性。但是您希望如何对您的地址进行排序?按字母顺序的街道名称?通过数据库ID? ...

我希望你能更明确地说明你想要什么,例如OrderBy(x =&gt; x.Address.Street).ThenBy(x =&gt; x.Address.HouseNumber)(这是一个简化的例子)。

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

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

这会对所有地址列表进行排序。 不会改变订单列表的顺序

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

  • orders 列表的排序应在 枚举之前完成。由您的 SQL 数据库处理它通常会更快,当您使用 LINQ-to-SQL 时会发生这种情况。
  • 嵌套列表的排序应该在枚举之后进行,因为这些列表的顺序与原来的IQueryable&lt;Order&gt;无关,它只关注排序,而不是其嵌套的相关实体(在枚举期间) , 包含的实体,例如 CustomerAddress 是在没有排序的情况下检索的)。

【讨论】:

  • @karthik:不客气。对长度感到抱歉:)但我怀疑部分问题是您的工作基于一些错误(但可以理解)的假设,这使得在不明确反驳这些假设的情况下很难准确地衡量解决方案。
【解决方案2】:

您可以转换您的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"), parma) }),
                             parmx);

【讨论】:

  • 我需要对父节点进行排序,而不是单独的兄弟节点。 QueryData.Select(a => new { a, TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] }).OrderBy(a => a.TempData).Select(a = > aa);我根据临时数据对原始数据进行排序。然后我单独拆分原始数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-03
  • 1970-01-01
相关资源
最近更新 更多