Entity Framework Core 2.0 在生成主键之前进行多对多插入

Entity Framework Core 2.0 Many to Many Inserts before primary key is generated

我正在尝试创建一个与其他实体具有多对多关系的实体对象。关系表示如下。

public class Change {
    // Change Form Fields
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ChangeId { get; set; }
    public string ChangeTitle { get; set; }
    public string ChangeType { get; set; }
    public DateTime DateSubmitted { get; set; }
    public DateTime TargetDate { get; set; }

    //Many to Many Collections
    public virtual ICollection<Change_CriticalBankingApp> Change_CriticalBankingApps { get; set; } = new List<Change_CriticalBankingApp>();
    public virtual ICollection<Change_ImpactedBusiness> Change_ImpactedBusinesses { get; set; } = new List<Change_ImpactedBusiness>();
    public virtual ICollection<Change_ImpactedService> Change_ImpactedServices { get; set; } = new List<Change_ImpactedService>();
    public virtual ICollection<Change_TestStage> Change_TestStages { get; set; } = new List<Change_TestStage>();
    public virtual ICollection<Change_TypeOfChange> Change_TypeOfChanges { get; set; } = new List<Change_TypeOfChange>();

而DbContext设置如下

public class ChangeContext : DbContext {
    public ChangeContext(DbContextOptions<ChangeContext> options) : base(options) {
        Database.Migrate();
    }

    public DbSet<Change> Change { get; set; }     
    public DbSet<TestStage> TestStage { get; set; }
    public DbSet<TypeOfChange> TypeOfChange { get; set; }
    public DbSet<CriticalBankingApp> CriticalBankingApp { get; set; }
    public DbSet<ImpactedBusiness> ImpactedBusiness { get; set; }
    public DbSet<ImpactedService> ImpactedService { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.Entity<Change_CriticalBankingApp>().HasKey(t => new { t.ChangeId, t.CriticalBankingAppId });
        modelBuilder.Entity<Change_ImpactedBusiness>().HasKey(t => new { t.ChangeId, t.ImpactedBusinessId });
        modelBuilder.Entity<Change_ImpactedService>().HasKey(t => new { t.ChangeId, t.ImpactedServiceId });
        modelBuilder.Entity<Change_TestStage>().HasKey(t => new { t.ChangeId, t.TestStageId });
        modelBuilder.Entity<Change_TypeOfChange>().HasKey(t => new { t.ChangeId, t.TypeOfChangeId });
    }
}

我开始 运行 遇到问题的地方是我没有使用 Entity Framework 生成 Id,主键是 SQL Server 2012 中的一个身份,我得到了一次插入已完成,而不是使用 GUID(我几乎到处都读到它在 DBA 世界中是超级不受欢迎的)。

所以最终发生的事情是我要么尝试执行插入,它会尝试插入多对多关系,其中连接 table 中的 changeId 为 null(因为它尚未生成)或当我尝试使用下面的方法在一个 post 方法中进行插入和更新时。它出错是因为 ChangeId 键值已经被跟踪。以下是我正在尝试的内容。

控制器方法

