是否可以在Entity Framework中捕获0..1到0..1的关系?

时间:2014-02-19 18:36:30

标签: c# entity-framework foreign-keys

有没有办法在Entity Framework中为可为空的外键关系创建可为空的反向导航属性?在数据库用语中, 0..1到0..1 关系。

我尝试过如下,但我不断收到错误消息:

  

无法确定类型“Type1”和“Type2”之间关联的主要结尾。必须使用关系流畅API或数据注释显式配置此关联的主要结尾。

public class Type1 {

    public int ID { get; set; }

    public int? Type2ID { get; set; }
    public Type2 Type2 { get; set; }
}

public class Type2 {

    public int ID { get; set; }

    public int? Type1ID { get; set; }
    public Type1 Type1 { get; set; }
}

据我所知,整数列只能存在于一个表或另一个表中,但是当然应该可以涵盖所有必要的情况吗? E.g:

Type1      Type2
========   ===============
ID         ID   | Type1ID
--------   ---------------
1          1    | null
2          2    | 2

我尝试过使用数据注释(例如[ForeignKey]在一端,[InverseProperty]在两端),但这些似乎都没有帮助。

如果可能,数据注释解决方案将优于Fluent API。此外,对于任一类,从域的角度来看,int?属性并非绝对必要,如果有帮助的话。

有一个有趣的解决方法here意味着无法在实体框架中捕获这种关系(实际上,这是一个可选择作为集合的一部分的项目) - 如果是这样,是否存在任何支持此文件的文件?。

3 个答案:

答案 0 :(得分:31)

"这可能很难"我读到你的问题时就是这样。但我再次发现,一对一协会是奸诈的混蛋。我们走了。

我假设0..1 – 0..1表示两个对象可以彼此独立存在,但也可以彼此唯一关联。

让我们具体化。 CarDriver。想象一下,有许多汽车和司机,其中包括CarA和DriverA。现在假设您希望CarA与DriverA相关联,并且您的实现是DriverA将自己链接到CarA。但是一旦DriverA执行此操作,您希望CarA仅适用于DriverA, CarA的关联不再是可选的,所以它也应立即设置。

如何实现?

选项1:

如果这是工作模式:

public class Car
{
    public int CarId { get; set; }
    public string Name { get; set; }
    public int? DriverId { get; set; }
    public virtual Driver Driver { get; set; }
}

public class Driver
{
    public int DriverId { get; set; }
    public string Name { get; set; }
    public int? CarId { get; set; }
    public virtual Car Car { get; set; }
}

从技术上讲, DriverA 可以拥有 CarA 的外键, CarA DriverB 的外键。< / p>

enter image description here

因此,当建立外键DriverA-CarA时,您应该&#34;同时&#34;建立反向外键CarA-DriverA。这是您应该在代码中执行的操作,这意味着它是业务规则。实际上,它不是原子操作,因此您必须确保它在一次数据库事务中完成。

类模型至少支持用例,但它过于宽松。它需要受到限制。更重要的是,它不会使用EF 。 EF关于必须设定主要目标的投诉。如果你这样做,EF将不会创建双向关联。

提出了另一种映射here。我试过了,但有两个可选的关联:

Driver的映射配置中:

this.HasOptional(t => t.Car).WithMany().HasForeignKey(d => d.CarId);

Car的映射配置中:

this.HasOptional(t => t.Driver).WithMany().HasForeignKey(c => c.DriverId);

(没有数据注释替代方案)

我发现EF在创建新的驱动程序和汽车时只在数据库中设置了一个外键值。您必须分别设置和保存两个关联,管理您自己的事务。使用现有对象时,您仍然需要设置两个外键,尽管可以在一次SaveChanges调用中保存。

更好的选择?我们来看看......

选项2:

这是您引用的链接中提到的一对多关联。此模型需要外部约束,但创建关联是原子的。而且你仍然在一端有一个参考,另一端有一个集合。它可以轻松映射到EF。

选项3:

