当我们在 Entity Framework Core 中拥有主键时,我们是不是应该始终使用 .Find() 而不是 .FirstOrDefault() ?

Posted

技术标签:

【中文标题】当我们在 Entity Framework Core 中拥有主键时,我们是不是应该始终使用 .Find() 而不是 .FirstOrDefault() ?【英文标题】:Should we always use .Find() rather than .FirstOrDefault() when we have the primary key in Entity Framework Core?当我们在 Entity Framework Core 中拥有主键时,我们是否应该始终使用 .Find() 而不是 .FirstOrDefault() ? 【发布时间】:2020-03-05 12:29:24 【问题描述】:

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details?view=aspnetcore-3.0

在 Microsoft 文档示例中, .FirstOrDefaultAsync()用于DetailDeleteGET.FindAsync() 用于DeleteConfirmed。 我想知道这是为什么?

【问题讨论】:

Performance of Find() vs. FirstOrDefault()的可能重复 但我的问题是为什么在文档示例中 .FirstOrDefaultAsync() 用于 Detail 而 Delete GET .FindAsync() 用于 DeleteConfirmed @Areks EF Core 和 LINQ to Objects 没有任何关系。 【参考方案1】:

根据reference source DbSet.Find,如果在 DbContext 中已经获取了具有相同 keyValues 的对象,则不会访问数据库:

///     Finds an entity with the given primary key values.
///     If an entity with the given primary key values exists in the context, then it is
///     returned immediately without making a request to the store. 
public abstract object Find(params object[] keyValues);

FirstOrDefault,类似的函数会调用IQueryable.GetEnumerator(),它会向IQueryable询问ProviderIQueryable.GetProvider()的接口,然后调用IQueryProvider.Execute(Expression)来获取Expression定义的数据。 这将始终访问数据库。

假设你有学校和他们的学生,一个简单的一对多关系。您还有一个更改学生数据的程序。

Student ChangeAddress(dbContext, int studentId, Address address);
Student ChangeSchool(dbContext, int studentId, int SchoolId);

您在程序中有这个,因为这些程序会检查更改的有效性,可能伊顿学生不允许住在牛津校区,并且可能有些学校只允许特定年龄的学生。

您有以下代码使用这些过程:

void ChangeStudent(int studentId, Address address, int schoolId)

    using (var dbContext = new SchoolDbContext())
    
        ChangeAddress(dbContext, studentId, address);
        ChangeSchool(dbContext, studentId, schoolId);
        dbContext.SaveChanges();
    

如果 Change... 函数将使用 FirstOrDefault(),那么您将丢失其他过程所做的更改。

但是,有时您希望能够重新获取数据库数据,例如,因为其他人可能已经更改了数据,或者您刚刚所做的某些更改无效

Student student = dbContext.Students.Find(10);
// let user change student attributes
...

bool changesAccepted = AskIfChangesOk();
if (!changesAccepted)
    // Refetch the student.
     // can't use Find, because that would give the changed Student
     student = dbContext.Students.Where(s => s.Id == 10).FirstOrDefault();


// now use the refetched Student with the original data

【讨论】:

嗨。谢谢你回答我的问题。我仍然有点想知道这是否意味着如果我使用FirstOrDefault(),它将覆盖其他程序之前所做的所有更改,这些更改在FirstOrDefault() 之前发送到数据库?在多个用户同时进行更改的情况下,何时是使用Find() 以使其仍能获得性能并确保 CRUD 仍按预期执行的最佳时间? 或者换句话说,如果我使用.Find()而不是.FirstOrDefault(),会有什么不好的副作用吗? 如果您使用 Find,如果您的 dbContext 对象已经获取它,您可能会获得数据库值,也可能不会。由于这种不确定性,我很少使用 Find。如果您真的想确保获得数据库值,请使用 FirstOrDefault。【参考方案2】:

我认为是因为删除时不知道该项目是否存在,所以需要默认以防找不到。

在执行DeleteConfirmed时,您知道带有id的项目存在并且可以使用Find

【讨论】:

以上是关于当我们在 Entity Framework Core 中拥有主键时,我们是不是应该始终使用 .Find() 而不是 .FirstOrDefault() ?的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 抛出意外异常,工作量大

Entity Framework 学习系列 - 认识理解Entity Framework

Entity Framework Many to Many Relation Mapping(Entity Framework多对多关系映射)

Entity Framework技巧系列之十二 - Tip 46 - 50

Entity Framework技巧系列之十一 - Tip 42 - 45

Code First Entity Framework 6化被动为主动之explicit loading模式实战分析( 附源码)