Linq,通过 IOrderedEnumerable 对 IQueryable 进行排序
我有一个 MVC telerik 网格,我已将其设置为对 IQueryable 进行自定义绑定,以方便计算列的排序。网格在此属性上排序时的默认排序行为是这样的:
data = data.OrderBy(product => product.oneMthCost_IntOnly);
“data”是 IQueryable,product.oneMthCost_IntOnly 不会从数据库返回,它是一个计算属性,在为此调用“get”访问器时计算“SearchViewModel”上的属性:
public class SearchViewModel
{
public int ID { get; set; }
public string lend_name { get; set; }
public decimal? pDes_rate { get; set; }
public string pDes_details { get; set; }
public int? pDes_totTerm { get; set; }
public decimal? pDes_APR { get; set; }
public string pDes_revDesc { get; set; }
public string pMax_desc { get; set; }
public DateTime? dDipNeeded { get; set; }
public DateTime? dAppNeeded { get; set; }
public decimal oneMthCost_IntOnly
{
get { return ProductOperations.CalculateSingleYearInterestCost(SourcingModel.search.LoanAmt, (decimal)pDes_rate); }
}
}
为了解释如何返回 SearchViewModel(“数据”),它基于实体数据模型,该模型使用以下延迟的 Linq 查询作为网格投影到搜索视图模型。
//Return the required products
var model = from p in Product.Products
where p.archive == false && ((Prod_ID == 0) || (p.ID == Prod_ID))
select new SearchViewModel
{
ID = p.ID,
lend_name = p.Lender.lend_name,
pDes_rate = p.pDes_rate,
pDes_details = p.pDes_details,
pDes_totTerm = p.pDes_totTerm,
pDes_APR = p.pDes_APR,
pDes_revDesc = p.pDes_revDesc,
pMax_desc = p.pMax_desc,
dDipNeeded = p.dDipNeeded,
dAppNeeded = p.dAppNeeded
};
因此,使用网格默认行为,如下所示:
data = data.OrderBy(product => product.CalculatedProp);
当此列排序时引发此错误:
不支持指定的类型成员“oneMthCost_IntOnly” LINQ 到实体。仅初始值设定项、实体成员和实体 支持导航属性。
嗯,这确实有道理,表达式树在使用“get”访问器获取该值之前并不知道该值是什么。因此,我非常接受这样的事实:我需要具体化整个对象集,对每一行进行计算,然后对它的 IQueryable 进行排序(不幸的是,业务逻辑太复杂,表达式树无法将计算进行转换)转换为 SQL,因此需要 C# 方法)。所以我执行以下操作:
var calcdata = data.ToList().OrderBy(p => p.oneMthCost_IntOnly);
具体化所有数据,执行所有计算,并将其排序到 IOrderedEnumerable calcdata 中...这是问题:
如何将“calcdata”连接到“data”以对 IQueryable 进行排序数据由 IOrderedEnumerable“calcdata”的键?将网格重新绑定到“calcdata”会弄乱分页,但是,如果您建议这是最好的方法,我也可以遵循此路径。
抱歉,这有点啰嗦,信息量相当大我想包括在内,以便为您提供最完整的图片。
谢谢,
马克
I have an MVC telerik grid that I have set up to do custom binding to an IQueryable to facilitate the sorting of a calculated column. The default sort behavior when the grid is sorted on this property is this:
data = data.OrderBy(product => product.oneMthCost_IntOnly);
"data" is the IQueryable, product.oneMthCost_IntOnly is not returned from the database, it is a calculated property which is calculated when the "get" accessor is called for this property on the "SearchViewModel":
public class SearchViewModel
{
public int ID { get; set; }
public string lend_name { get; set; }
public decimal? pDes_rate { get; set; }
public string pDes_details { get; set; }
public int? pDes_totTerm { get; set; }
public decimal? pDes_APR { get; set; }
public string pDes_revDesc { get; set; }
public string pMax_desc { get; set; }
public DateTime? dDipNeeded { get; set; }
public DateTime? dAppNeeded { get; set; }
public decimal oneMthCost_IntOnly
{
get { return ProductOperations.CalculateSingleYearInterestCost(SourcingModel.search.LoanAmt, (decimal)pDes_rate); }
}
}
To explain how the SearchViewModel ("data") is returned, it is based on an Entity Data Model that uses the following deferred Linq query as a basis for the grid to project into the SearchViewModel.
//Return the required products
var model = from p in Product.Products
where p.archive == false && ((Prod_ID == 0) || (p.ID == Prod_ID))
select new SearchViewModel
{
ID = p.ID,
lend_name = p.Lender.lend_name,
pDes_rate = p.pDes_rate,
pDes_details = p.pDes_details,
pDes_totTerm = p.pDes_totTerm,
pDes_APR = p.pDes_APR,
pDes_revDesc = p.pDes_revDesc,
pMax_desc = p.pMax_desc,
dDipNeeded = p.dDipNeeded,
dAppNeeded = p.dAppNeeded
};
Using the grids default behavior, therefore, the below:
data = data.OrderBy(product => product.CalculatedProp);
Throws this error when this column is sorted on:
The specified type member 'oneMthCost_IntOnly' is not supported in
LINQ to Entities. Only initializers, entity members, and entity
navigation properties are supported.
Well, this does make sense, the expression tree doesn't know what this value is going to be until it has got it using the 'get' accessor. So, I am quite resigned to the fact I will need to materialize the whole set of objects, do the calculation for each row, and then sort the IQueryable on it (unfortunately the business logic is too complex for the expression tree to turn the calculation into SQL, so a C# method is required). So I do the below:
var calcdata = data.ToList().OrderBy(p => p.oneMthCost_IntOnly);
Which materializes all the data, does all the calculations, and sorts it into an IOrderedEnumerable calcdata... here is the rub:
How do I join "calcdata" to "data" to sort the IQueryable data by IOrderedEnumerable "calcdata"s key? Rebinding the grid to "calcdata" messes up paging, however, if you suggest this is the best way ahead, I can follow this path also.
Apologies this is a bit long winded, there is just quite a lot of information I wanted to include to give you the fullest picture.
Thanks,
Mark
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好的-这是相当高的水平,是对您非常完整的问题的简短回答。
我们在订购非常复杂的数据集时遇到了问题。和你一样,我们试图实现它们,但表现很糟糕。最后我们所做的是在数据库中创建一个视图,其中包含我们需要排序的列。我们将此视图包含在实体模型中,然后它的行为就像一个表(老实说有一些警告)。
一旦我们在 EF 模型中拥有视图,我们就可以使用 order by 而无需实现。您的视图还可以包含您需要的联接。
这种方法很大程度上取决于您对数据库的控制权,我很欣赏并非每个开发人员都这样做。我们发现这种通用方法非常成功,特别是如果您可以以这样的方式构造视图,就可以获得它们的主键。
如果您无法将 View 放入数据库中,那么您可以使用实体框架构造,例如 QueryViews 达到类似的效果。然而,它们有自己的限制 - 例如,不能再自动更新模型,可以将 QueryView 以外的任何内容加入其中。我相信它会起作用,但您需要亲自动手并为此直接修改 EF 模型 xml。
视图注意事项
我们必须在将存储过程映射到视图方面做一些调整。因为它是只读的,所以我们并不想打扰插入,但是在删除依赖实体时,我从视图中删除了。说实话 - 我们此时对它进行了攻击,并添加了一个影响一行和这项工作的虚拟过程。但一定有比这更好的答案。
好吧——比我开始写作时想象的要简短。
编辑
如果您想加入两个集合,可以使用
join
关键字。由于一个来自数据库,另一个来自 c#,那么它们都必须 bu IEnumerables - 即通过ToList()
实现的 EF 查询和适当集合(即通用列表)中的 c# 对象。那么语法可能是
当然这假设两个集合之间有一个公共键。而且效率可能很糟糕。
OK- this is quite high level and a brief answer to your very full question.
We were hitting problems with ordering very complex dataset. Like you we tried to realise them and the performance was awful. What we did in the end was create a View in the database with the columns that we needed to order by. We included this View in the Entity model which then behaves just like a table (some caveats to be honest).
Once we had the View in the EF model we could then use order by without materialising. You view could also contain the join that you need.
This method very much depends on you having that control over your database which I appreciate not every developer does. We found this general approach very successful particular if you could construct the View in such a way it is possible to get a primary key on them.
If you can't put View in your databases then you could use entity framework constructs such as QueryViews to similar effect. These have their own constraints however - e.g. can't update the model automatically any more, can join anything other than QueryView to them. I believe it would work but you would need to get your hands dirty and amend the EF model xml directly for this.
Caveats for views
We had to do some fiddling around mapping stored procs to the Views. Because it was read only we didn't really want to bother with inserts but when deleting dependent entities I expacted a delete from the view. TBH - we hacked it at this point and included a dummy proc that affected one row and this work. There must be a better answer than this though.
OK - less brief than I thought it was going to be when I started writing.
EDIT
if you want to join the two collections you could with the
join
keyword. As one is from the database and the other from c# then they would both have to bu IEnumerables - i.e the EF query realised viaToList()
and the c# object in an appropriate collection i.e. generic List.Then the syntax might be
Of course this assumes there is a common key between the two collection. Also efficiency might be terrible.
我在正确的轨道上进行了以下操作:
当以这种方式排序时,这会具体化数据的所有行(我知道性能不是很好,但这是能够对数据库执行的数据进行这种排序的唯一方法不知道)。我遇到的麻烦是上面创建了一个 IOrderedEnumerable,当对其进行分页时,如下所示:
这会发出一个 IEnumerable 返回(我假设它会发出一个 IOrderedEnumerable),所以试图转换回这种类型,然后破坏了分页。上面的方法适用于 calcdata 是 IEnumerable,而不是 IOrderedEnumerable。 IQueryable 不需要加入(当您拥有所需的所有信息时为什么要返回数据库?),因此我们重新绑定到 IEnumerable。
感谢您输入 Crab Bucket,它引导我进行了一些新的询问,帮助我最终解决了这个问题!
问候,
马克
I was on the right track with the below:
This meterializes all of the rows of the data when sorted in this way (not very performant, I know, but it is the only way to be able to do this sort on data the database does not know about). The trouble I was having was the above creates an IOrderedEnumerable, when paging this, as below:
This emits an IEnumerable back (where I was assuming it would emit an IOrderedEnumerable), so was trying to cast back to this type which then broke the paging. The above works where calcdata is an IEnumerable, NOT an IOrderedEnumerable. No join is required to the IQueryable (why go back to the database when you have all the information required?), so we rebind to the IEnumerable.
Thank you for the input Crab Bucket, it led me down some new lines of enquiry that have helped me to eventually sort this problem out!
Regards,
Mark