您可以创建一个包含两个外键的联结表CarDriverCarDriver,这两个外键都包含其唯一的主键:

enter image description here

这是一个常规的多对多关联。默认情况下,EF会将此映射为类模型,其中CarDriver具有指向彼此的集合属性,并且未直接映射联结表:

public class Car
{
    public int CarId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Driver> Drivers { get; set; }
}

public class Driver
{
    public int DriverId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Car> Cars { get; set; }
}

现在,关联的创建是一个原子操作。使用EF映射此模型是完全可能的。相互引用已经消失,但您仍然可以将集合属性的FirstOrDefault()作为代理参考。

但这是一个重要的问题。现在每个对象可以有任何个关联的对应项。如果您创建了关联,则需要一个编码的业务规则来检查所涉及的对象是否还没有任何关联。也许这个选项比选项2更差。但是我之所以提到它是因为下一个选项:

选项4

选项3是原子的,但它也需要外部约束。要使关联成为独占关系,CarDriver中的两列都应具有唯一键,因此每个汽车或驱动程序只能在表格中出现一次。通过这些索引,模型实现了双向可选的1:1关联。处理它的任何代码都必须遵守规则。安全又健全......

但你想要吗?

您永远不会首先在EF代码中配置选项4。没有流畅的映射或数据注释。您必须使用Migrations来执行此操作,或者先使用数据库。

如果数据库由多个应用程序使用,您最好的选择可能是确定数据模型中的约束。如果规则不太可能改变,这也可能是一个可行的选择。但是,如果只有一个(或两个)应用程序在数据库上工作,并且业务规则可能将来发生变化,我更喜欢伴随编码规则的更自由的数据模型。编码业务逻辑比数据模型更容易更改。

此外,即使使用选项4,在尝试创建关联之前,您还需要业务逻辑来检查关联是否存在,否则丑陋的数据库异常将为您执行此操作。

结论

选项1最接近您的要求。但是我不喜欢设置这两个外键的义务,它很容易被未来的开发者遗忘或忽略。

但是,对于可以忘记的编码业务规则而言,选项2和3的要求更高。这些收藏作为代理人不自然而且#1;&#34; 1&#34;结束。选项3对我有一些吸引力,因为CarDriver在数据库中是完全独立的,并且关联是具有不可为空的外键的记录(DBA也倾向于这样)。

选项4具有相同的吸引力,当多个应用程序必须实现需要对选项2和3施加的外部约束时,它是最佳选择。此外,即使忘记编码规则,数据库约束也是最后一个问题。但它不容易通过EF代码首先实现。

答案 1 :(得分:1)

这不是您如何使用实体框架构建表。这些类的正确声明是:

public class Type1 {
    public int ID { get; set; }
}

public class Type2 {

    public int ID { get; set; }
    public virtual Type1 @Type1 { get; set; }
}

编辑: 我认为做你想做的最简单的方法是:

    public class Type1 {
        public int ID { get; set; }
        public virtual ContainerClass {get; set;}
    }

    public class Type2 {

        public int ID { get; set; }
        public virtual ContainerClass {get; set;}
    }

    public class ContainerClass {
         public int ID {get;set;}
         public virtual Type1 @Type1 {get;set;}
         public virtual Type2 @Type2 {get;set;}
}

答案 2 :(得分:0)

从内存中执行此操作,很遗憾未进行测试:

public Type1
{
    [Key]        
    public int ID { get; set; }

    [ForeignKey("Type2")]
    public int? Type2ID { get; set; }
    public virtual Type2 Type2 { get; set; }
}

public Type2
{
    [Key]        
    public int ID { get; set; }

    [ForeignKey("Type1")]
    public int? Type1ID { get; set; }
    public virtual Type1 Type1 { get; set; }
}

另外,在实体上使用显式ForeignKey允许测试是否存在关联对象而不必调用数据库。例如IEnumerable<Type1>.Where(t => t.Type2ID.HasValue)会返回Type1的所有具有关联Type2的对象!有关详细信息,请参阅this question