Closed tzarot closed 1 year ago
@tzarot When EF is configured to generate key values for an entity type, then any instance of that entity type is considered to already exist in the database when it already has a key value set before it is tracked. If you don't want to use auto-generated keys, then configure the entity type as such:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().Property(e => e.Id).ValueGeneratedNever();
}
@ajcvickers, Thank you for your answer. However I believe what you say, that was also initially my thought, is not totally accurate, since then I cannot explain why if I just create a simple entity i.e. Customer, with no detail entities whatsoever, and then again manually set the Id in the constructor to a Guid.NewGuid(), the process works fine without any exceptions!. Of course it works if I do not assign the Id also. (Attached this simpler solution also.)
So, to recap and possibly explain better: The entry.State of the newly added Customer (No detail entities), just before db.SaveChanges() is always Added, no matter if I assign the Id manually or not.
In the case as described, in my original question, the added Order that is a detail entity of Customer (when this Customer is fetched from database), when I have not assigned the Order.Id manually has a state of Added, where if I manually assign the Id in the constructor, then it has a state of Modified, hence the exception!. Then again if I also have added the Customer (a new Customer, not in database), then as mentioned also in my original article the entry.State of the Order is always Added, hence the process works.
public class Customer
{
public Guid Id { get; private set; } // I hust made this a Guid. In Migrations Snapshot file, it also gets .ValueGeneratedOnAdd()
public string Name { get; set; } = null!;
public Customer()
{
Id = Guid.NewGuid(); // and I still assign this manually
}
}
public class PlayDbContext:DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.;Database=PlayAcmeDatabase3;Trusted_Connection=true;TrustServerCertificate=true;");
}
}
// in Program.cs
using (var db = new PlayDbContext())
{
CreateCustomer(db);
}
void CreateCustomer( PlayDbContext db )
{
var c = new Customer()
{
Name = "John Doe"
};
db.Customers.Add(c);
db.SaveChanges();
}
@tzarot Calling DbSet.Add
or DbContext.Add
always marks the pass entity as new, even if its key is set. This is essentially the way you tell EF to insert the entity even though it has a key value already set. Using DbSet.Attach/Update
, DbContext.Attach/Update
, or having EF detect the change, like you are doing in your first example, results in a state based on the key value. See Explicitly tracking entities for more info.
When saving a master-detail (one-many) graph, where the child/detail entity has a PK of Guid, that gets assigned from within the detail entity's constructor, and the master/principal entity is fetched from the database, I am getting a DbUpdateConcurrencyException. If however, I do not fetch the Principal/Master entity from the database. but instead I create a new one and also add the child entity to it, then all work without any problems.
Am I missing something here?
Also, if I remove the initializer, Guid.NewGuid(); from the Order Id property, all work without any errors.
However I want to point-out that the Order.Id property, in the migration, is declared as .ValueGeneratedOnAdd() so reading the documentation in https://learn.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder-1.ValueGeneratedOnAdd?view=efcore-7.0&viewFallbackFrom=net-7.0, the approach taken should work.
EF Core version: Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer) Target framework: (e.g. .NET 7.0)
Attached the solution zip file. Play.EFCore.Master-Detail-Detail2.zip