使用Code First在Entity Framework中定义一对一或零关系

时间:2012-06-08 11:33:59

标签: entity-framework-4.3

我知道关于这个问题有很多问题,但我找不到能够解释如何解决我的特定问题的问题。我认为这意味着它可能是不可解的(我认为它可能是'向后'到EF的思维方式),但我不得不问。

我有一个模型,其中包含三个(缩写)POCO:

[Table("People")]
public class Person {
    public int PersonID { get; set; }
    [Required]
    public string PersonName { get; set; }
}

public class Location {
    public int LocationID { get; set; }
    public int LocationTypeID { get; set; }
    public virtual LocationType LocationType { get; set; }
}

public class Van : Location {
    public int PartyID { get; set; }
    public virtual Party Party { get; set; }
}

这些由(缩写的)数据库表支持(我们手工编写):

CREATE TABLE People (
    PersonID INTEGER IDENTITY NOT NULL,
    PersonName VARCHAR(255) NOT NULL,
    PRIMARY KEY (PersonID)
)

CREATE TABLE Locations (
    LocationID INTEGER IDENTITY NOT NULL,
    LocationTypeID INTEGER NOT NULL,
    FOREIGN KEY (LocationTypeID) REFERENCES LocationTypes(LocationTypeID)
)

CREATE TABLE Vans (
    LocationID INTEGER NOT NULL,
    PersonID INTEGER NOT NULL,
    PRIMARY KEY (LocationID),
    FOREIGN KEY (LocationID) REFERENCES Locations(LocationID),
    FOREIGN KEY (PersonID) REFERENCES People(PersonID)
)

您可以想象LocationTypes的样子。

Locations是每个类型的表层次结构的根 - 还有适当的检查约束来强制执行此操作。 Vans是一种位置,其他与此类无关的内容如Warehouse

现在,Van属于Person,因为我们向员工发放面包车,他们有责任填写燃料,而不是崩溃,将其带到客户站点,当他们用尽所有螺丝,钻头和铠装直流电缆时,他们会订购更多东西。但是,并非每个Person都有一个面包车(其中一些面包车在一辆面包车中成对使用),Person表没有指向Van的外键 - 它是另一种方式。这在某种程度上是一次历史性事故,但它对情况进行了非常巧妙的模拟,因为虽然Person不需要Van,但Van最确定必须有一个人

所以我的问题是:如何让Person拥有一个带有Van的导航属性?

public virtual Van Van { get; set; }

我已经做了大量的数据注释和流畅的API,而我最接近的是OnModelCreating

modelBuilder.Entity<Van>()
    .HasRequired(v => v.Person)
    .WithOptional(p => p.Van);

不幸的是,这会尝试使用生成Van对象的代理填充Location属性。它甚至可能是正确的Location对象(我无法检查),但它没有意识到应该寻找面包车。但是,我确实怀疑它在查找时可能会尝试将PersonIDLocationID匹配 - 如果没有Fluent API映射,我根本就没有任何面包车,这就是我的意思expect(所有PersonID值都低于对应于vans的最低LocationID值,因此无法找到任何内容。

如果Person具有Van的可空外键,这无疑会非常容易,但是我们在两个方向上都有外键,如果我们从{{{{}}中取出一个外键1}}然后我们不会对VanVan的绝对必要约束进行建模。

所以,我想,Person拥有这种关系,而Van上的Van属性是一个反向导航属性,但似乎EF并不是很擅长这种关系一对一的技巧,即使一端是可选的。有没有办法让它发挥作用,还是我必须接受妥协?

为了Entity Framework的缺失功能,我们通常拒绝妥协数据库模型。我真正需要的是告诉EF可以通过加入Vans.PersonID = Person.PersonID上的Vans来填充Person上的Van属性。

1 个答案:

答案 0 :(得分:4)

问题在于目前不支持此功能。你提到你没有找到任何问题解决你的问题。我想知道你是否发现任何问题,提到EF不支持解决这种一对一关系所必需的唯一约束/候选键。

在数据库中,只有依赖表中的FK是唯一的,才能实现一对一的关系。这可以通过两种方式完成:在依赖表中的FK列上放置唯一约束(索引),或者在从属表中将PK用作FK到主表。

EF对参照完整性实施与数据库相同的规则,但在一对一关系和缺乏对唯一约束的支持的情况下,它不支持前一种方式。 EF只能通过将依赖表的PK作为FK到主表来建模一对一关系。

您可以在Data UserVoice上投票支持唯一约束/候选键。

如何解决您的特定问题?通过欺骗EF。让EF认为您具有一对多关系,并在PersonID表中的Van上设置唯一约束。比这更新你的人:

[Table("People")]
public class Person {
    public int PersonID { get; set; }
    [Required]
    public string PersonName { get; set; }

    public virtual ICollection<Van> Vans { get; set; }

    [NotMapped]
    public Van Van 
    {
        get { return Vans.FirstOrDefault(); }
        set
        {
             Vans.Clear();
             if (value != null)
             {
                 Vans.Add(value);
             }
        }
    }
}

这是一个非常丑陋的解决方法,因为Vans集合仍然是公开的。您可以发挥其可见性,但要确保您了解一些事情:

  • Vans未公开后,您必须将其映射到OnModelCreating,并且该上下文必须能够查看该属性,或者您必须提供执行此操作的映射配置。请查看thisthis以获取更多信息。
  • Vans属性不得违反代理创建规则以支持延迟加载
  • 预先加载必须使用Vans属性