【问题标题】:Can't add calculated value to IQueryable无法将计算值添加到 IQueryable
【发布时间】:2018-11-27 08:58:30
【问题描述】:

我正在运行一个需要计算免赔额的 EF 语句。经过长时间的尝试,我似乎无法在 .Select() 语句中添加自定义函数。相反,我试图在 .Select() 语句之后添加值。

这里的问题是,在我的CalculateDeductibles() 中,我似乎无法向item.Deductibles 添加任何值。

GetDeductibles(item.RequestId) 是一个相当繁重的函数,它会执行几个额外的查询,所以我试图阻止将我的 IQueryable 转换为 IList 对象。

所以实际上有2个问题:

  1. 我可以在.Select() 语句中直接使用GetDeductibles() 函数吗?
  2. 在我完成.Select() 之后,我能否以某种方式(密切关注性能)增加价值?

代码:

public IQueryable<ReinsuranceSlip> GetReinsuranceSlipsOverview(int userId, int companyId, string owner, string ownerCompany)
{
    IQueryable<ReinsuranceSlip> model = null;

    model = _context.Request
        .Where(w => w.RequestGroup.ProgramData.MCContactId == userId)
        .Select(x => new ReinsuranceSlip()
        {
            Id = x.Id,
            RequestId = x.Id,
            LocalPolicyNumber = x.LocalPolicyNumber,
            BusinessLine = x.RequestGroup.ProgramData.BusinessLine.DisplayName,
            BusinessLineId = x.RequestGroup.ProgramData.BusinessLine.Id,
            ParentBroker = x.RequestGroup.ProgramData.Broker.Name,
            LocalBroker = x.Broker.Name,
            InceptionDate = x.InceptionDate,
            RenewDate = x.RenewDate,
            //Deductibles = CalculateDeductibles(x)
        });

    CalculateDeductibles(model);

    return model;
}


private void CalculateDeductibles(IQueryable<ReinsuranceSlip> model)
{
    //model.ForEach(m => m.Deductibles = GetDeductibles(m.RequestId));
    foreach (var item in model)
    {
        item.Deductibles = GetDeductibles(item.RequestId);
    }
}

【问题讨论】:

  • 您正在使用 LINQ to EF,而不仅仅是 IQueryable。提供者应该能够将查询转换为 SQL,这意味着它不能包含客户端代码。 Q Linq-toSQL 和 EF Core 通过将尽可能多的查询转换为 SQL,然后在客户端上执行其余的处理,这也不是一件 的事情。缺点是他们会在没有警告的情况下在客户端上提取 LOT 数据。
  • 什么是DeductiblesGetDeductibles 是做什么的?这是计算还是尝试检索相关实体?
  • 无论如何,ORM适合报告查询。也许您应该创建一个生成所需结果的报告视图,然后将其映射到它自己的实体。或者创建一个适当的报告架构并映射到它
  • GetDeductibles 通过linq to EF从数据库中获取更多相关数据。见:i.stack.imgur.com/IK5c0.png
  • 存储过程能帮我解决这个问题吗?

标签: c# entity-framework linq iqueryable


【解决方案1】:

更新对不起这个答案的第一个版本。我不太明白。
答案1:IQueryable 是用来创建一个完整的SQL 语句在SQL Server 中调用的。所以如果你想使用 IQueryable,你的方法需要生成语句并返回它。您的 GetDetuctibles 方法获取请求 Id 参数,但您的可查询模型对象尚未从 DB 收集任何数据,并且它不知道 x.Id 值。更重要的是,您的 GetCarearDetuciples 会获得一个参数,因此使用该参数会生成一个可查询的对象,经过一些计算,它会返回十进制。我的意思是是的,您可以在 select 语句中使用您的方法,但这确实很复杂。您可以使用 AsExpendable() LINQ 方法并重写您的方法返回类型 Expression 或 Iqueryable。
有关详细信息,您应该检查。这个:

Entity Navigation Property IQueryable cannot be translated into a store expression
这个:http://www.albahari.com/nutshell/predicatebuilder.aspx

您还应该查看这篇文章以了解 IQueryable 接口:https://samueleresca.net/2015/03/the-difference-between-iqueryable-and-ienumerable/

答案 2:您可以使用 IEnumerable 接口代替 IQueryable 接口来实现这一点。在这种情况下,它将很容易使用。您可以进行性能测试并按时间改进您的方法。

