Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Code First Mapping Foreign Key Using Fluent API

I have the situation where a User can have several addresses. Accordingly, I have an ICollection on my user class. But I also want the user to be able to choose a default address. So I've done the following:

public class User 
{
    public int Id { get; set; }
    public int? DefaultAddressId { get; set; }
    [ForeignKey("DefaultAddressId")]
    public virtual Address DefaultAddress { get; set; }
    public virtual ICollection<Address> Addresses { get; set; }
    //properties were removed for purpose of this post
}

I would like to remove the public virtual Address DefaultAddress { get; set; } altogether, keep the DefaultAddressId and map it using the Fluent API instead because the current setup is causing a lot of trouble (in this and other classes where I have a similar setup). So can this be done using the fluent api?

UPDATE: The address class currently doesn't have any reference to the User class, it's a uni-directional relationship. But yes, an address belongs to only ONE user, it's not a many to many relationship. Here's the address class:

public class Address
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Details { get; set; }
    public virtual Area Area { get; set; }
}
like image 393
Kassem Avatar asked Mar 30 '11 17:03

Kassem


People also ask

How do I add a foreign key in Fluent API?

You can then configure foreign key properties by using the HasForeignKey method. This method takes a lambda expression that represents the property to be used as the foreign key.

How do I create a foreign key in Entity Framework Code First?

To create Foreign Key, you need to use ForeignKey attribute with specifying the name of the property as parameter. You also need to specify the name of the table which is going to participate in relationship.

How do you configure a primary key for an entity in Entity Framework Fluent API?

Configuring a primary key By convention, a property named Id or <type name>Id will be configured as the primary key of an entity. Owned entity types use different rules to define keys. You can configure a single property to be the primary key of an entity as follows: Data Annotations.

How do you set a foreign key in an entity?

The [ForeignKey(name)] attribute can be applied in three ways: [ForeignKey(NavigationPropertyName)] on the foreign key scalar property in the dependent entity. [ForeignKey(ForeignKeyPropertyName)] on the related reference navigation property in the dependent entity.


2 Answers

I would personally move the Foreign Key relation from User to Address, and add an IsDefaultAddress property on the address class.

public class Address
{
    public int Id { get; set; }

    // This property marks the FK relation
    public virtual User User { get; set; }

    public string Name { get; set; }
    public string Details { get; set; }
    public virtual Area Area { get; set; }

    // This property signals whether this is the user's default address
    public bool IsDefaultAddress { get; set; }
}

EF will know that it needs a Foreign Key relation between Address and User.

This would simplify your model a great deal. That is, of course, if an address can only belong to one user (as asked by Slauma in the comments).

like image 109
Sergi Papaseit Avatar answered Sep 22 '22 10:09

Sergi Papaseit


Your original model in the question should work. You can test it quite easily:

  • Create new console application (VS 2010)
  • Name it "EFTestApp"
  • Add reference to "EntityFramework.dll"
  • Delete content of Program.cs and copy the following code into the file

Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace EFTestApp
{
    public class User
    {
        public int Id { get; set; }
        public int? DefaultAddressId { get; set; }
        [ForeignKey("DefaultAddressId")]
        public virtual Address DefaultAddress { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }

    public class Address
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Context : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Address> Addresses { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Context())
            {
                try
                {
                    User user = new User() { Addresses = new List<Address>() };

                    Address address1 = new Address() { Name = "Address1" };
                    Address address2 = new Address() { Name = "Address2" };

                    user.Addresses.Add(address1);
                    user.Addresses.Add(address2);

                    context.Users.Add(user);

                    context.SaveChanges();
                    // user has now 2 addresses in the DB and no DefaultAddress

                    user.DefaultAddress = address1;
                    context.SaveChanges();
                    // user has now address1 as DefaultAddress

                    user.DefaultAddress = address2;
                    context.SaveChanges();
                    // user has now address2 as DefaultAddress

                    user.DefaultAddress = null;
                    context.SaveChanges();
                    // user has now no DefaultAddress again
                }
                catch (Exception e)
                {
                    throw;
                }
            }
        }
    }
}

In SQL Server Express it creates a new DB called "EFTestApp.Context". You can set breakpoints on every SaveChanges above, step over and watch the changes in the DB.

If you look at the relationships in the database then there are two, and in table Addresses in the DB is a foreign key column User_Id.

I think you could also remove public int? DefaultAddressId { get; set; } and [ForeignKey("DefaultAddressId")]. It creates the same database tables and relationships with an optional DefaultAddress.

Perhaps you want the relationship Address -> User as required (Addresses cannot live alone in the DB without a User). Then you can add this to the Context class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
                .HasMany(u => u.Addresses)
                .WithRequired();
}

It makes User_Id in the Addresses table non nullable and sets up cascading delete by default. So, when a user gets deleted all its addresses get deleted as well.

like image 33
Slauma Avatar answered Sep 18 '22 10:09

Slauma