c# - Entity Framework Core 2.1 无法更新具有关系的实体
问题描述
我目前在使用 EF Core 2.1 和本机客户端用来更新包含多个嵌入式对象级别的对象的 Web api 时遇到问题。我已经阅读了这两个主题:
Entity Framework Core:无法使用嵌套值对象更新实体
https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities
我从中了解到,现在更新 EF Core 2 中的对象确实不是那么明显。但我还没有设法找到有效的解决方案。在每次尝试时,我都会遇到一个异常,告诉我 EF 已经跟踪了一个“步骤”。
我的模型如下所示:
//CIApplication the root class I’m trying to update
public class CIApplication : ConfigurationItem // -> derive of BaseEntity which holds the ID and some other properties
{
//Collection of DeploymentScenario
public virtual ICollection<DeploymentScenario> DeploymentScenarios { get; set; }
//Collection of SoftwareMeteringRules
public virtual ICollection<SoftwareMeteringRule> SoftwareMeteringRules { get; set; }
}
//与Application有一对多关系的部署场景。部署方案包含两个步骤列表
public class DeploymentScenario : BaseEntity
{
//Collection of substeps
public virtual ICollection<Step> InstallSteps { get; set; }
public virtual ICollection<Step> UninstallSteps { get; set; }
//Navigation properties Parent CI
public Guid? ParentCIID { get; set; }
public virtual CIApplication ParentCI { get; set; }
}
//Step,也比较复杂,也是自引用的
public class Step : BaseEntity
{
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Guid? ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Parent InstallDeploymentScenario Navigation property
public Guid? ParentInstallDeploymentScenarioID { get; set; }
public virtual DeploymentScenario ParentInstallDeploymentScenario { get; set; }
//Parent InstallDeploymentScenario Navigation property
public Guid? ParentUninstallDeploymentScenarioID { get; set; }
public virtual DeploymentScenario ParentUninstallDeploymentScenario { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//Collection of input variables
public virtual List<ScriptVariable> InputVariables { get; set; }
//Collection of output variables
public virtual List<ScriptVariable> OutPutVariables { get; set; }
}
这是我的更新方法,我知道它很丑,它不应该在控制器中,但我每两个小时更改一次,因为如果在网上找到解决方案,我会尝试实施解决方案。所以这是来自 https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities的最后一次迭代
public async Task<IActionResult> PutCIApplication([FromRoute] Guid id, [FromBody] CIApplication cIApplication)
{
_logger.LogWarning("Updating CIApplication " + cIApplication.Name);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != cIApplication.ID)
{
return BadRequest();
}
var cIApplicationInDB = _context.CIApplications
.Include(c => c.Translations)
.Include(c => c.DeploymentScenarios).ThenInclude(d => d.InstallSteps).ThenInclude(s => s.SubSteps)
.Include(c => c.DeploymentScenarios).ThenInclude(d => d.UninstallSteps).ThenInclude(s => s.SubSteps)
.Include(c => c.SoftwareMeteringRules)
.Include(c => c.Catalogs)
.Include(c => c.Categories)
.Include(c => c.OwnerCompany)
.SingleOrDefault(c => c.ID == id);
_context.Entry(cIApplicationInDB).CurrentValues.SetValues(cIApplication);
foreach(var ds in cIApplication.DeploymentScenarios)
{
var existingDeploymentScenario = cIApplicationInDB.DeploymentScenarios.FirstOrDefault(d => d.ID == ds.ID);
if (existingDeploymentScenario == null)
{
cIApplicationInDB.DeploymentScenarios.Add(ds);
}
else
{
_context.Entry(existingDeploymentScenario).CurrentValues.SetValues(ds);
foreach(var step in existingDeploymentScenario.InstallSteps)
{
var existingStep = existingDeploymentScenario.InstallSteps.FirstOrDefault(s => s.ID == step.ID);
if (existingStep == null)
{
existingDeploymentScenario.InstallSteps.Add(step);
}
else
{
_context.Entry(existingStep).CurrentValues.SetValues(step);
}
}
}
}
foreach(var ds in cIApplicationInDB.DeploymentScenarios)
{
if(!cIApplication.DeploymentScenarios.Any(d => d.ID == ds.ID))
{
_context.Remove(ds);
}
}
//_context.Update(cIApplication);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException e)
{
if (!CIApplicationExists(id))
{
return NotFound();
}
else
{
throw;
}
}
catch(Exception e)
{
}
return Ok(cIApplication);
}
到目前为止,我遇到了这个异常:无法跟踪实体类型“Step”的实例,因为已经在跟踪另一个具有键值“{ID: e29b3c1c-2e06-4c7b-b0cd-f8f1c5ccb7b6}”的实例。
我注意到客户端之前没有进行任何“get”操作,即使是这种情况,我也已将 AsNoTracking 放在我的 get 方法中。客户端在更新之前所做的唯一操作是“_context.CIApplications.Any(e => e.ID == id);” 检查我是否应该添加新记录或更新现有记录。
几天以来我一直在与这个问题作斗争,所以如果有人能帮助我朝着正确的方向前进,我将不胜感激。非常感谢
更新 :
我在控制器中添加了以下代码:
var existingStep = existingDeploymentScenario.InstallSteps.FirstOrDefault(s => s.ID == step.ID);
entries = _context.ChangeTracker.Entries();
if (existingStep == null)
{
existingDeploymentScenario.InstallSteps.Add(step);
entries = _context.ChangeTracker.Entries();
}
条目 = _context.ChangeTracker.Entries(); 在添加包含新步骤的新部署场景后立即引发“步骤已跟踪”异常。
就在它之前,新的 deploymentScenario 和 step 不在跟踪器中,我在 DB 中检查了它们的 ID 没有重复。
我还检查了我的 Post 方法,现在它也失败了......我将它恢复为默认方法,里面没有花哨的东西:
[HttpPost]
public async Task<IActionResult> PostCIApplication([FromBody] CIApplication cIApplication)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var entries = _context.ChangeTracker.Entries();
_context.CIApplications.Add(cIApplication);
entries = _context.ChangeTracker.Entries();
await _context.SaveChangesAsync();
entries = _context.ChangeTracker.Entries();
return CreatedAtAction("GetCIApplication", new { id = cIApplication.ID }, cIApplication);
}
条目开头为空,_context.CIApplications.Add(cIApplication); 行仍在引发异常,仍然是部署场景中包含的唯一一步...
所以当我尝试在我的上下文中添加东西时,显然有问题,但现在我感觉完全迷失了。它可以帮助我如何在启动时声明我的上下文:
services.AddDbContext<MyAppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("DeployFactoryDataModel")),
ServiceLifetime.Transient
);
添加我的上下文类:
public class MyAppContext : DbContext
{
private readonly IHttpContextAccessor _contextAccessor;
public MyAppContext(DbContextOptions<MyAppContext> options, IHttpContextAccessor contextAccessor) : base(options)
{
_contextAccessor = contextAccessor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.EnableSensitiveDataLogging();
}
public DbSet<Step> Steps { get; set; }
//public DbSet<Sequence> Sequences { get; set; }
public DbSet<DeploymentScenario> DeploymentScenarios { get; set; }
public DbSet<ConfigurationItem> ConfigurationItems { get; set; }
public DbSet<CIApplication> CIApplications { get; set; }
public DbSet<SoftwareMeteringRule> SoftwareMeteringRules { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<ConfigurationItemCategory> ConfigurationItemsCategories { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Catalog> Catalogs { get; set; }
public DbSet<CIDriver> CIDrivers { get; set; }
public DbSet<DriverCompatiblityEntry> DriverCompatiblityEntries { get; set; }
public DbSet<ScriptVariable> ScriptVariables { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Step one to many with step for sub steps
modelBuilder.Entity<Step>().HasMany(s => s.SubSteps).WithOne(s => s.ParentStep).HasForeignKey(s => s.ParentStepID);
//Step one to many with step for variables
modelBuilder.Entity<Step>().HasMany(s => s.InputVariables).WithOne(s => s.ParentInputStep).HasForeignKey(s => s.ParentInputStepID);
modelBuilder.Entity<Step>().HasMany(s => s.OutPutVariables).WithOne(s => s.ParentOutputStep).HasForeignKey(s => s.ParentOutputStepID);
//Step one to many with sequence
//modelBuilder.Entity<Step>().HasOne(step => step.ParentSequence).WithMany(seq => seq.Steps).HasForeignKey(step => step.ParentSequenceID).OnDelete(DeleteBehavior.Cascade);
//DeploymentScenario One to many with install steps
modelBuilder.Entity<DeploymentScenario>().HasMany(d => d.InstallSteps).WithOne(s => s.ParentInstallDeploymentScenario).HasForeignKey(s => s.ParentInstallDeploymentScenarioID);
//DeploymentScenario One to many with uninstall steps
modelBuilder.Entity<DeploymentScenario>().HasMany(d => d.UninstallSteps).WithOne(s => s.ParentUninstallDeploymentScenario).HasForeignKey(s => s.ParentUninstallDeploymentScenarioID);
//DeploymentScenario one to one with sequences
//modelBuilder.Entity<DeploymentScenario>().HasOne(ds => ds.InstallSequence).WithOne(seq => seq.IDeploymentScenario).HasForeignKey<DeploymentScenario>(ds => ds.InstallSequenceID).OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<DeploymentScenario>().HasOne(ds => ds.UninstallSequence).WithOne(seq => seq.UDeploymentScenario).HasForeignKey<DeploymentScenario>(ds => ds.UninstallSequenceID);
//Step MUI config
modelBuilder.Entity<Step>().Ignore(s => s.Description);
modelBuilder.Entity<Step>().HasMany(s => s.Translations).WithOne().HasForeignKey(x => x.StepTranslationId);
//Sequence MUI config
//modelBuilder.Entity<Sequence>().Ignore(s => s.Description);
//modelBuilder.Entity<Sequence>().HasMany(s => s.Translations).WithOne().HasForeignKey(x => x.SequenceTranslationId);
//DeploymentScenario MUI config
modelBuilder.Entity<DeploymentScenario>().Ignore(s => s.Name);
modelBuilder.Entity<DeploymentScenario>().Ignore(s => s.Description);
modelBuilder.Entity<DeploymentScenario>().HasMany(s => s.Translations).WithOne().HasForeignKey(x => x.DeploymentScenarioTranslationId);
//CIApplication relations
//CIApplication one to many relation with Deployment Scenario
modelBuilder.Entity<CIApplication>().HasMany(ci => ci.DeploymentScenarios).WithOne(d => d.ParentCI).HasForeignKey(d => d.ParentCIID).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CIApplication>().HasMany(ci => ci.SoftwareMeteringRules).WithOne(d => d.ParentCI).HasForeignKey(d => d.ParentCIID).OnDelete(DeleteBehavior.Cascade);
// CIDriver relations
// CIAPpplication one to many relation with DriverCompatibilityEntry
modelBuilder.Entity<CIDriver>().HasMany(ci => ci.CompatibilityList).WithOne(c => c.ParentCI).HasForeignKey(c => c.ParentCIID).OnDelete(DeleteBehavior.Restrict);
//ConfigurationItem MUI config
modelBuilder.Entity<ConfigurationItem>().Ignore(s => s.Name);
modelBuilder.Entity<ConfigurationItem>().Ignore(s => s.Description);
modelBuilder.Entity<ConfigurationItem>().HasMany(s => s.Translations).WithOne().HasForeignKey(x => x.ConfigurationItemTranslationId);
//category MUI config
modelBuilder.Entity<Category>().Ignore(s => s.Name);
modelBuilder.Entity<Category>().Ignore(s => s.Description);
modelBuilder.Entity<Category>().HasMany(s => s.Translations).WithOne().HasForeignKey(x => x.CategoryTranslationId);
//CI Categories Many to Many
modelBuilder.Entity<ConfigurationItemCategory>().HasKey(cc => new { cc.CategoryId, cc.CIId });
modelBuilder.Entity<ConfigurationItemCategory>().HasOne(cc => cc.Category).WithMany(cat => cat.ConfigurationItems).HasForeignKey(cc => cc.CategoryId);
modelBuilder.Entity<ConfigurationItemCategory>().HasOne(cc => cc.ConfigurationItem).WithMany(ci => ci.Categories).HasForeignKey(cc => cc.CIId);
//CI Catalog Many to Many
modelBuilder.Entity<CICatalog>().HasKey(cc => new { cc.CatalogId, cc.ConfigurationItemId });
modelBuilder.Entity<CICatalog>().HasOne(cc => cc.Catalog).WithMany(cat => cat.CIs).HasForeignKey(cc => cc.CatalogId);
modelBuilder.Entity<CICatalog>().HasOne(cc => cc.ConfigurationItem).WithMany(ci => ci.Catalogs).HasForeignKey(cc => cc.ConfigurationItemId);
//Company Customers Many to Many
modelBuilder.Entity<CompanyCustomers>().HasKey(cc => new { cc.CustomerId, cc.ProviderId });
modelBuilder.Entity<CompanyCustomers>().HasOne(cc => cc.Provider).WithMany(p => p.Customers).HasForeignKey(cc => cc.ProviderId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<CompanyCustomers>().HasOne(cc => cc.Customer).WithMany(c => c.Providers).HasForeignKey(cc => cc.CustomerId);
//Company Catalog Many to Many
modelBuilder.Entity<CompanyCatalog>().HasKey(cc => new { cc.CatalogId, cc.CompanyId });
modelBuilder.Entity<CompanyCatalog>().HasOne(cc => cc.Catalog).WithMany(c => c.Companies).HasForeignKey(cc => cc.CatalogId);
modelBuilder.Entity<CompanyCatalog>().HasOne(cc => cc.Company).WithMany(c => c.Catalogs).HasForeignKey(cc => cc.CompanyId);
//Author Catalog Many to Many
modelBuilder.Entity<CatalogAuthors>().HasKey(ca => new { ca.AuthorId, ca.CatalogId });
modelBuilder.Entity<CatalogAuthors>().HasOne(ca => ca.Catalog).WithMany(c => c.Authors).HasForeignKey(ca => ca.CatalogId);
modelBuilder.Entity<CatalogAuthors>().HasOne(ca => ca.Author).WithMany(a => a.AuthoringCatalogs).HasForeignKey(ca => ca.AuthorId);
//Company one to many with owned Catalog
modelBuilder.Entity<Company>().HasMany(c => c.OwnedCatalogs).WithOne(c => c.OwnerCompany).HasForeignKey(c => c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
//Company one to many with owned Categories
modelBuilder.Entity<Company>().HasMany(c => c.OwnedCategories).WithOne(c => c.OwnerCompany).HasForeignKey(c => c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
//Company one to many with owned CIs
modelBuilder.Entity<Company>().HasMany(c => c.OwnedCIs).WithOne(c => c.OwnerCompany).HasForeignKey(c => c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
//CIDriver one to many with DriverCompatibilityEntry
modelBuilder.Entity<CIDriver>().HasMany(c => c.CompatibilityList).WithOne(c => c.ParentCI).HasForeignKey(c => c.ParentCIID).OnDelete(DeleteBehavior.Restrict);
//User Group Many to Many
modelBuilder.Entity<UserGroup>().HasKey(ug => new { ug.UserId, ug.GroupId });
modelBuilder.Entity<UserGroup>().HasOne(cg => cg.User).WithMany(ci => ci.Groups).HasForeignKey(cg => cg.UserId);
modelBuilder.Entity<UserGroup>().HasOne(cg => cg.Group).WithMany(ci => ci.Users).HasForeignKey(cg => cg.GroupId);
//User one to many with Company
modelBuilder.Entity<Company>().HasMany(c => c.Employees).WithOne(u => u.Employer).HasForeignKey(u => u.EmployerID).OnDelete(DeleteBehavior.Restrict);
}
更新 2
这是一个指向最小复制示例的驱动器链接。我还没有在客户端中实现 PUT,因为 post 方法已经重现了这个问题。
解决方案
您在此处枚举现有步骤,并在现有步骤集合中搜索没有意义的现有步骤。
foreach(var step in existingDeploymentScenario.InstallSteps)
var existingStep = existingDeploymentScenario.InstallSteps
.FirstOrDefault(s => s.ID == step.ID);
虽然它可能应该是:
foreach(var step in ds.InstallSteps)
推荐阅读
- powershell - Powershell:列出 ADusers - 需要用户所属的组
- exe - 执行 exe 文件时出错。“原因:系统找不到指定的文件”
- android - 我得到“调用需要 API 级别 X(当前最小值为 Y)”,即使我将 min 更改为 X
- web-scraping - 是否可以将 Scrapy 实现到桌面应用程序中?
- django - Heroku 在通过 Django 部署之前构建 React 应用程序
- android - 在 MaterialCardView 的中心设置自定义图标
- elasticsearch - 按日期和对象分组弹性搜索
- eslint - eslint 中的 Tslint 规则等效
- swift - 覆盖变量中的值
- scala - 如何根据 Scala 中的对象字段对 List 对象进行排序?