    public IActionResult CreateChange([FromBody] ChangeModel change) {
        if (change == null) {
            return BadRequest();
        }

        //Remove many to many from Change to insert without them (as this can't be done until primary key is generated.
        List<Change_CriticalBankingAppModel> criticalApps = new List<Change_CriticalBankingAppModel>();
        criticalApps.AddRange(change.Change_CriticalBankingApps);
        List<Change_ImpactedBusinessModel> impactedBusinesses = new List<Change_ImpactedBusinessModel>();
        impactedBusinesses.AddRange(change.Change_ImpactedBusinesses);
        List<Change_ImpactedServiceModel> impactedServices = new List<Change_ImpactedServiceModel>();
        impactedServices.AddRange(change.Change_ImpactedServices);
        List<Change_TestStageModel> testStages = new List<Change_TestStageModel>();
        testStages.AddRange(change.Change_TestStages);
        List<Change_TypeOfChangeModel> changeTypes = new List<Change_TypeOfChangeModel>();
        changeTypes.AddRange(change.Change_TypeOfChanges);

        change.Change_CriticalBankingApps.Clear();
        change.Change_ImpactedBusinesses.Clear();
        change.Change_ImpactedServices.Clear();
        change.Change_TestStages.Clear();
        change.Change_TypeOfChanges.Clear();

        //Map Change model to change entity for inserting
        var changeEntity = Mapper.Map<Change>(change);
        _changeRepository.AddChange(changeEntity);

        if (!_changeRepository.Save()) {
            throw new Exception("Creating change failed on save.");
        }

        var changetoReturn = Mapper.Map<ChangeModel>(changeEntity);

        //Iterate through Many to many Lists to add generated changeId
        foreach (var criticalApp in criticalApps) {
            criticalApp.ChangeId = changetoReturn.ChangeId;
        }
        foreach (var impactedBusiness in impactedBusinesses) {
            impactedBusiness.ChangeId = changetoReturn.ChangeId;
        }
        foreach (var impactedService in impactedServices) {
            impactedService.ChangeId = changetoReturn.ChangeId;
        }
        foreach (var testStage in testStages) {
            testStage.ChangeId = changetoReturn.ChangeId;
        }
        foreach (var changeType in changeTypes) {
            changeType.ChangeId = changetoReturn.ChangeId;
        }

        //Add many to many lists back to change to update
        changetoReturn.Change_CriticalBankingApps = criticalApps;
        changetoReturn.Change_ImpactedBusinesses = impactedBusinesses;
        changetoReturn.Change_ImpactedServices = impactedServices;
        changetoReturn.Change_TestStages = testStages;
        changetoReturn.Change_TypeOfChanges = changeTypes;

        changeEntity = Mapper.Map<Change>(changetoReturn);

        _changeRepository.UpdateChange(changeEntity);
        if (!_changeRepository.Save()) {
            throw new Exception("Updating change with many to many relationships failed on save.");
        }

        changetoReturn = Mapper.Map<ChangeModel>(changeEntity);

        return CreatedAtRoute("GetChange",
            new { changeId = changetoReturn.ChangeId },
            changetoReturn);
    }

相关存储库方法

public Change GetChange(int changeId) {
    return _context.Change.FirstOrDefault(c => c.ChangeId == changeId);
}
public void AddChange(Change change) {
    _context.Change.Add(change);
}
public void UpdateChange(Change change) {
    _context.Change.Update(change);
}
public bool ChangeExists(int changeId) {
    return _context.Change.Any(c => c.ChangeId == changeId);
}

我在尝试更新时遇到了这个错误。

我明白,如果我让 entity framework 生成 guid 而不是让数据库生成 identity int,我会更容易地使用它,但这个项目的要求是不使用Guid 的。

任何有关如何成功处理此问题的帮助将不胜感激。

编辑:如果有帮助,这里是我与 postman 一起使用的 http post。

{
    "changeTitle": "Test",
    "changeType": "Test",
    "dateSubmitted": "02/12/2018",
    "targetDate": "02/12/2018",
    "change_CriticalBankingApps": [
        {
            "criticalBankingAppId" : 1,
            "description" : "Very critical"
        },
        {
            "criticalBankingAppId" : 2,
            "description" : "Moderately critical"
        }
        ],
    "change_impactedBusinesses": [
        {
            "ImpactedBusinessId" : 1
        },
        {
            "ImpactedBusinessId" : 2
        }
        ]
}

您遇到的错误与 guid vs db 身份无关。

你得到它是因为你是:

  1. 正在从数据库中获取实体
  2. 从您的控制器中创建新实体(未跟踪)(映射器执行此操作)
  3. 尝试更新未被entity framework
  4. 跟踪的实体

更新将尝试将实体添加到 EF 存储库,但会失败,因为它已经包含具有给定 ID 的实体。

如果您打算对实体进行更改,则需要确保 entity framework 在调用更新方法之前跟踪实体。

如果 EF 不跟踪您的实体,它不知道哪些字段已更新(如果有)。


编辑:

如果您想消除错误,您可以分离您的原始实体。确保在将 changetoReturn 映射回您的 changeEntity 之前执行此操作。

dbContext.Entry(entity).State = EntityState.Detached;

但由于不会跟踪您的新实体,我认为不会更新任何内容(EF 不知道更改了什么)。


编辑 2:

另请查看此内容以将您的更改恢复到原始实体中。

改变这个:

changeEntity = Mapper.Map<Change>(changetoReturn);

进入这个:

Mapper.Map(changetoReturn, changeEntity);

Using Automapper to update an existing Entity POCO

通过 joint table 添加新实体...这样,实体在 joint table 及其各自的表

中都会被跟踪

好的,这是否是一个优雅的解决方案还有待商榷,但在执行初始插入后,我能够将实体状态与 changeEntity 分离,如下所示

_changeRepository.AddChange(changeEntity);
_changecontext.Entry(changeEntity).State = EntityState.Detached;

然后在将所有多对多列表重新附加回 changeToReturn 之后,我创建了一个新的 Change 实体并添加了该实体状态,并对其进行了如下更新。

var newChangeEntity = Mapper.Map<Change>(changeToReturn);
_changecontext.Entry(newChangeEntity).State = EntityState.Added;
_changeRepository.UpdateChange(newChangeEntity);

然后我将这个映射返回给视图模型。

这似乎很老套,也许通过对 entity framework 更深入的理解,我会发现一个更好的方法来解决这个问题,但目前可行。