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 身份无关。
你得到它是因为你是:
- 正在从数据库中获取实体
- 从您的控制器中创建新实体(未跟踪)(映射器执行此操作)
- 尝试更新未被entity framework
跟踪的实体
更新将尝试将实体添加到 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 更深入的理解,我会发现一个更好的方法来解决这个问题,但目前可行。
我正在尝试创建一个与其他实体具有多对多关系的实体对象。关系表示如下。
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 身份无关。
你得到它是因为你是:
- 正在从数据库中获取实体
- 从您的控制器中创建新实体(未跟踪)(映射器执行此操作)
- 尝试更新未被entity framework 跟踪的实体
更新将尝试将实体添加到 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 更深入的理解,我会发现一个更好的方法来解决这个问题,但目前可行。