Mus*_*gdy 5 c# change-tracking entity-framework-core
我有一个非常基本的设置来测试和理解为什么EF Core 6在将新项目添加到已保存实体的多端时默认不保存相关实体。
entry手动设置状态吗?我还遵循了Microsoft 文档中提供的示例,它给了我相同的结果 ( DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s);)
using Microsoft.EntityFrameworkCore;
var db = new AuthorsDbContext();
db.Database.EnsureCreated();
var author = new Author();
author.Id = Guid.NewGuid();
db.Authors.Add(author);
db.SaveChanges();
// Not working
// var author1 = await db.Authors.FindAsync(new object?[] { author.Id });
// author1.Posts.Add(new Post() { Id = Guid.NewGuid(), Title = "test" });
// db.SaveChanges(); //-> DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
// Not working
// var author1 = await db.Authors.Include(a => a.Posts).FirstAsync(a => a.Id == author.Id);
// author1.Posts.Add(new Post() { Id = Guid.NewGuid(), Title = "test" });
// db.SaveChanges(); //-> DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
// Not working
// var author1 = await db.Authors.AsTracking().FirstAsync(a => a.Id == author.Id);
// author1.Posts.Add(new Post() { Id = Guid.NewGuid(), Title = "test" });
// db.SaveChanges(); //-> DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
// Not working
// var author1 = await db.Authors.Include(a=>a.Posts).AsTracking().FirstAsync(a => a.Id == author.Id);
// author1.Posts.Add(new Post() { Id = Guid.NewGuid(), Title = "test" });
// db.SaveChanges(); //-> DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
// this is the only way it works
// var author1 = await db.Authors.FindAsync(new object?[] { author.Id });
// var post = new Post() { Id = Guid.NewGuid(), Title = "test" };
// author1.Posts.Add(post);
// db.Entry(post).State = EntityState.Added;
// db.SaveChanges();
public class Author
{
public Guid Id { get; set; }
public List<Post> Posts { get; set; } = new();
}
public class Post
{
public Guid Id { get; set; }
public string Title { get; set; }
}
public class AuthorsDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("Data Source=127.0.0.1;Initial Catalog=test-db-091;User Id=root;Password=DeV12345");
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Author>().HasKey(a => a.Id);
builder.Entity<Post>().HasKey(a => a.Id);
builder.Entity<Author>().HasMany(a => a.Posts).WithOne(a => a.Author).HasForeignKey(a => a.AuthorId);
builder.Entity<Post>().HasOne(a => a.Author).WithMany().HasForeignKey(a => a.AuthorId);
}
}
Run Code Online (Sandbox Code Playgroud)
Iva*_*oev 10
首先,发布的模型与流畅的配置不匹配,所以让我们纠正它。根据流畅的配置,Post具有引用导航属性和显式 FK 属性Author:
public class Post
{
//...
public Author Author { get; set; }
public Guid AuthorId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
其次,这里存在关系配置错误
builder.Entity<Author>().HasMany(a => a.Posts).WithOne(a => a.Author).HasForeignKey(a => a.AuthorId);
builder.Entity<Post>().HasOne(a => a.Author).WithMany().HasForeignKey(a => a.AuthorId);
Run Code Online (Sandbox Code Playgroud)
您正在两次配置一个相同的关系,这通常是潜在问题的根源,因为在这种情况下,第二个配置(覆盖第一个)缺少 中的集合导航属性WithMany,这反过来会导致 2 个关系和 2 个关系FK。所以删除它并只留下第一个(正确的)
builder.Entity<Author>()
.HasMany(a => a.Posts)
.WithOne(a => a.Author)
.HasForeignKey(a => a.AuthorId);
Run Code Online (Sandbox Code Playgroud)
通常,每个关系始终使用一种流畅的配置,并将导航属性传递给Has/With调用(当它们存在时)。
现在讨论主要主题 - 变更跟踪。当您直接调用更改跟踪 API(Entry.State或DbContext/ DbSet Add、Remove、Attach)时,EF 仅使用您告诉它的任何内容以及所提供实体的状态。然而,当某个实体没有被跟踪,并且 EF 需要确定其状态(由其他几个地方ChangeTracker.DetectChanges调用SaveChanges)时,事情就会变得棘手,因为它不知道该实体是否是新的(因此应该添加)或现有且需要更新。
在这里,他们使用了一种适用于大多数情况的简单方法。如果PK是生成 值或者设置了 PK(其值与 CLR 默认值不同),则实体被视为现有的,否则被视为新的。
这就是这里问题的原因。Guid默认情况下,PK 被认为是自动生成的 ( ) ValueGeneratedOnAdd,并且由于您提供显式值,因此实例Post被认为是现有的,并且 EF 尝试更新(而不是插入),这当然会失败。
基本上有两种方法可以解决这个问题。第一种是使用默认的 PK 属性配置,并且不为新实体分配 PK 值(当实体进入更改跟踪器时,EF 会生成并分配一个值,但它也会知道它是自动生成的,并且实体必须是创建),即在所有“非工作”示例中,Id = Guid.NewGuid(),从new Post() { ... }语句中删除,它们应该可以正常工作。
第二种方法是(如果您想使用显式GuidPK)保留用法不变,但更改流畅配置以告诉 EF 这些属性不是自动生成的,即
builder.Entity<Post>().Property(e => e.Id).ValueGeneratedNever();
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3846 次 |
| 最近记录: |