简体   繁体   English

您是否必须使用 .Include 在 EF Core 5 中加载子对象?

[英]Do you have to use .Include to load child objects in EF Core 5?

I'm used to EF 6, I'm new to EF Core.我习惯了 EF 6,我是 EF Core 的新手。 I'm trying to do a simple fetch from a table which has some foreign key relationships to lookup tables.我正在尝试从一个表中进行简单的提取,该表与查找表有一些外键关系。

I started with this:我从这个开始:

List<StatementModel> myStatements = new List<StatementModel>();

myStatements = db.Statements.Select(s => new StatementModel
{
    StatmentId = s.StatementId,
    EmployeeNumber = s.EmployeeNumber,
    FirstName = s.Employee.FirstName,
    LastName = s.Employee.LastName,
    PlanType = s.Employee.PlanType != null ? s.Employee.PlanType.PlanTypeName : "",
    FiscalPeriod = s.FiscalPeriod.StartDate.ToString("yyyy-MM-dd") + " - " + s.FiscalPeriod.EndDate.ToString("yyyy-MM-dd"),
    CostCenterId = s.Employee.CostCenterId,
    RVPName = s.Employee.CostCenter.Evp != null ? s.Employee.CostCenter.Evp.FirstName + " " + s.Employee.CostCenter.Evp.LastName : "",
    SVPName = s.Employee.CostCenter.Svp != null ? s.Employee.CostCenter.Svp.FirstName + " " + s.Employee.CostCenter.Svp.LastName : "",
    LOBMgrName = s.Employee.CostCenter.Lobmgr != null ? s.Employee.CostCenter.Lobmgr.FirstName + " " + s.Employee.CostCenter.Lobmgr.LastName : "",
    AdminApprovalStatus = s.AdminApprovalStatus.ApprovalStatusName,
    StatementStatus = s.StatementStatus.StatementStatusName,
    AmountDue = s.AmountDue
}).Where(s => s.StatementStatusId == "PAA").ToList();

But it gave me a runtime error saying it couldn't translate this into SQL.但是它给了我一个运行时错误,说它无法将其转换为 SQL。 I think it's a problem with .ToString() and the date formatting.我认为这是.ToString()和日期格式的问题。

So I changed it to:所以我把它改成:

List<StatementModel> myStatements = new List<StatementModel>();
var statements = db.Statements.Where(s => s.StatementStatusId == "PAA").ToList();

foreach (var s in statements)
{
    StatementModel sm = new StatementModel();

    sm.StatmentId = s.StatementId;
    sm.EmployeeNumber = s.EmployeeNumber;
    sm.FirstName = s.Employee.FirstName;
    sm.LastName = s.Employee.LastName;
    sm.PlanType = s.Employee.PlanType != null ? s.Employee.PlanType.PlanTypeName : "";
    sm.FiscalPeriod = s.FiscalPeriod.StartDate.ToString("yyyy-MM-dd") + " - " + s.FiscalPeriod.EndDate.ToString("yyyy-MM-dd");
    sm.CostCenterId = s.Employee.CostCenterId;
    sm.RVPName = s.Employee.CostCenter.Evp != null ? s.Employee.CostCenter.Evp.FirstName + " " + s.Employee.CostCenter.Evp.LastName : "";
    sm.SVPName = s.Employee.CostCenter.Svp != null ? s.Employee.CostCenter.Svp.FirstName + " " + s.Employee.CostCenter.Svp.LastName : "";
    sm.LOBMgrName = s.Employee.CostCenter.Lobmgr != null ? s.Employee.CostCenter.Lobmgr.FirstName + " " + s.Employee.CostCenter.Lobmgr.LastName : "";
    sm.AdminApprovalStatus = s.AdminApprovalStatus.ApprovalStatusName;
    sm.StatementStatus = s.StatementStatus.StatementStatusName;
    sm.AmountDue = s.AmountDue;

    myStatements.Add(sm);
}

