【问题标题】:Can I create a bidirectional foreign key relationship using Entity Framework Code First data annotations?我可以使用 Entity Framework Code First 数据注释创建双向外键关系吗?
【发布时间】:2014-11-18 11:12:36
【问题描述】:

我正在使用 EF 6 Code First 并试图找出使用数据注释配置我的关系的最佳方法,但由于某种原因,EF 正在向架构中添加我不想要的额外列。

我有两个实体:ShipVoyage。一艘船可以有很多航次;一次航程只属于一艘船。所以我从这个开始(为 SO 目的而简化):

public class Ship
{
    public int Id { get; set; }
    public virtual ICollection<Voyage> Voyages { get; set; }
}

public class Voyage
{
    public int Id { get; set; }
    public int ShipId { get; set; }
    public virtual Ship Ship { get; set; }
    public DateTimeOffset Started { get; set; }
}

这会在数据库中创建两个表,如下:

CREATE TABLE [dbo].[Ship] (
    [Id]                INT                IDENTITY (1, 1) NOT NULL,
    CONSTRAINT [PK_dbo.Ship] PRIMARY KEY CLUSTERED ([Id] ASC)
);

CREATE TABLE [dbo].[Voyage] (
    [Id]               INT                IDENTITY (1, 1) NOT NULL,
    [ShipId]           INT                NOT NULL,
    [Started]          DATETIMEOFFSET (7) NOT NULL,
    CONSTRAINT [PK_dbo.Voyage] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_dbo.Voyage_dbo.Ship_ShipId] FOREIGN KEY ([ShipId]) REFERENCES [dbo].[Ship] ([Id]) ON DELETE CASCADE
);

到目前为止,一切都很好。当我想查询这些数据时,问题就来了。我需要获取船舶列表,但对于每艘船,我还需要最近的航次。尽管我可以使用 SQL 编写这样的查询,但我看不到一种编写 LINQ 查询的方法可以一键完成。

此时我的选择似乎是:

  • 装载所有船只,并为每艘船急切装载其所有航次。我不想这样做,因为会影响性能(每艘船最终可能有很多航程)
  • 首先加载所有船只,然后foreach 在结果列表中,并为每艘船执行单独的查询以加载其“最近”航次。虽然这样可行,但与单个查询相比,它似乎效率低下。

然后我想我可以向Ship 实体添加一个导航属性,用于直接引用“最近的”航程。所以我尝试了这个:

public class Ship
{
    public int Id { get; set; }
    public virtual ICollection<Voyage> Voyages { get; set; }
    public int? MostRecentVoyageId { get; set; }    <-- new property added
    public virtual Voyage MostRecentVoyage { get; set; }    <-- new property added
}

我将MostRecentVoyageId 设为可空,因为一艘船在首次创建时根本没有任何航程。

这在数据库中为我提供了以下信息:

CREATE TABLE [dbo].[Ship] (
    [Id]                  INT                IDENTITY (1, 1) NOT NULL,
    [MostRecentVoyageId]  INT                NULL,
    CONSTRAINT [PK_dbo.Ship] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_dbo.Ship_dbo.Voyage_MostRecentVoyageId] FOREIGN KEY ([MostRecentVoyageId]) REFERENCES [dbo].[Voyage] ([Id])
);

这对于Ship 表来说很好,但我在Voyage 表中得到了这个:

CREATE TABLE [dbo].[Voyage] (
    [Id]               INT                IDENTITY (1, 1) NOT NULL,
    [ShipId]           INT                NOT NULL,
    [Started]          DATETIMEOFFSET (7) NOT NULL,
    [Ship_Id]          INT                NULL,
    CONSTRAINT [PK_dbo.Voyage] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_dbo.Voyage_dbo.Ship_ShipId] FOREIGN KEY ([ShipId]) REFERENCES [dbo].[Ship] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.Voyage_dbo.Ship_Ship_Id] FOREIGN KEY ([Ship_Id]) REFERENCES  [dbo].[Ship] ([Id])
);

注意额外的Ship_Id 列和额外的外键关系。我很确定不需要这个专栏,但我找不到摆脱它的方法。我试过使用 [ForeignKey][InverseProperty] 属性,但这些只是给我一个例外。

这是我无法使用 EF 数据注释配置的那种关系吗?我必须为此使用流利的语法吗?反正我做错了吗:有没有更好的方法来使用我的原始实体类型进行 LINQ 查询?

我尝试在 SO 上寻找有类似问题的人,我找到了 thisthisthis,但这些似乎都没有帮助。

【问题讨论】:

  • 我有几乎完全相同的模型,但在不同的上下文中,我通过将[ForeignKey("MostRecentVoyage")] 添加到 MostRecentVoyageId 并将[InverseProperty("Ship")] 添加到航程来删除数据库中不需要的列。这是否会引发您提到的异常,如果是,那么异常是什么。
  • @BenRobinson 我刚刚尝试过,它不会引发异常,但我仍然会在Voyage 表上得到额外的Ship_Id 列。我假设[InverseProperty] 属性应该出现在航程的ShipId 属性上?
  • 不应该将[InverseProperty("Ship")] 放在Ship 实体上的Voyages 属性上。
  • @BenRobinson 太棒了!这就像一个魅力。非常感谢。您能否将您的评论放入答案中以便我接受?
  • 按要求将其添加为答案。

标签: c# linq entity-framework ef-code-first


【解决方案1】:

我有几乎完全相同的模型,但在不同的上下文中,我通过将[ForeignKey("MostRecentVoyage")] 属性添加到MostRecentVoyageId 并将[InverseProperty("Ship")] 属性添加到@987654324 来删除数据库中不需要的列@ 属性都在 Ship 实体/类上。

【讨论】:

  • 我已经找到了一种方法来执行单个 LINQ 查询以在一次“命中”中获取数据,但生成的 SQL 比使用 MostRecentVoyage 外键时更复杂,即。 ship.MostRecentVoyageship.Voyages.OrderByDescending(x=&gt;x.Started).FirstOrDefault()。您认为显式外键是一个糟糕的架构决策,还是值得增加维护成本以获得更高性能的查询? (假设它的性能更高,我还没有进行基准测试)
  • Becnhmark 并且如果动态计算值对于您的目的来说足够快,那么就去吧。然后您不必担心确保外键始终正确,正如您所说的那样,这可能是维护的痛苦。
猜你喜欢
  • 1970-01-01
  • 2011-09-10
  • 1970-01-01
  • 2014-05-02
  • 2016-03-29
  • 1970-01-01
  • 1970-01-01
  • 2013-01-10
  • 2023-04-11
相关资源
最近更新 更多