【问题标题】:How to declare one to one relationship using Entity Framework 4 Code First (POCO)如何使用 Entity Framework 4 Code First (POCO) 声明一对一关系
【发布时间】:2011-04-07 01:25:25
【问题描述】:

如何使用 Entity Framework 4 Code First (POCO) 声明一对一关系?

我找到了this question (one-to-one relationships in Entity Framework 4) ,但是答案引用的那篇文章没有用(有一行代码是1-1的关系,但没有提到如何定义)。

【问题讨论】:

    标签: c# entity-framework ef-code-first poco one-to-one


    【解决方案1】:

    你只是在寻找这样的东西吗?

    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public Profile Profile { get; set; }
        public int ProfileId { get; set; }
    }
    
    public class Profile
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string PostalCode { get; set; }
        // etc...
    }
    
    public class UserMapping : EntityConfiguration<User>
    {
        public UserMapping()
        {
            this.HasKey(u => u.Id);
            this.Property(u => u.Username).HasMaxLength(32);
    
            // User has ONE profile.
            this.HasRequired(u => u.Profile);
        }
    }
    
    public class ProfileMapping : EntityConfiguration<Profile>
    {
        public ProfileMapping()
        {
            this.HasKey(p => p.Id);
            this.Property(p => p.FirstName).HasMaxLength(32);
            this.Property(p => p.LastName).HasMaxLength(32);
            this.Property(p => p.PostalCode).HasMaxLength(6);
        }
    }
    

    编辑:是的,我面前没有 VS,但您需要在 UserMapping 中添加以下行而不是当前的 HasRequired 并添加 ProfileId属性(而不是您添加的 Profile_Id):

    this.HasRequired(u => u.Profile).HasConstraint((u, p) => u.ProfileId == p.Id);
    

    我目前认为没有办法解决这个问题,但我相信它会改变,因为我们只在 CTP4 中。如果我能说,那就太好了:

    this.HasRequired(u => u.Profile).WithSingle().Map(
        new StoreForeignKeyName("ProfileId"));
    

    这样我就不必包含ProfileId 属性。也许目前有一种方法可以解决这个问题,但我想现在还很早:)。

    如果您想包含“导航属性”,请记得致电.Include("Profile")。

    【讨论】:

    • 这对你有用吗?我无法让它工作。我没有自动生成数据库,因为我有现有的表。我的个人资料表的 ID 为 PK 和 FK(因为关系是 1:1)。我认为这和你一样。如果我尝试运行您的代码,则会收到错误消息:列名“Profile_Id”无效。如果我将该列添加到用户(不是我想要的),则返回用户但配置文件为空。生成的sql是:SELECT [Extent1].[Id] AS [Id], [Extent1].[Username] AS [Username], [Extent1].[Profile_Id] AS [Profile_Id] FROM [dbo].[Users] AS [Extent1] 有什么想法吗?
    • 嗯。在第二次检查时,看起来这在 sqlce4 上运行良好,但不是 sql server 2010,除非我遗漏了什么。
    • @zaph0d - 如果要访问“另一个实体”的属性,则需要“包含”它。这样做的方法是context.Users.Include("Profile")。 Profile 是一个“导航属性”,需要在 SQL 中进行 JOIN。我用我忘记的个人资料的额外信息编辑了我的帖子。
    • @zaph0d - 我打算建议你在 MSDN 论坛上发布这个,看看是否有更好的方法,但我看到你已经做到了:)。也许你也应该链接回这个 SO 帖子。
    • 这种方法在发布版本中不是必需的,而且比它需要的更复杂和脆弱。看我的回答。
    【解决方案2】:
    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
    
        public virtual Profile Profile { get; set; }
    }
    
    public class Profile
    {
        public int Id { get; set; }
    
        public int UserID { get; set; }
    
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string PostalCode { get; set; }
    }
    

    添加虚拟个人资料和用户 ID,我认为这应该可以帮助您。

    【讨论】:

    • 这不是一对多的关系吗?问题大约是 1:1。
    • 不,不是。这是 1:1 在这个例子中一个用户有 1 个个人资料......如果不是我完全误解了一些东西。
    【解决方案3】:

    三种方法:

    A) 相互声明两个具有导航属性的类。在其主键上使用 ForeignKey 属性标记其中一个表(从属表)。 EF 从这里推断出 1 对 1:

    public class AppUser
    {
        public int Id { get; set; }
    
        public string Username { get; set; }
    
        public OpenIdInfo OpenIdInfo { get; set; }
    }
    
    ​public class OpenIdInfo
    {
        [ForeignKey("AppUser")]
        public int Id { get; set; }
    
        public string OpenId { get; set; }
    
        public AppUser AppUser { get; set; }
    }
    

    http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx

    我没有使用virtual,你也不应该使用。*

    B) 声明一个继承层次结构,同时明确声明两个表名,从而产生 Table-Per-Type 和共享主键。

    using System.ComponentModel.DataAnnotations;
    
    [Table("AppUser")]
    public class AppUser
    {
        public int Id { get; set; }
    
        public string Username { get; set; }
    
        public OpenIdInfo OpenIdInfo { get; set; }
    }
    
    [Table("AdminUser")]      
    public class AdminUser : AppUser
    {
        public bool SuperAdmin { get; set; }
    }
    

    您将获得 2 个表:一个用于 AppUser,一个用于 AdminUser。 AdminUser 与 AppUser 是 1:1 并且是 Dependent - 这意味着您可以删除 AdminUser,但是如果您在 AdminUser 仍然指向它时删除 AppUser,您将收到约束违规错误。 ​

    C) EF中做一对一的中途方法有2种:

    Entity-Splitting,你有一个类,但它存储在一个主表和 1 个或多个一对一相关的表中。

    Table-Splitting,其中一棵对象树展开成一个表。例如,具有 Address 属性的类会将 Address 对象的列(如 Address_City)扁平化为单个表。

    *您可以在任何 EF 属性或集合if you want to lazy-load them 中包含 virtual。如果将具有延迟加载属性的对象传递给例如 MVC JSON 转换器或遍历对象层次结构的任何其他对象,这可能会导致无限循环或加载整个数据库。延迟加载总是同步完成,阻塞线程,没有任何通知。总而言之,可以使用它冻结代码、应用程序或服务器的方法列表很长。避免在 EF 类上使用 virtual。是的,互联网上有很多使用它的代码示例。不,你仍然不应该使用它。

    【讨论】:

    • 为什么不使用“虚拟”关键字
    • @AminM 请参阅最后关于延迟加载和虚拟的注释。
    【解决方案4】:

    以以下 Student 和 StudentAddress 实体为例。
    使用 DataAnnotations 配置一对零或一关系:

    public class Student
    {
        public Student() { }
    
        public int StudentId { get; set; }
        public string StudentName { get; set; }
    
        public virtual StudentAddress Address { get; set; }
    
    }
    
    public class StudentAddress 
    {
        [ForeignKey("Student")]
        public int StudentAddressId { get; set; }
    
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string City { get; set; }
        public int Zipcode { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    
        public virtual Student Student { get; set; }
    }
    

    当 StudentAddress 实体不遵循约定时:

    例如,如果 StudentAddress 实体不遵循 PK 的约定,即 Id 属性的不同名称,那么您还需要为 PK 配置它。考虑以下具有属性名称 StudentId 而不是 StudentAddressId 的 StudentAddress 实体。

    public class Student
    {
        public Student() { }
    
        public int StudentId { get; set; }
        public string StudentName { get; set; }
    
        public virtual StudentAddress Address { get; set; }
    
    }
    
    public class StudentAddress 
    {
        [Key, ForeignKey("Student")]
        public int StudentId { get; set; }
    
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string City { get; set; }
        public int Zipcode { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    
        public virtual Student Student { get; set; }
    }
    

    在上面的示例中,我们需要将 StudentId 属性配置为 Key 以及 ForeignKey。这将使 StudentAddress 实体中的 StudentId 属性成为 PK 和 FK。

    使用 Fluent API 配置一对零或一关系:
    当 Student 和 StudentAddress 遵循约定时: Student 和 StudentAddress 实体遵循 PrimaryKey 的默认代码优先约定。因此,我们不需要配置它们来定义它们的 PrimaryKey。我们只需要配置StudentAddress实体,StudentAddressId应该是ForeignKey。

    以下示例使用 Fluent API 在 Student 和 StudentAddress 之间设置一对零或一对一的关系。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    
        // Configure Student & StudentAddress entity
        modelBuilder.Entity<Student>()
                    .HasOptional(s => s.Address) // Mark Address property optional in Student entity
                    .WithRequired(ad => ad.Student); // mark Student property as required in StudentAddress entity. Cannot save StudentAddress without Student
    
    }
    

    在上面的示例中,Student 实体是使用 HasOptional() 方法配置的,这表明 Student 实体中的 StudentAddress 导航属性是可选的(保存 Student 实体时不需要)。然后,WithRequired() 方法配置StudentAddress 实体,并根据需要设置StudentAddress 的Student 导航属性(保存StudentAddress 实体时需要,在没有Student 导航属性的情况下保存StudentAddress 实体时会抛出异常)。这将使 StudentAddressId 也成为 ForeignKey。

    因此,您可以在两个实体之间配置一对零或一关系,其中可以在不附加 StudentAddress 对象的情况下保存 Student 实体,但如果不附加 Student 实体对象,则无法保存 StudentAddress 实体。这就需要一个末端。

    当 StudentAddress 实体不遵循约定时:
    现在,让我们以 StudentAddress 实体为例,它不遵循主键约定,即具有与 Id 不同的 Id 属性名称。考虑以下 Student 和 StudentAddress 实体。

    public class Student
    {
        public Student() { }
    
        public int StudentId { get; set; }
        public string StudentName { get; set; }
    
        public virtual StudentAddress Address { get; set; }
    
    }
    
    public class StudentAddress 
    {
        public int StudentId { get; set; }
    
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string City { get; set; }
        public int Zipcode { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    
        public virtual Student Student { get; set; }
    }
    

    所以现在,我们需要为 StudentAddress 的 PrimaryKey 以及 ForeignKey 配置 StudentAddress 的 StudentId 属性,如下所示。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure StudentId as PK for StudentAddress
        modelBuilder.Entity<StudentAddress>()
            .HasKey(e => e.StudentId);
    
        // Configure StudentId as FK for StudentAddress
        modelBuilder.Entity<Student>()
                    .HasOptional(s => s.Address) 
                    .WithRequired(ad => ad.Student); 
    
    }
    

    使用 Fluent API 配置一对一关系:
    我们可以使用 Fluent API 配置实体之间的一对一关系,其中需要两端,这意味着 Student 实体对象必须包含 StudentAddress 实体对象,并且 StudentAddress 实体必须包含 Student 实体对象才能保存。

    注意:一对一的关系在 MS SQL Server 中技术上是不可能的。它总是一对零或一。 EF 在不在 DB 中的实体上形成一对一关系。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure StudentId as PK for StudentAddress
        modelBuilder.Entity<StudentAddress>()
            .HasKey(e => e.StudentId);
    
        // Configure StudentId as FK for StudentAddress
        modelBuilder.Entity<Student>()
                    .HasRequired(s => s.Address) 
                    .WithRequiredPrincipal(ad => ad.Student); 
    
    }
    

    在上面的例子中,modelBuilder.Entity().HasRequired(s => s.Address) 使得 StudentAddress 的 Address 属性是必需的。 .WithRequiredPrincipal(ad => ad.Student) 根据需要生成 StudentAddress 实体的 Student 属性。因此,它配置了所需的两端。所以现在,当你尝试保存不带地址的 Student 实体或不带 Student 的 StudentAddress 实体时,它会抛出异常。

    参考:http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-01
      • 2013-01-12
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      • 2012-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多