But then I got a bunch of null reference errors for any of the child objects like Employee.CostCenter or FiscalPeriod.StartDate .但是后来我收到了一堆针对任何子对象(如Employee.CostCenterFiscalPeriod.StartDate的空引用错误。

So then I changed it to:于是我把它改成了:

var statements = db.Statements.Include("Employee.CostCenter.Evp")
              .Include("Employee.CostCenter.Svp")
              .Include("Employee.CostCenter.Lobmgr")
              .Include("FiscalPeriod")
              .Include("AdminApprovalStatus")
              .Include("StatementStatus").Where(s => s.StatementStatusId == "PAA").ToList();

foreach (var s in statements)
{
    StatementModel sm = new StatementModel();

    sm.StatmentId = s.StatementId;
    sm.EmployeeNumber = s.EmployeeNumber;
    sm.FirstName = s.Employee.FirstName;
    sm.LastName = s.Employee.LastName;
    sm.PlanType = s.Employee.PlanType != null ? s.Employee.PlanType.PlanTypeName : "";
    sm.FiscalPeriod = s.FiscalPeriod.StartDate.ToString("yyyy-MM-dd") + " - " + s.FiscalPeriod.EndDate.ToString("yyyy-MM-dd");
    sm.CostCenterId = s.Employee.CostCenterId;
    sm.RVPName = s.Employee.CostCenter.Evp != null ? s.Employee.CostCenter.Evp.FirstName + " " + s.Employee.CostCenter.Evp.LastName : "";
    sm.SVPName = s.Employee.CostCenter.Svp != null ? s.Employee.CostCenter.Svp.FirstName + " " + s.Employee.CostCenter.Svp.LastName : "";
    sm.LOBMgrName = s.Employee.CostCenter.Lobmgr != null ? s.Employee.CostCenter.Lobmgr.FirstName + " " + s.Employee.CostCenter.Lobmgr.LastName : "";
    sm.AdminApprovalStatus = s.AdminApprovalStatus.ApprovalStatusName;
    sm.StatementStatus = s.StatementStatus.StatementStatusName;
    sm.AmountDue = s.AmountDue;

    myStatements.Add(sm);
}

This works, but it's very verbose and I don't recall ever having to explicitly .Include things in EF 6 unless it was an optimization issue.这有效,但它非常冗长,我不记得必须明确.Include EF 6 中的东西,除非它是优化问题。

Is this the only or best way to do it in EF Core or am I missing something?这是在 EF Core 中唯一或最好的方法,还是我遗漏了什么?

There are a few separate issues here, however the overall problem is that you are stuck trying to pick either the database (SQL) to manage the DTO projection OR the local C# runtime.这里有几个单独的问题,但总体问题是您在尝试选择数据库 (SQL) 来管理 DTO 投影或本地 C# 运行时时遇到困难。

  • Your deferred SQL version (the initial attempt) failed due to the incorrect DateTime.ToString() syntax.由于 DateTime.ToString() 语法不正确,您延迟的 SQL 版本(初始尝试)失败。
  • The C# version is verbose in terms of .Include() , mixed with magic strings, but also very inefficient, you are pulling back potentially hundreds of columns across the wire, only to ignore most of the columns when you project the results into the DTO C# 版本在.Include()方面很冗长,与魔术字符串混合,但效率也很低,您可能会通过线路拉回数百列,当您将结果投影到 DTO 时,只会忽略大部分列

For assistance specifically on .Include please review these previous posts:如需专门针对.Include帮助,请查看这些以前的帖子:

For problems like this you should consider a Hybrid approach, one that respects and exploits the strengths of both environments.对于此类问题,您应该考虑采用混合方法,即尊重并利用两种环境的优势的方法。

  1. Project the DB elements that your logic needs, but keep them in the raw format, let EF and SQL work together to optimise the output.投影您的逻辑所需的 DB 元素,但将它们保留为原始格式,让 EF 和 SQL 协同工作以优化输出。

    NOTE: There is no need to formalise this projection model, instead we can simply use an anonymous type!注意:不需要形式化这个投影模型,我们可以简单地使用匿名类型!

     List<StatementModel> myStatements = new List<StatementModel>(); var dbQuery = db.Statements.Select(s => new { s.StatementId, s.EmployeeNumber, s.Employee.FirstName, s.Employee.LastName, s.Employee.PlanType.PlanTypeName, s.FiscalPeriod.StartDate, s.FiscalPeriod.EndDate, s.Employee.CostCenterId, Evp = new { s.Employee.CostCenter.Evp.FirstName, s.Employee.CostCenter.Evp.LastName }, Svp = new { s.Employee.CostCenter.Svp.FirstName, s.Employee.CostCenter.Svp.LastName }, LOBMgr = new { s.Employee.CostCenter.Lobmgr.FirstName, s.Employee.CostCenter.Lobmgr.LastName }, s.AdminApprovalStatus.ApprovalStatusName, s.StatementStatus.StatementStatusName, s.AmountDue }).Where(s => s.StatementStatusId == "PAA");

    It is not necessary to nest the Evp , Svp , LOBMgr projections, you could flatten the entire resultset, it would actually be ever so slightly more efficient, but this shows the possibilities没有必要嵌套EvpSvpLOBMgr投影,您可以展平整个结果集,实际上效率会稍微提高一些,但这显示了可能性

  2. Now project the result set into DTOs in memory, in this way you get total C# control over the type casting and string formatting.现在将结果集投影到内存中的 DTO 中,这样您就可以获得对类型转换和字符串格式的完全 C# 控制。

     myStatements = dbQuery.ToList() .Select(s => new StatementModel { StatmentId = s.StatementId, EmployeeNumber = s.EmployeeNumber, FirstName = s.FirstName, LastName = s.LastName, PlanType = s.PlanTypeName ?? "", FiscalPeriod = $"{s.StartDate:yyyy-MM-dd} - {s.EndDate:yyyy-MM-dd}", CostCenterId = s.CostCenterId, RVPName = $"{s.Evp.FirstName} {s.Evp.LastName}".Trim(), SVPName = $"{s.Svp.FirstName} {s.Svp.LastName}".Trim(), LOBMgrName = $"{s.LOBMgr.FirstName} {s.LOBMgr.LastName}".Trim(), AdminApprovalStatus = s.ApprovalStatusName, StatementStatus = s.StatementStatusName, AmountDue = s.AmountDue }).ToList();

Notice there are NO includes !注意没有包括 we relace the Includes with the initial Projection.我们将包含与初始投影相关联。 IMO I find this code is more natural, includes can be quite indirect, its not always obvious why we need them or when we've forgotten to add them at all. IMO 我发现这段代码更自然,包含可能非常间接,为什么我们需要它们或者我们根本忘记添加它们并不总是很明显。 This code feels like double handling a bit, but we only bring back the specific columns we need and don't have to get in the way of clean SQL by trying to get the database to format the data values when we can do that with minimal effort in C#.这段代码感觉有点像双重处理,但我们只带回了我们需要的特定列,而不必通过尝试让数据库格式化数据值来妨碍干净的 SQL,当我们可以用最少的时间做到这一点时在 C# 中的努力。

Date formatting is trivial, but this technique can be very powerful if you need to perform complex formatting or other processing logic on the results that you already have in c# without replicating that logic into SQL friendly Linq.日期格式化是微不足道的,但如果您需要对 C# 中已有的结果执行复杂的格式化或其他处理逻辑,而不将该逻辑复制到 SQL 友好的 Linq 中,则此技术可能非常强大。


Avoid Magic strings in Includes避免在 Includes 中使用魔术字符串

If you are going to use Includes, you should try to avoid the string variant of include and instead use the lambdas.如果您打算使用 Includes,您应该尽量避免使用 include 的字符串变体,而是使用 lambdas。 This will allow the compiler to notify you or later devs when the schema changes might invalidate your query:这将允许编译器在架构更改可能使您的查询无效时通知您或更高版本的开发人员:

    .Include(x => x.Employee.CostCenter.Evp)
    .Include(x => x.Employee.CostCenter.Svp)
    .Include(x => x.Employee.CostCenter.Lobmgr)
    .Include(x => x.FiscalPeriod)
    .Include(x => x.AdminApprovalStatus)
    .Include(x => x.StatementStatus) 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM