在 Entity Framework Core 中第二次调用 SaveChanges() 期间,将子实体添加到父级集合失败

问题描述 投票:0回答:1

我在 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
);
entity-framework-core
1个回答
0
投票

默认情况下,EF Core 将主键视为从数据库生成。在您的示例代码中,PK 是从客户端生成的。根本原因是你忘了配置这个。

在您的情况下,请使用

[DatabaseGenerated(DatabaseGeneratedOption.None)]
代替
Id
Blog
中的
Post
,或者在 DbContext 中使用相应的 Fluent API 配置。

© www.soinside.com 2019 - 2024. All rights reserved.