外键依赖于另一个外键作为其父键

时间:2016-02-13 14:29:54

标签: c# sql-server entity-framework

我正在创建一个作为其他项目基础的库,可以找到项目的详细信息here,并且可以找到nuget包here

由于这是一个基础库,我想允许用户扩展模型,所以我创建接口而不是具体的库(默认实现显然存在,但这超出了当前问题的范围),但是因为实体框架只能用具体的类来完成导航/外键,我正在通过通用接口的路径,它工作得很好,但我已经到了一个点,我想在同一个表中基于另一个外键限制外键

在搜索答案时,这个问题最符合我的要求

  

SQL: Foreign key that is a child of another foreign key - Stack Overflow

在那里提供的解决方案说技术上你应该删除父键,因为子表已经有父键可用于关联到父表,我完全同意它,但在我的情况下,这是不可能的,因为它将导致循环依赖地狱,这应该不惜一切代价远离软件项目。

IOrganization接口

public interface IOrganization<TLinkedAddress> where TLinkedAddress : ILinkedAddress
{
    long ID { get; set; }
    string Name { get; set; }
    string Brand { get; set; }
    string Genre { get; set; }
    string Industry { get; set; }
    string Master_Security_Stamp { get; set; }
    long? Control_Branch_ID { get; set; }

    [ForeignKey("Control_Branch_ID")]
    TLinkedAddress Control_Branch { get; set; }

    IList<TLinkedAddress> Branches { get; set; }
}

ILinkedAddress接口

public interface ILinkedAddress
{
    long ID { get; set; }
    string Address { get; set; }
    long LocationID { get; set; }
    string AddressType { get; set; }
    string Contact_Person_Name { get; set; }
    string Contact_Person_Number { get; set; }
    string Contact_Person_Relation { get; set; }
    [ForeignKey("OrganizationID")]
    long? OrganizationID { get; set; }
    [ForeignKey("UserID")]
    long? UserID { get; set; }
}

IEducation接口

public interface IEducation<TUser, TOrganization, TLinkedAddress>
    where TUser : IUser
    where TOrganization : IOrganization<TLinkedAddress>
    where TLinkedAddress : ILinkedAddress
{
    long ID { get; set; }
    long UserID { get; set; }
    long InstituteID { get; set; }
    long InstituteLocationID { get; set; }
    string Degree { get; set; }
    string Major { get; set; }
    string Grade { get; set; }
    DateTime StartDate { get; set; }
    DateTime? EndDate { get; set; }
    string Socities { get; set; }
    string Description { get; set; }

    [ForeignKey(name: "UserID")]
    TUser User { get; set; }
    [ForeignKey(name: "InstituteID")]
    TOrganization Institute { get; set; }
    [ForeignKey(name: "InstituteLocationID")]
    TLinkedAddress InstituteLocation { get; set; }
}

默认实施

LinkedAddress.cs

[Table(name: "LinkedAddress", Schema = "Arinsys_CRM")]
public class LinkedAddress : DBEntity<LinkedAddress>, ILinkedAddress
{
    public long ID { get; set; }
    public string Address { get; set; }
    public long LocationID { get; set; }
    public string AddressType { get; set; }
    public string Contact_Person_Name { get; set; }
    public string Contact_Person_Number { get; set; }
    public string Contact_Person_Relation { get; set; }
    [ForeignKey("OrganizationID")]
    public long? OrganizationID { get; set; }
    [ForeignKey("UserID")]
    public long? UserID { get; set; }
}

Organization.cs

[Table(name: "Organization", Schema = "Arinsys_CRM")]
public partial class Organization : DataContext.DBEntity<Organization>,
    IOrganization<LinkedAddress>
{
    public long ID { get; set; }
    public string Name { get; set; }
    public string Brand { get; set; }
    public string Genre { get; set; }
    public string Industry { get; set; }
    public string Master_Security_Stamp { get; set; }
    public long? Control_Branch_ID { get; set; }

    [ForeignKey("Control_Branch_ID")]
    public virtual LinkedAddress Control_Branch { get; set; }

    public virtual IList<LinkedAddress> Branches { get; set; }
}

