【问题标题】:Entity Framework Core 2.0 Many to Many Inserts before primary key is generated在生成主键之前,Entity Framework Core 2.0 多对多插入
【发布时间】:2018-06-15 11:23:06
【问题描述】:

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

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 });
    }
}

我开始遇到问题的地方是我没有使用实体框架生成 Id,主键是 SQL Server 2012 中的一个身份,一旦插入完成,我就会得到它,而不是使用 GUID(它我读过几乎所有在 DBA 世界中都非常不受欢迎的地方)。

所以最终发生的事情是我要么尝试插入,它尝试插入多对多关系,联结表中的 changeId 为空(因为它尚未生成),或者当我尝试我所拥有的下面在一个 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);
}

我在尝试更新时遇到此错误。

我知道,如果我让实体框架生成 guid 而不是让数据库生成身份 int,我会更轻松地使用它,但这个项目的要求是不使用 Guid。

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

编辑:如果有帮助,这是我与邮递员一起使用的 http 帖子。

{
    "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
        }
        ]
}

【问题讨论】:

  • 任何答案对您有帮助吗?也许您可以提供一些反馈?
  • 非常抱歉,是的,我可以为我的问题添加一些反馈,我是如何解决的,以及这些答案如何帮助我解决问题。

标签: c# sql-server-2012 entity-framework-core asp.net-core-2.0 asp.net-core-webapi


【解决方案1】:

好的,这是否是一个优雅的解决方案还有待商榷,但在执行如下初始插入后,我能够将实体状态与 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);

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

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

【讨论】:

  • 创建一个新对象而不是重新使用最初创建的对象是否有特殊原因?您是否尝试过我第二次编辑的代码?
  • 现有实体不再被跟踪,如果没有收到我发布此问题的错误,我将无法再次对其进行编辑。此外,我的 StartUp.cs 中有明确的映射代码,因此映射不需要任何更改,因为它已经在工作了。
  • 您收到错误是因为: 1 - 您仍在跟踪实体; 2 - 您创建一个具有相同 id 的新实体并尝试跟踪它。通过将您的更改映射到您的原始实体,您应该获得所需的行为。不要使用 Mapper.Map(obj) - 使用 Mapper.Map(viewModel, yourEntity) 代替。如果没有特定的阅读条件,则应避免分离实体。
  • 我相信您的错误来自于不理解 changeEntity = Mapper.Map&lt;Change&gt;(changetoReturn); 会将您跟踪的实体的引用替换为对新的未跟踪对象的引用。
  • 啊啊啊啊。是的,那部分我绝对不知道。好的,我想如果实体属性和模型属性不完全相同并且需要自定义映射,那么您的解决方案是否有效?
【解决方案2】:

通过joint table 添加新实体...这样,实体在joint table 及其各自的表格中都被跟踪

【讨论】:

    【解决方案3】:

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

    你得到它是因为你是:

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

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

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

    如果 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

    【讨论】:

    • 嘿桑德。我并不是说 guids 与 int 是我的问题的根源,我只是说能够在插入之前分配 guid 意味着我可以插入主记录,而且一举就有很多记录反对插入主记录,然后进行更新以获取其多对多记录。但是,您将我引向分离和添加实体状态的概念导致我找到了我的解决方案以允许我做我想做的事情,所以我将相应地投票,并发布我如何解决它的答案。
    猜你喜欢
    • 1970-01-01
    • 2022-06-23
    • 2018-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 2018-09-25
    • 2018-06-19
    相关资源
    最近更新 更多