我有一个函数在数据库上下文中执行复杂的Where查询,然后应用传递给它的另一个转换:
static IQueryable<T> Query<T>(Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try {
using (var dbc = new MyDbContext() ) {
var res = dbc.ServicesData
.Where(sd =>
(path1 == null || (path1.Contains("%") || path1.Contains("_") ? EF.Functions.Like(sd.Path1, path1) : sd.Path1 == path1))
&& (path2 == null || (path2.Contains("%") || path2.Contains("_") ? EF.Functions.Like(sd.Path2, path2) : sd.Path2 == path2))
&& (path3 == null || (path3.Contains("%") || path3.Contains("_") ? EF.Functions.Like(sd.Path3, path3) : sd.Path3 == path3))
&& (path4 == null || (path4.Contains("%") || path4.Contains("_") ? EF.Functions.Like(sd.Path4, path4) : sd.Path4 == path4))
&& (path5 == null || (path5.Contains("%") || path5.Contains("_") ? EF.Functions.Like(sd.Path5, path5) : sd.Path5 == path5)));
return f(res.ToList().AsQueryable());
//return f(res).ToList().AsQueryable();
}
} catch (Exception ex_) {
return VList<T>.Empty.AsQueryable();
}
}
这是这样使用的:
IQueryable<int> Int1InLastHour(IQueryable<ServicesData> input) {
var lastHour = DateTimeOffset.Now.AddHours(-1).ToUnixTimeMilliseconds();
return input
.Where(v => (v.Time <= lastHour) && (v.Int1 is object))
.Select(v => v.Int1.Value);
}
var lastHourProcessTime = Query(Int1InLastHour, "Publisher", "%", "ItemProcessTime").Sum();
这可行,但是由于我在调用res.ToList()
之前先调用f
,所以f
中的linq是在内存中完成的,而不是在DB SQL上完成
如果我尝试将f(res.ToList().AsQueryable())
替换为f(res).ToList().AsQueryable()
,则会出现异常:
{“通过'RelationalProjectionBindingExpressExpressVisitor'处理LINQ表达式'[EntityShaperExpression] [ServicesData]'失败。这可能表示EF Core中存在错误或限制。有关详细信息,请参见https://go.microsoft.com/fwlink/?linkid=2101433。”}
我有什么办法解决这个问题?我如何以某种方式传递查询(Func<IQueryable<ServicesData>, IQueryable<T>>
,然后将其组合到Query
中的查询中,然后在dbc上执行它?
答案 0 :(得分:1)
一些问题。您可以拆分查询以分解结果,但DbContext的范围必须位于链的最外点,而不是最内层:
这里:
static IQueryable<T> Query<T>(Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try {
using (var dbc = new MyDbContext() ) { // DbContext should not be scoped here...
var res = dbc.ServicesData
最简单的重构方法:
static IQueryable<T> Query<T>(MyDbContext dbc, Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try
{
var res = dbc.ServicesData.AsQueryable();
if(path1 != null)
if(path1.Contains("%") || path1.Contains("_"))
res = res.Where(EF.Functions.Like(sd.Path1, path1));
else
res = res.Where(sd.Path1 == path1);
// Repeat for Path 2 - 5 ....
return f(res);
}
catch (Exception ex_)
{
return VList<T>.Empty.AsQueryable();
}
}
首先,我们传入DbContext。如果上下文在此作用域,则列表必须在返回之前实现。目的是允许调用者在执行列表之前进一步简化表达式。这意味着DbContext的作用域需要在此初始代之外并传递。使用IoC容器管理生存期作用域,如果注入了DbContext并将其作用域限制为Request或公共生存期作用域,则可以绕过此范围。
下一个改进建议是将参数的条件检查移出Linq并移入常规条件,以便仅在提供条件后才添加“赞/等于”检查。这将导致在服务器上运行更简单,更快的SQL。
因此最终结果将类似于:
using (var dbContext = new MyDbContext())
{
var lastHourProcessTime = Query(dbContext, Int1InLastHour, "Publisher", "%", "ItemProcessTime").Sum();
}
我有点到达您要去的地方,但是从EF中抽象表达式必然会导致代码混乱,并且仍然容易受到限制和错误。 IMO使其更简单通常可以减少问题,但是可以尝试一下,看它是否使您更接近。