实体框架:即使使用流畅的 api 或数据注释,列名 *_ID 也无效

问题描述

我一整天都在研究的奇怪问题。我正在使用实体框架 6。我的问题是我有三个实体:

public partial class Order : ILocationbearingObject
{
    public int Id { get; set; }
    // other properties and relationships here
    public int? OrderProfileId { get; set; }
    public int OrderTemplateId { get; set; }

    public virtual OrderProfile Profile { get; set; } // optional property
    public virtual OrderTemplate OrderTemplate{ get; set; }
}

public class OrderProfile 
{
    public int Id { get; set; }
    // other properties
    
    // added here 6/15/2021
    public virtual OrderTemplate OrderTemplate{ get; set; }
}

public class OrderTemplate : EntityMetaData
{
    public int Id { get; set; }
    // other properties

    public int? OrderProfileId{ get; set; }
    public OrderProfile OrderProfile { get; set; }
}

在我们的模型构建器中,我们有以下定义:

modelBuilder.Entity<Order>()
    .HasOptional(x => x.OrderProfile)
    .WithMany(x => x.Orders)
    .HasForeignKey(x => x.OrderProfileId);

modelBuilder.Entity<OrderProfile>()
    .HasOptional(x => x.OrderTemplate)
    .WithOptionalPrincipal(x => x.OrderProfile);

但即使使用上述流畅的 api 模型,我们也会得到错误

无效的列名“OrderProfile_Id”

在各种测试中,我无法找到发生此问题的原因,因此我查看了我们的日志,发现当此错误开始出现时,它会抬头,然后能够找到与 OrderProfile 关联的更改,并发现唯一的更改所做的就是添加从 OrderProfile 到 OrderTemplate 的关系。

当我将那个流畅的 api 关系 OrderProfile 删除到 OrderTemplate 时,它​​按预期工作......我不需要与 OrderTemplate 的关系,但希望它在那里,我如何建立可选的 1 到可选的 1 关系不破坏其他关系?另外,为什么会受到其他关系的影响?

更新 6/15/2021 所以我发现我在 OrderProfile 模型中有一个反向导航属性

public virtual OrderTemplate OrderTemplate{ get; set; }

删除那个和相关的流畅关系

        modelBuilder.Entity<OrderProfile>()
            .HasOptional(x => x.OrderTemplate)
            .WithOptionalPrincipal(x => x.OrderProfile);

执行上述操作解决了该问题,但由于某种原因,该问题似乎已下降到另一个具有上述循环引用的关系。 Order 类与这个级联问题有关。我想这是一个非常值得关注的问题,因为这个应用程序在过去的 4 年里运行良好,而这些关系像这样衰退是令人担忧的。有谁知道为什么会这样?

解决方法

如果您使用正确的命名约定,EF 将发挥神奇的作用。在此示例中,您不需要 fluent API 来关联实体。

public partial class Order : ILocationBearingObject
{
    public int Id { get; set; }
    public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey

    //variable name must be OrderProfile not Profile
    public virtual OrderProfile OrderProfile { get; set; } 
}

public class OrderProfile
{
    public OrderProfile()
    {
       Orders = new HashSet<Order>();
    }

    public int Id { get; set; }

    //be aware circular reference at any conversion or mapping
    public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
,

我也遇到过这样的错误。这是由于 OrderTemplate 类中的 OrderProfileId 属性与流畅的 api 模型不匹配造成的

如果我没记错的话,您希望 OrderProfile 模型在 Order 和 OrderTemplate 之间建立多对多的关系。然后,如果是这种情况,请在 OrderProfile 中添加 nvaigation 属性。

public class OrderProfile 
{
    public int Id { get; set; }
    // other properties

    public virtual ICollection<Order> Orders { get; set; }
    public virtual OrderTemplate OrderTemplate { get; set; }
}

然后把fluent api模型改成这样

// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
//     .HasOptional(x => x.OrderProfile)
//     .WithMany(x => x.Orders)
//     .HasForeignKey(x => x.OrderProfileId);

modelBuilder.Entity<OrderTemplate>()
    .HasOptional(x => x.OrderProfile)
    .WithOptional(x => x.OrderTemplate);
,

您首先使用数据库,这总是会在实际数据库模型与 EF 从类和属性名称以及映射代码(= 概念模型)推断出的模型之间留下不匹配的空间。如果发生这种情况,让 EF 从概念模型生成数据库并查看它在哪里创建它期望的列 OrderProfile_Id 可能会有所帮助。

这是您在记录 SQL 语句时会看到的内容:

CREATE TABLE [dbo].[OrderTemplates] (
    [Id] [int] NOT NULL IDENTITY,[OrderProfileId] [int],[OrderProfile_Id] [int],CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)

...

ALTER TABLE [dbo].[OrderTemplates]
    ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
    FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])

您会看到预期的可为空列 OrderProfile_Id,它是 OrderProfiles 的 FK。值得注意的是,EF 使用 OrderProfileId 作为外键字段。它只是一个可以用于任何事情的字段。

那是因为 EF6 不支持 1:1 关联作为外键关联(引用属性和原始 FK 属性)。

知道了这一点,补救方法很简单:删除属性OrderTemplate.OrderProfileId并告诉EF使用数据库中的字段OrderTemplate.OrderProfileId

modelBuilder.Entity<OrderProfile>()
    .HasOptional(x => x.OrderTemplate)
    .WithOptionalPrincipal(x => x.OrderProfile)
        .Map(m => m.MapKey("OrderProfileId"));

也就是说,我想知道为什么 Order 有一个指向 OrderProfile 的外键。它的 OrderProfile 不是由它的 OrderTemplate 决定的吗?如果是多余的关系,最好将其删除。