【问题标题】:EF Core - Why are entities being saved as new DB entities when they should be Foreign Key Relationships?EF Core - 为什么实体应该是外键关系时被保存为新的数据库实体?
【发布时间】:2019-08-23 03:43:48
【问题描述】:

我们的上下文不再使用 EF6 DB 优先模型 - 我正在构建一个 ASP Core API,该 API 将 EF Core Code First 模型用于数据库上下文。要在滚动更新中执行此操作,我的上下文必须存在于当前数据库架构中,因此我使用 EF Fluent API 为不反映当前数据库架构的 Code First 模型构建我的实体映射。

在插入操作期间,我遇到了实体上的导航属性问题。使用标准 .Include(x => x.OtherEntity) 格式从数据库中获取主要实体的数据效果很好,将主要实体保存回数据库是问题所在。

使用 Fluent API 映射实体对我来说是新事物,因此学习曲线很可能是我的问题所在。我尝试过使用 OwnsOne 与 HasOne,但 MS 文档表明 HasOne() 是进行此映射的正确方法。

当我在流畅映射中使用 .HasOne() 时,我的主要实体具有外键字段的影子属性(在下面的代码中)

异常消息在这种情况下没有用,因为它们不反映映射问题,它们建议当导航属性的表具有标识列时无法插入数据(IE 无法插入记录具有显式 ID)-> 这很奇怪,因为我没有尝试通过这些导航属性插入数据,我只是尝试使用外键将我的主要实体链接到该次要实体。

实体映射:

 // WorkOrder Entity Mapping:
 modelBuilder.Entity<WorkOrder>().ToTable("WorkOrder");
 modelBuilder.Entity<WorkOrder>().Property(x => x.Id).HasColumnName("IDWorkOrder");
  modelBuilder.Entity<WorkOrder>().Property(x => x.CreatedBy).HasColumnName("IDUserCreated");
  modelBuilder.Entity<WorkOrder>().Property(x => x.UpdatedBy).HasColumnName("IDUserUpdated");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOCategory");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDProblem");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOCostCenter");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOLocation");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOPriority");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOStatus");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOTrade");
  modelBuilder.Entity<WorkOrder>().Property<Guid?>("IDUserCompleted");
  modelBuilder.Entity<WorkOrder>().Property<Guid?>("IDParentWO");

  // WO Navigation Properties:
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Category).WithOne().HasForeignKey<WorkOrder>("IDWOCategory").HasPrincipalKey<Category>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Problem).WithOne().HasForeignKey<WorkOrder>("IDProblem").HasPrincipalKey<Problem>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.CostCenter).WithOne().HasForeignKey<WorkOrder>("IDWOCostCenter").HasPrincipalKey<CostCenter>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Location).WithOne().HasForeignKey<WorkOrder>("IDWOLocation").HasPrincipalKey<Location>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Priority).WithOne().HasForeignKey<WorkOrder>("IDWOPriority").HasPrincipalKey<Priority>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Status).WithOne().HasForeignKey<WorkOrder>("IDWOStatus").HasPrincipalKey<Status>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Trade).WithOne().HasForeignKey<WorkOrder>("IDWOTrade").HasPrincipalKey<Trade>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Requester).WithOne().HasForeignKey<WorkOrder>("IDRequester").HasPrincipalKey<Requester>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.UserCompleted).WithOne().HasForeignKey<WorkOrder>("IDUserCompleted").HasPrincipalKey<User>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.ParentWorkOrder).WithOne().HasForeignKey<WorkOrder>("IDParentWO").HasPrincipalKey<WorkOrder>(c => c.Id);

在控制器中获取数据:(就像一个魅力!)

[HttpGet("{Id}")]
public async Task<ActionResult<List<WorkOrder>>> GetWorkOrders(Guid Id)
{
    var result = await WorkOrdersContext.WorkOrders
        .Include(x => x.Problem)
        .Include(x => x.Status)
        .Include(x => x.Requester)
        .Include(x => x.ParentWorkOrder)
        .Include(x => x.Category)
        .Include(x => x.Trade)
        .Include(x => x.Location)
        .Include(x => x.CostCenter)
        .Include(x => x.Priority)
        .Where(x => x.Id == Id)
        .ToListAsync();

    return Ok(result);
}

在控制器中保存新数据:(它发生故障的地方!)

[HttpPost]
public async Task<ActionResult<WorkOrder>> CreateWorkOrderFromPending([FromBody]WorkOrder call)
{
    // Insert the Work Order to the DB:
    DbContext.WorkOrders.Add(call);
    var saveResult = await DbContext.SaveChangesAsync();

    // Check if any oddities occurred during the save:
    if (saveResult == 0) return BadRequest("An Error occurred during saving and the Call was not saved, please try again.");

    // Return the Inserted Work Order:
    return Ok(call);
}

在这里,当它尝试保存新的工单时,由于映射类型(来自实体映射代码 sn-p 的问题、类别、成本中心等)而引发异常它正在尝试将数据保存为新实体而不是在现有实体上建立 FK 关系。我认为这就是我缺少 Fluent API 的一些逻辑的地方!

任何帮助都将不胜感激,因为很难用谷歌搜索将这个问题放在几句话中!

【问题讨论】:

  • 与 fluent API 无关。您将对象图 Add() 到上下文中,所有对象都将标记为已添加。您可以稍后通过更改跟踪器更改此设置,或使用 Attach() 而不是 Add(),这会将对象设置为 Unchanged,除非它们具有空的 StoreGenerated 键,在这种情况下它们将设置为已添加。
  • @DavidBrowne-Microsoft,太棒了!感谢您提供的信息,我会看看这样做是否有效!
  • @DavidBrowne-Microsoft - 我收回最后的声明!它现在按预期工作,非常感谢您的输入!来自 Code First 背景,我从来不需要使用 .Attach() 执行此操作并更改实体状态,但我想尝试使 EF Core 与 DB First 共存也不是一个简单或正常的情况!再次感谢您的帮助!
  • 旁注:所有这些 WithOne 看起来很可疑,可能会导致您将来出现问题 - 所有这些 FK 在 WorkOrder 表中不太可能是唯一的。考虑将它们更改为WithMany()

标签: c# entity-framework-core ef-fluent-api


【解决方案1】:

感谢@DavidBrowne-Microsoft,我得到了答案,试图让我们过去的 DB First 东西与 .NET Core 共存并非易事,并且来自 Code First 背景,其中 .Add() 是路径这行得通,很难看出 .Attach() 是如何做到的。

[HttpPost]
public async Task<ActionResult<WorkOrder>> CreateWorkOrderFromPending([FromBody]WorkOrder call)
{
    // Insert the Work Order to the DB:
    // DbContext.WorkOrders.Add(call);
    var entity = DbContext.WorkOrders.Attach(call);

    entity.State = EntityState.Added;

    var saveResult = await DbContext.SaveChangesAsync();

    // Check if any oddities occurred during the save:
    if (saveResult == 0) return BadRequest("An Error occurred during saving and the Call was not saved, please try again.");

    // Return the Inserted Work Order:
    return Ok(call);
}

使用 .Attach() 而不是 .Add() 可以解决问题,将实体放入并将 FK 映射到我的 Fluent 映射中它们各自的阴影属性!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多