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;
}
这是正确的解决方案吗?
答案 0 :(得分:2)
这感觉就像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")
。所以我们不要使用字符串。让我们回顾一下我最初如何重写`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();
}
这命令所有地址列表。 它不会更改订单列表的顺序。
请记住,如果您想在内存中订购数据,那么您确实需要将数据存储在内存中。如果您从未加载客户的地址,则无法使用地址作为排序参数。
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);