[英]Include vs select projects in EfCore
我有这个查询
var answers = await context.QuestionnaireUserAnswers
.Include(a => a.QuestionAnsweres)
.Include(a => a.QuestionnaireSentOut).ThenInclude(q => q.Questionnaire)
.Include(a => a.User).ThenInclude(w => w.Organization)
.Where(a => a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedDraft &&
a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedPublished)
.Where(a => a.QuestionnaireSentOut.QuestionnaireType.Equals(QuestionnaireSentType.Normal))
.Where(a => a.Status.Equals(QuestionAnsweredStatus.Draft))
.Where(a => a.NextReminderDate < DateTime.UtcNow)
.ToListAsync();
我改成了这个
var answers = await context.QuestionnaireUserAnswers
.Where(a => a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedDraft &&
a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedPublished)
.Where(a => a.QuestionnaireSentOut.QuestionnaireType.Equals(QuestionnaireSentType.Normal))
.Where(a => a.Status.Equals(QuestionAnsweredStatus.Draft))
.Where(a => a.NextReminderDate < DateTime.UtcNow)
.Select(ans => new QuestionnaireApiServiceReminderModel
{
AnswerId = ans.Id,
Created = ans.Created,
NextReminderDate = ans.NextReminderDate,
QuestionnaireSentOutQuestionnaireTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
QuestionnaireSentOutTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
User = new SimpleUserWithOrganizationId
{
OrganizationId = ans.User.OrganizationId,
UserId = ans.UserId,
Email = ans.User.Email,
Name = ans.User.Name,
}
})
.ToListAsync();
这样我就只得到我需要的那些属性。
在我的代码下方执行此操作
foreach (var answer in answers) {
.. // some calls
answer.NextReminderDate = DateTime.UtcNow.AddDays(7);
}
await context.SaveChangesAsync();
现在,如果我的更新版本为 go,那么我需要在更新之前获取相应的 QuestionnaireUserAnswers。 这将是多次往返数据库。 这是否意味着在这种情况下最好使用包含的第一个查询?
处理这个问题的一种方法是写这样的东西
await context.Database.ExecuteSqlInterpolatedAsync($"UPDATE QuestionnaireUserAnswers SET NextReminderDate = {newNextReminderDate} WHERE Id IN ({string.Join(',', answers.Select(x => x.AnswerId))})");
但这是经过批准的 efcore 方式吗?
那么在这个 scanerio 中这是一个更好的解决方案吗?
var answers = await context.QuestionnaireUserAnswers
.Where(a => a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedDraft &&
a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedPublished)
.Where(a => a.QuestionnaireSentOut.QuestionnaireType.Equals(QuestionnaireSentType.Normal))
.Where(a => a.Status.Equals(QuestionAnsweredStatus.Draft))
.Where(a => a.NextReminderDate < DateTime.UtcNow)
.Select(ans => new QuestionnaireApiServiceReminderModel
{
Answer = ans,
AnswerId = ans.Id,
Created = ans.Created,
NextReminderDate = ans.NextReminderDate,
QuestionnaireSentOutQuestionnaireTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
QuestionnaireSentOutTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
User = new SimpleUserWithOrganizationId
{
OrganizationId = ans.User.OrganizationId,
UserId = ans.UserId,
Email = ans.User.Email,
Name = ans.User.Name,
}
})
.ToListAsync();
//Sending reminders
foreach (var answer in answers)
{
try
{
answer.Answer.NextReminderDate = DateTime.UtcNow.AddDays(7);
QuestionnaireReminder(
answer.User.Name,
answer.User.Email,
answer.QuestionnaireSentOutQuestionnaireTitle,
answer.QuestionnaireSentOutTitle,
answer.Created.ToString(),
answer.AnswerId,
appServer,
answer.User.OrganizationId, _configuration);
}
catch (Exception)
{
await ErrorHandleService.HandleError("Cound not send questionnaire reminder",
"Cound not send questionnaire reminder email to: " + answer.User.Email,
null, null, null, _configuration, sendDeveloperMails, currentVersion, whoAmIName);
}
}
await context.SaveChangesAsync();
简短的回答是肯定的,也不是。
读取数据时,像更新后的代码一样投射到 DTO/ViewModels。 这减少了结果数据的大小,以仅从所需的相关实体中提取信息。
更新数据时,加载被跟踪的实体。
答案的“否”部分是,在您的第一个查询中,删除Include
语句,它们是不需要的,应该删除以避免通过网络发送额外数据,从而可能形成一对多的笛卡尔积通过 JOIN 的表。
var answers = await context.QuestionnaireUserAnswers
.Where(a => a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedDraft &&
a.QuestionnaireSentOut.Questionnaire.Status != QuestionnaireStatus.ArchivedPublished)
.Where(a => a.QuestionnaireSentOut.QuestionnaireType.Equals(QuestionnaireSentType.Normal))
.Where(a => a.Status.Equals(QuestionAnsweredStatus.Draft))
.Where(a => a.NextReminderDate < DateTime.UtcNow)
.ToListAsync();
执行Where
条件不需要Include
。 EF 会将这些结果很好地处理到查询中。 只有在您想要在使用结果集的过程中访问那些相关实体的情况下才需要它,否则它们将被延迟加载,可能会有多个数据库往返排队。
前提是“某些呼叫”不会尝试访问答案中的相关实体。 如果您的逻辑确实需要来自 QuestionaireSentOut 或 Questionaire 等的一些数据,那么您可以将投影与实体引用混合使用:
.Select(ans => new
{
QuestionnaireSentOutQuestionnaireTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
QuestionnaireSentOutTitle = ans.QuestionnaireSentOut.Questionnaire.Title,
Answer = ans,
User = ans.User.Select(u => new
{
OrganizationId = u.OrganizationId,
UserId = u.UserId,
Email = u.Email,
Name = u.Name,
}
})
与您的投影类似,您可以在结果查询中返回答案实体以进行处理。 在循环内部,如果逻辑需要来自用户或问卷的详细信息,它会从生成的投影中获取它,然后当您 go 更新答案本身时,通过 .Answer 引用更新它,这将是被跟踪的 EF 实体。 这种方法不应该用于填充 ViewModel 以发送回 View 等的读取类型操作。建议不要混合视图模型和实体,以避免序列化等方面的错误和性能问题。 在需要此类详细信息的地方,我将使用匿名类型来阻止传递带有实体引用的对象。
您应该能够使用免费库Entity Framework Plus使用批量更新功能来执行此操作。 创建该功能是为了适用于多条记录,但它同样适用于一条记录。
它构建了一个UPDATE... SET... WHERE...
查询,类似于您自己编写的查询,无需将任何数据从数据库加载到 DbContext。
示例代码:
using Z.EntityFramework.Plus;
var id = .... ;
var newDate = DateTime.UtcNow.AddDays(7);
context.QuestionnaireUserAnswers
.Where(a => a.Id == id)
.Update(a => new QuestionnaireUserAnswer() { NextReminderDate = newDate });
或者对于Id
值的集合:
using Z.EntityFramework.Plus;
var ids = new[] { ... , ... , ... };
var newDate = DateTime.UtcNow.AddDays(7);
context.QuestionnaireUserAnswers
.Where(a => ids.Contains(a.Id))
.Update(a => new QuestionnaireUserAnswer() { NextReminderDate = newDate });
注意:我相信这不会更新任何已加载到 DbContext 中的QuestionnaireUserAnswer
对象。 这在 ASP .NET 核心(或 API)中可能不是什么大问题,其中上下文是短暂的,但如果您使用上下文较长时间,则可能是个问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.