7

https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details

In the Microsoft documentation example, .FirstOrDefaultAsync() is used in Detail and Delete GET; .FindAsync() is used in DeleteConfirmed. I wonder why is that?

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Peter
  • 111
  • 3
  • 6
  • Possible duplicate of [Performance of Find() vs. FirstOrDefault()](https://stackoverflow.com/questions/14032709/performance-of-find-vs-firstordefault) –  Nov 08 '19 at 21:36
  • But my question is why in the documentation example .FirstOrDefaultAsync() is used in Detail and Delete GET .FindAsync() is used in DeleteConfirmed – Peter Nov 08 '19 at 21:49
  • 1
    @Areks EF Core and LINQ to Objects have nothing to do with each other. – NetMage Nov 09 '19 at 00:08

2 Answers2

11

According to the reference source DbSet.Find will not access the database if an object with the same keyValues is already fetched in the DbContext:

///     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, and similar functions will call IQueryable.GetEnumerator(), which will ask the IQueryable for the interface to the Provider IQueryable.GetProvider() and then call IQueryProvider.Execute(Expression) to get the data defined by the Expression. This will always access the database.

Suppose you have Schools with their Students, a simple one-to-many relationship. You also have a procedures to change Student data.

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

You have this in procedures, because these procedure will check the validity of the changes, probably Eton Students are not allowed to live on Oxford Campus, and there might be Schools that only allow Students from a certain age.

You have the following code that uses these procedures:

void ChangeStudent(int studentId, Address address, int schoolId)
{
    using (var dbContext = new SchoolDbContext())
    {
        ChangeAddress(dbContext, studentId, address);
        ChangeSchool(dbContext, studentId, schoolId);
        dbContext.SaveChanges();
    }
}

If ChangeSchool would use FirstOrDefault() then you would lose changes made by ChangeAddress. So in this case you want to access the data that might already have been fetched (and changed), in other words: use Find.

However, sometimes you want to be able to re-fetch the database data, for instance, because others might have changed the data, or some changes you just made are invalid

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

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

// now use the refetched Student with the original data

    
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Hi. Thank you for answering my question. I am still kinda wondering does it mean if I use `FirstOrDefault()`, it will override all the changes that other procedures have made previous that is sent to the database before the `FirstOrDefault()`? In the case of multiple users made changes at the same time, when is the best time to use `Find()` such that it still gains performance and ensure the CRUD still perform as expected? – Peter Nov 12 '19 at 22:12
  • Or in other words, are there any bad side effect if I use `.Find()` instead of `.FirstOrDefault()`? – Peter Nov 12 '19 at 22:19
  • 2
    If you use Find, you might get the database value or you might not, if your dbContext object already fetched it. Because of this uncertainty I seldom use Find. If you really want to be sure that you get the database value, use FirstOrDefault. – Harald Coppoolse Nov 14 '19 at 08:09
1

I think it is because when doing Delete you do not know if the item exists or not, so you need default in case it is not found.

When doing DeleteConfirmed, you know the item with id exists and can use Find.

NetMage
  • 26,163
  • 3
  • 34
  • 55