我在 EF Core 中有父/子关系,我想添加一个父实体,保存更改,然后向该父实体添加一个子实体并再次保存更改(注意:这是运行在一个交易,需要中间
SaveChanges
,但这里我只提取了显示问题所需的代码)。
如果我在第一次
SaveChanges
调用之前向新父级添加一个子级,则效果很好。但是,如果我在第一个 SaveChanges
调用之后添加它,则会收到错误:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException:数据库操作预计影响1行,但实际影响0行;自加载实体以来,数据可能已被修改或删除。有关理解和处理乐观并发异常的信息,请参阅 https://go.microsoft.com/fwlink/?LinkId=527962。
这在实体框架(非核心)中运行良好,但现在在 EF Core 中失败。
如果我手动将子实体添加到适当的数据库集(而不是子实体的集合),它就可以正常工作。
我能做些什么来解决这个问题吗?通过以不同的方式配置上下文?
这是模拟该问题的完整代码:
internal class Program
{
static void Main(string[] args)
{
// Empty everything from previous runs
{
using var db = new BloggingContext();
var posts = db.Posts.ToList();
var blogs = db.Blogs.ToList();
foreach (var post in posts)
db.Remove(post);
foreach (var blog in blogs)
db.Remove(blog);
db.SaveChanges();
}
try
{
using var db = new BloggingContext();
var blog = new Blog
{
Id = 1,
Name = "Blog 1",
SiteUri = "http://blogs.msdn.com/adonet",
Posts = new List<Post>()
};
db.Add(blog);
var post1 = new Post
{
Id = 11,
BlogId = 1,
Title = "Hello World",
Content = "I wrote an app using EF Core!"
};
blog.Posts.Add(post1);
db.SaveChanges();
var post2 = new Post
{
Id = 12,
BlogId = 1,
Title = "Hello World 2",
Content = "I wrote an app using EF Core 2!"
};
blog.Posts.Add(post2);
// db.Posts.Add(post2); --> With this line it works
db.SaveChanges();
Console.WriteLine("Done");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string SiteUri { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime PublishedOn { get; set; }
public bool Archived { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public BloggingContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer($"Server=.;Database=DbTests;Trusted_Connection=True;Encrypt=False");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.HasPrincipalKey(e => e.Id);
}
}
以及数据库模型的 SQL:
CREATE TABLE [Blogs]
(
[Id] int NOT NULL,
[Name] nvarchar(max) NULL,
[SiteUri] nvarchar(max) NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY ([Id])
);
CREATE TABLE [Posts]
(
[Id] int NOT NULL,
[Title] nvarchar(max) NULL,
[Content] nvarchar(max) NULL,
[PublishedOn] datetime2 NOT NULL,
[Archived] bit NOT NULL,
[BlogId] int NOT NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]) ON DELETE CASCADE
);
默认情况下,EF Core 将主键视为从数据库生成。在您的示例代码中,PK 是从客户端生成的。根本原因是你忘了配置这个。
在您的情况下,请使用
[DatabaseGenerated(DatabaseGeneratedOption.None)]
代替 Id
和 Blog
中的 Post
,或者在 DbContext 中使用相应的 Fluent API 配置。