How to declare a one to one relationship using Entity Framework 4 Code First (POCO)?
I found this questio开发者_C百科n (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).
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.
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".
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; }
}
Add the virtual Profile and the UserID and I think that should get you there.
Take an example of the following Student and StudentAddress entities.
Configure one-to-zero-or-one relationship using 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; }
}
When StudentAddress entity does not follow conventions:
If, for example, StudentAddress entity does not follow the convention for PK i.e. different name for Id property then you need to configure it for PK as well. Consider the following StudentAddress entity which has property name StudentId instead of StudentAddressId.
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; }
}
In the above example, we need to configure StudentId property as Key as well as ForeignKey. This will make StudentId property in StudentAddress entity as PK and FK both.
Configure One-to-Zero-or-One relationship using Fluent API:
When Student and StudentAddress follow the conventions:
Student and StudentAddress entities follow the default code-first convention for PrimaryKey. So, we don't need to configure them to define their PrimaryKeys. We only need to configure StudentAddress entity where StudentAddressId should be ForeignKey.
The following example sets one-to-zero or one relationship between Student and StudentAddress using Fluent API.
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
}
In the above example, Student entity is configured using HasOptional() method which indicates that the StudentAddress navigation property in Student entity is an optional (not required when saving Student entity). Then, WithRequired() method configures StudentAddress entity and make Student navigation property of StudentAddress as required (required when saving StudentAddress entity. It will throw an exception when StudentAddress entity is saving without Student navigation property). This will make StudentAddressId as ForeignKey also.
Thus, you can configure One-to-Zero-or-one relationship between two entities where Student entity can be saved without attaching StudentAddress object to it but StudentAddress entity cannot be saved without attaching an object of Student entity. This makes one end required.
When StudentAddress entity do not follow conventions:
Now, let's take an example of StudentAddress entity where it does not follow primary key convention i.e. have different Id property name than Id. Consider the following Student and StudentAddress entities.
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; }
}
So now, we need to configure StudentId property of StudentAddress for PrimaryKey of StudentAddress as well as ForeignKey as shown below.
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);
}
Configure One-to-One relationship using Fluent API:
We can configure One-to-One relationship between entities using Fluent API where both ends are required, meaning Student entity object must include StudentAddress entity object and StudentAddress entity must include Student entity object in order to save it.
Note:One-to-one relationship is technically not possible in MS SQL Server. It will always be one-to-zero or one. EF forms One-to-One relationships on entities not in 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);
}
In the above example, modelBuilder.Entity().HasRequired(s => s.Address) makes Address property of StudentAddress is required. .WithRequiredPrincipal(ad => ad.Student) makes Student property of StudentAddress entity as required. Thus it configures both ends required. So now, when you try to save Student entity without address or StudentAddress entity without Student, it will throw an exception.
Reference :http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx
精彩评论