Education.cs

[Table(name: "Education", Schema = "Arinsys_CRM")]
public class Education : IEducation<User, Organization, LinkedAddress>
{
    public long ID { get; set; }
    public long UserID { get; set; }
    public long InstituteID { get; set; }
    public long InstituteLocationID { get; set; }
    public string Degree { get; set; }
    public string Major { get; set; }
    public string Grade { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public string Socities { get; set; }
    public string Description { get; set; }

    [ForeignKey(name: "UserID")]
    public virtual User User { get; set; }
    [ForeignKey(name: "InstituteID")]
    public virtual Organization Institute { get; set; }
    [ForeignKey(name: "InstituteLocationID")]
    public virtual LinkedAddress InstituteLocation { get; set; }
}

现在的问题是,是否可以在InstitueLocationID接口的IEducation上设置约束,它必须是由Organization定义的InstituteID的分支,否则我会没有其他选择,除了让最终的开发人员自己设置约束

编辑:进一步研究

我开始研究@Ivan Starostin所建议的传递依赖性,并且@Jeff Atwood(联合创始人(Emeritus)和Stack Overflow的共同创建者)发表了关于他们在Stack Overflow中遇到的问题的精彩博客文章。 2008

  

Maybe Normalizing Isn't Normal

博客文章的摘要是,规范化不是一个灵丹妙药,有时非规范化数据实际上会带来更好的生产力和性能,尽管该博文没有回答确切的问题,但它帮助我获得了更深刻的理解,所以在这里张贴给未来的读者。

2 个答案:

答案 0 :(得分:1)

这是一个理论上的问题。 InstituteID不是Education实体的属性 - 它是LinkedLocation实体的属性。所以你在Education实体中有一个传递依赖,这意味着它不是第三种正常形式:InstituteID依赖于InstituteLocationID。

所以你必须将InstituteID视为没有关系约束的非规范化数据或者从Education实体中删除它。

此外,我认为麻烦来自LinkedAddress实体 - 它是地址实体和附加链接实体的混合,它应该在地址和组织之间存储链接。因此,如果您从LinkedAddress中提取实体地址并使LinkedAddress成为存储AddressID和OrganizationID元组的真实 链接实体 - 这可能会使模型更加清晰,并且存在InstituteLocationID属性将足以确定与编辑实体数据相关的所有内容。

答案 1 :(得分:1)

  

现在的问题是,是否可以设置约束   IEducation接口中的InstitueLocationID必须是它的一个分支   由InstituteID定义的组织

EF对接口一无所知,但只关心课程。实际上不可能指定暗示接口的配置。 Fluent API和Data Annotation配置仅适用于类。您不能为从IEducation派生的所有类创建约束,但只能在实现此接口的类上创建约束。

  除了离开最终的开发者之外,我别无选择   自己约束

以ASP.Net Identity为例。要使用零配置来处理此API,Microsoft会为我们可能需要的每个类提供默认实现:IdentityUserRole等...这些默认类实现IUser,{{1}等等......

如果默认实施不能满足您的需求,您有两种选择:

  • 创建派生自默认实现的新类
  • 创建实现API提供的接口的新类(由默认类实现)

如果选择后一个选项,则EF配置留给您。您必须对所有配置(使用Fluent API或数据注释)进行编码,以使其他类可以与您的新类一起使用。我的意思是实现IRoleIUSerStoreIUserPasswordStore等的类...如果你甚至不想使用EF这个API让你实现所有IUserLoginStore接口等...

我的答案是创建所有接口的默认实现,并对这些类设置约束。如果开发人员想要扩展您的API,他可以继承您的默认类,或者他可以通过实现API接口创建新类,并且必须创建所需的所有约束,以使他的新类与其余API一起正常工作。