子集合未在EntityFrameworkCore中更新

时间:2016-09-03 14:10:41

标签: c# .net win-universal-app uwp entity-framework-core

考虑以下具有嵌套一对多关系的简化模型:

public class Report
{
    public Guid ReportId { get; set; }
    public string Name { get; set; }
    public List<Schedule> Schedules { get; set; }
}

public class Schedule
{
    public Guid ScheduleId { get; set; }
    public string Title { get; set; }
    public List<Description> Descriptions { get; set;}

    // FK
    public Guid ReportId { get; set; }
    public Report Report { get; set; }
}

public class Description
{
    public Guid DescriptionId { get; set; }
    public string Content { get; set; }

    // FK
    public Guid ScheduleId { get; set; }
    public Schedule Schedule { get; set; }
}

public class DataContext : DbContext
{
    public DbSet<Report> Reports { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Filename=mydbname.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Report>()
            .HasMany(report => report.Schedules)
            .WithOne(schedule => schedule.Report);
        modelBuilder.Entity<Schedule>()
            .HasOne(schedule => schedule.Report)
            .WithMany(report => report.Schedules)
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<Description>()
            .HasOne(description => description.Schedule)
            .WithMany(schedule => schedule.Descriptions)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

按预期添加作品,每个项目都插入每个表格中:

Description description = new Description();
description.DescriptionId = Guid.NewGuid();
description.Content = "my content";

Schedule schedule = new Schedule();
schedule.ScheduleId = Guid.NewGuid();
schedule.Title = "my schedule";
schedule.Descriptions = new List<Description>();
schedule.Descriptions.Add(description);

Report report = new Report;
report.ReportId = Guid.NewGuid();
report.Name = "my report";
report.Schedules = new List<Schedule>();
report.Schedules.Add(schedule);

using(var db = new DataContext())
{
    db.Reports.Add(report);
    db.SaveChanges();
}

删除实体也按预期工作,所有项目都在每个表中删除:

using(var db = new DataContext())
{
    db.Reports.Remove(report);
    db.SaveChanges();
}

但是,更新实体仅在我更改现有项目的内容时才有效,而不是在我在集合中添加新项目时。例如:

// this works
report.Name = "updated report";
report.Schedules.ElementAt(0).Descriptions.ElementAt(0).Content = "updated content";
using(var db = new DataContext())
{
    db.Reports.Update(report);
    db.SaveChanges();
}

// this throws DbUpdateConcurrencyException because no rows are affected.
Description newDescription = new Description();
newDescription.DescriptionId = Guid.NewGuid();
newDescription.Content = "new content";

Schedule newSchedule = new Schedule();
newSchedule.ScheduleId = Guid.NewGuid();
newSchedule.Title = "new schedule";
newSchedule.Descriptions = new List<Description>();
newSchedule.Descriptions.Add(newDescription);

report.Schedules.Add(newSchedule);
using(var db = new DataContext())
{
    db.Reports.Update(report);
    db.SaveChanges();
}

如何正确地将项目添加到集合中并在之后更新?我的桌子设计是否有意义?这就是我获取Report对象的方法:

List<Report> Reports;
using (var db = new DataContext())
    Reports = db.Reports
                    .Include(report => report.Schedules)
                        .ThenInclude(schedule => schedule.Descriptions)
                    .ToList()

修改

正如Sunteen - MSFT post所暗示的那样,我的代码似乎没有任何问题。我发现在我的情况下导致问题的原因是,我不应该故意生成每个实体的主键ID(Guid.NewGuid())。事实证明,ID是自动生成的,问题已解决。谢谢Sunteen - MSFT。

2 个答案:

答案 0 :(得分:1)

我无法重现您的问题。我写的唯一代码是获取您未在代码段中显示的report对象。请确保表中存在您尝试更新的report对象。我刚刚从Report表中获得了第一条记录,然后更新,代码如下,

using (var db = new DataContext())
{
    List<Report> Reports;
    Reports = db.Reports.Include(report1 => report1.Schedules)
                        .ThenInclude(schedule => schedule.Descriptions)
                    .ToList();
    var report = Reports[0];

    Description newDescription = new Description();
    newDescription.DescriptionId = Guid.NewGuid();
    newDescription.Content = "new content";

    Schedule newSchedule = new Schedule();
    newSchedule.ScheduleId = Guid.NewGuid();
    newSchedule.Title = "new schedule";
    newSchedule.Descriptions = new List<Description>();
    newSchedule.Descriptions.Add(newDescription);
    report.Schedules.Add(newSchedule);
    db.Reports.Update(report);
    db.SaveChanges();
}

我使用您的代码创建了一个演示程序并且运行良好,我将demo上传到github,您可以下载进行测试并与您的项目进行比较以查找差异。如果通过比较差异无法解决问题,您可以尝试在此演示中重现您的问题,让我们再次帮助解决。

答案 1 :(得分:0)

我知道这是一个较旧的问题,但是(如建议的答案所建议),您应该使用相同的DbContext来查询,修改/创建然后保存更改。

查询并获取实体,然后处理上下文时,您所查询的实体现在已断开连接,因为您丢失了作为原始DbContext一部分的变更跟踪器。如果您修改了那个断开连接的实体,然后创建了一个新的DbSet<T>.Update(T entity),则需要先将其重新附加到新的上下文中,然后才能保存更改,否则会遇到错误。

EF(尤其是EF Core)足够聪明,可以在您调用db.Reports.Update(report)(例如DbContext)并将其标记为已跟踪时,以递归方式查找导航属性的变化。

故事道德:

对整个相关数据库操作集使用一个DbSet<T>.Add(T entity),从查询开始以检索现有实体(如果正在更新),或者如果添加新记录则调用DbContext.SaveChanges() 。然后,在处理上下文之前,先调用{{1}}。