但如果我是你,我会考虑使用存储过程来提高性能。

【讨论】:

  • 是的,答案 1 中的方法错误,此处复制粘贴错误,但我尝试了答案 1,它会给我以下错误:LINQ to Entities does not recognize the method 'System.Nullable1[System.Decimal] GetCarEarDeductibles(Web.DAL.Model.Request)' method, and this method cannot be translated into a store expression.
  • 答案 2 也无济于事。好像你不能像这样更新 IQueryable 对象。
  • 我只是更新我的答案。一开始没看懂。
  • 我也考虑过使用 IEnumerable 接口,但最后我认为存储过程会更好。
【解决方案2】:

您必须了解IEnumerableIQueryable 之间的区别。

IEnumerable 对象包含要枚举该对象表示的序列中的元素的所有内容。你可以请求第一个元素,一旦你得到它,你可以反复请求下一个元素,直到没有更多的下一个元素。

IQueryable 的工作方式不同。一个IQueryable 拥有一个Expression 和一个ProviderExpression 是对应该选择哪些数据的通用描述。 Provider 知道谁必须执行查询(通常是数据库),它知道如何将Expression 转换为Provider 可以理解的格式。

有两种类型的 LINQ 函数:返回 IQueryable&lt;TResult&gt; 的函数和返回 TResult 的函数。第一种类型的函数不执行查询,它们只会更改表达式。他们使用延迟执行。第二组的函数将执行查询。

当必须执行查询时,Provider 采用Expression 并尝试将其转换为执行查询的进程可以理解的格式。如果此进程是关系数据库管理系统,则通常是 SQL。

这种翻译是您无法添加自己的功能的原因:Expression 必须可翻译为 SQL,并且您的函数唯一可以做的就是调用将更改 Expression 的函数可以翻译成SQL。

事实上,即使是实体框架也不支持所有的 LINQ 功能。有一个list of Supported and Unsupported LINQ methods

回到你的问题

我可以在查询中直接使用 GetDeductibles 吗?

不,你不能,除非你可以让它变得如此简单,它只会使用支持的 LINQ 方法来更改Expression。您必须以扩展函数的格式编写它。见extension methods demystified

您的 GetDeductibles 应该有一个 IQueryable&lt;TSource&gt; 作为输入,并返回一个 IQueryable&lt;TResult&gt; 作为输出:

static class QueryableExtensions
{
    public static IQueryable<TResult> ToDeductibles<TSource, TResult, ...>(
       this IQueryable<TSource> source,
       ... other input parameters, keySelectors, resultSelectors, etc)
    {
         IQueryable<TResult> result = source... // use only supported LINQ methods
         return result;
    } 
}

如果您确实需要调用其他本地函数,请考虑在调用本地函数之前调用AsEnumerableToList 上面的优势是智能 IQueryable 提供程序,如 Entity Framework 中的提供程序,不会获取所有项目,但 每页 的项目除外。因此,如果您只需要几个,您就不会将所有数据传输到本地进程。确保在调用 AsEnumerable 之前丢弃所有不再需要的数据,从而限制传输的数据量。

我可以在完成 .Select() 后以某种方式添加值

LINQ 旨在查询数据,而不是更改数据。在您可以更改数据之前,您必须在更改数据之前将其具体化。在数据库查询的情况下,这意味着您拥有存档数据的副本,而不是原始数据。因此,如果您进行更改,您将更改副本,而不是原件。

使用实体框架时,您必须获取要更新/删除的每个项目。确保您不选择值,而是选择原始项目。

不是:

var schoolToUpdate = schoolDbContext.Schools.Where(schoolId = 10)
   .Select(school = new
   {
       ... // you get a copy of the values: fast, but not suitable for updates
   })
   .FirstOrDefault();

但是:

School schoolToUpdate = schoolDbContext.Schools.Where(schoolId = 10)
   .FirstOrDefault()

现在您的 DbContext 在其 ChangeTracker 中有原始 School。如果您更改 SchoolToUpdate 并调用 SaveChanges,则会将您的 SchoolToUpdate 与原始 School 进行比较,以检查 School 是否必须更新。

如果需要,您可以绕过此机制,将新 School 直接附加到 ChangeTracker,或调用存储过程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 2020-05-07
    相关资源
    最近更新 更多