使用EFCore 3.0组合Linq表达式

时间:2019-11-11 01:08:42

标签: entity-framework entity-framework-core entity-framework-core-3.0

我有一个函数在数据库上下文中执行复杂的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上执行它?

1 个答案:

答案 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使其更简单通常可以减少问题,但是可以尝试一下,看它是否使您更接近。