Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to declare one to one relationship using Entity Framework 4 Code First (POCO)

How to declare a one to one relationship using Entity Framework 4 Code First (POCO)?

I found this question (one-to-one relationships in Entity Framework 4) , but the article that the answer references was not useful (there is one line of code that is a 1-1 relationship, but no mention of how to define it).

like image 728
Paul Hiles Avatar asked Sep 01 '10 22:09

Paul Hiles


2 Answers

Three methods:

A) Declare both classes with navigation properties to each other. Mark one of the tables (the dependent table) with the ForeignKey attribute on its Primary Key. EF infers 1-to-1 from this:

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

I didn't use virtual and you shouldn't either.*

B) Declare an inheritance hierarchy with both table names explicitly stated, resulting in Table-Per-Type and a shared Primary Key.

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; }
}

You'll get 2 tables: One for AppUser, one for AdminUser. AdminUser is 1:1 with AppUser and is Dependent - meaning you can delete an AdminUser, but if you delete an AppUser when an AdminUser is still pointing at it, you'll get a Constraint Violation Error. ​

C) There are 2 half-way methods of doing one-to-one in EF:

Entity-Splitting, where you have a single Class, but it's stored in a primary table, and 1 or more one-to-one related tables.

Table-Splitting, where a tree of objects flattens out into a single table. For example a class with an Address property would have columns for the Address object, like Address_City, flattened into a single table.

*You can include virtual on any EF Property or Collections if you want to lazy-load them. This can result in infinite loops or loading the entire DB if you pass an object with lazy-loaded properties to, for example, the MVC JSON converter or anything else that walks the object hierarchy. The Lazy-Loading is always done Synchronously, blocking the thread, and without any notice. To summarize, the list of ways you can freeze your code, app or server with it is long. Avoid using virtual on EF classes. Yes there are a lot of code samples on the internet that use it. No, you still should not use it.

like image 113
Chris Moschini Avatar answered Oct 18 '22 19:10

Chris Moschini


Are you just looking for something like this?

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);
    }
}

EDIT: Yeah I didn't have VS in front of me but you need to add the following line in the UserMapping instead of the current HasRequired and also add a ProfileId property (instead of Profile_Id that you added):

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

I currently don't think there's a way around this, but I'm sure it'll change since we're only in CTP4. It'd be nice if I could say:

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

This way I wouldn't have to include a ProfileId property. Maybe there is a way around this currently and it's still to early in the morning for me to think :).

Also remember to call .Include("Profile") if you want to include a "navigational property".

like image 35
TheCloudlessSky Avatar answered Oct 18 '22 19:10

TheCloudlessSky