четверг, 23 августа 2012 г.

Entity Framework 4.1 – One To One Mapping


In the snippet below you can see that a customer has an address property.
    public class Customer
    {
        public Customer()
        {
           Address = new Address();
        }
 
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Address Address { get; set; }
        public Guid AddressId { get; set; }
    }
 
    public class Address
    {
        public Guid Id { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Street { get; set; }
    }
The database that gets generated though defines this as a one to many relationship.
Which is not what you’re expecting, or is it? I haven’t defined any restrictions on the address class so indeed an address could be shared between multiple customers. Let’s change that.
    public class Customer
    {
        public Customer()
        {
           Address = new Address();
        }
 
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Address Address { get; set; }
 
    }
 
    public class Address
    {
        public Guid Id { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Street { get; set; }
        [Required]
        public Customer Customer { get; set; }
    }
I’ve added a navigation property from Address to Customer and marked the Customer property as required. This basically is telling EF that there is a one to one mapping between the two and the primary key of the customer should be used in the relationship. The primary key of the customer will also become the primary key of the address. If you did not add the required attribute you’ll get an invalidoperationexception saying that it’s unable to determine the principal end of an association.
You can also use the fluent api.
    public  class  CustomerContext
        : DbContext
    {
        public IDbSet<Customer> Customers { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Address>().HasRequired(x => x.Customer);
            base.OnModelCreating(modelBuilder);
        }
 
    }
They both result in the same database structure.
What if both ends are required? A customer always has an address and an address always has a customer. Marking both ends as required will again result in the same exception as before.
In order to model this you have to use the fluent api and there are two ways to achieve the desired result.
    public  class  CustomerContext
        : DbContext
    {
        public IDbSet<Customer> Customers { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Address>()
                .HasRequired(x => x.Customer)
                .WithRequiredDependent(x => x.Address);
            base.OnModelCreating(modelBuilder);
        }
    }
I’m saying here that the address entity has a required customer property and that the customer class is actually in charge, MSDN reference.
Resulting database structure:
Another way to achieve the same result:
    public  class  CustomerContext
        : DbContext
    {
        public IDbSet<Customer> Customers { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Address>()
                .HasRequired(x => x.Customer)
                .WithRequiredDependent();
            modelBuilder.Entity<Customer>()
                .HasRequired(x => x.Address)
                .WithRequiredPrincipal();
            base.OnModelCreating(modelBuilder);
        }
    }
MSDN link on WithRequiredPrincipal.
You saw that the WithRequiredPrincipal and Depedant actually have one taking a lambda and one with no arguments. This allows you to exclude a navigation property and still get a proper one to one mapping. Which brings me back to my starting point.
public class Customer
    {
        public Customer()
        {
           Address = new Address();
        }
 
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Address Address { get; set; }
    }
 
    public class Address
    {
        public Guid Id { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Street { get; set; }
 
 
    }
 
    public  class  CustomerContext
        : DbContext
    {
        public IDbSet<Customer> Customers { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Customer>()
                .HasRequired(x => x.Address)
                .WithRequiredPrincipal();
            base.OnModelCreating(modelBuilder);
        }
    }

Комментариев нет: