Padel
Padel

Reputation: 8957

Setting up optional:required relationship Entity Framework - fluent API

I have a USER - PROFILE relation where the user can (optional) have a profile associated. In the database the PROFILES table has a FK to the USERS table (the FK is nullable).

I want to setup this with EF fluent API. I managed to do it and everything works ok but the problem is i cannot have the FK from the profile as a property in the classes.

So in the DB i have:

[USERS]
ID uniqueidentifier PK not null

[PROFILES]
ID uniqueidentifier PK not null
USERID uniqueidentifer FK null

And in the code i have:

public class User { public virtual Profile { get; set; } }
public class Profile { public virtual User { get; set; } }

Note that the column names in the DB does not go along with EF naming convention.

I can configure EF as follows:

HasOptional(u => u.Profile).WithRequired(p => p.User).Map(m => m.MapKey("USERID")).WillCascadeOnDelete();

or the other way around:

HasRequired(p => p.User).WithOptional(u => u.Profile).Map(m => m.MapKey("USERID")).WillCascadeOnDelete();

I really need in this case to map the FK using the MapKey (because of the naming convention EF uses) and this seems to be the problem that won't let me add the FK in the model also.

So i cannot have:

public class Profile { public virtual User { get; set; } public Guid? UsreId { get; set; } }

Anyone has an idea how can i accomplish this?

EDIT The problem source is the fact that if I don't use .Map(m => m.MapKey("USERID")) method the convention breaks the relationship (it expects that the FK is User_Id) which basically is like using it without any params. If i use .Map(m => m.MapKey("USERID")) method then it says that i already have the UserId as a property in the model (from inline help: Configures the relationship to use foreign key property(s) that are not exposed in the object model. The column(s) and table can be customized by specifying a configuration action. If an empty configuration action is specified then column name(s) will be generated by convention).

Bummer...

EDIT 2 Managed to fix it somehow (basically going for another approach). Changed the tables to:

[USERS]
ID uniqueidentifier PK not null

[PROFILES]
-- ID uniqueidentifier PK not null (removed the PK)
USERID uniqueidentifer PK not null, FK not null -- (the PK is also the FK)

I ended up here after running the SQL profiler and looking at the queries ran there. The optionl PROFILE for each user was queried based on USERID. The were clause was something like:

SELECT *
FROM PROFILES
WHERE ID = @userId -- ID was the PK

So i figured out that the PK from PROFILES table must also be the FK in this case, and the mappings can be kept simple:

HasOptional(u => u.Profile).WithRequired(p => p.User);

or on the other end

HasRequired(p => p.User).WithOptional(u => u.Profile);

And this one was helpfull: http://msdn.microsoft.com/en-us/library/hh295843%28v=vs.103%29.aspx

Cheers!

Upvotes: 3

Views: 3726

Answers (1)

Cosmin Onea
Cosmin Onea

Reputation: 2738

You need to focus on mapping the entities themselves properly and then relations will just work. The conventions about keys are something along:

  1. If the entity has a property called Id it will be used as key
  2. If there is an EntityId (e.g. UserId) property then that is the key
  3. You need to specify what the key property is otherwise

As to what column that key property corresponds to in the database, you can specify that using the Property() and HasColumnName() methods.

Once this is done you don't have to worry about mapping keys in the relations mapping(well at least in your case one-to-one mapping).

As to relationship mapping you don't have to define the relations both ways from User and from Profile. Once you define the relationship one way the other way is inferred. This keeps your code clean as well.

The code below should help you understand the whole thing.

class Program
{
    static void Main(string[] args)
    {
        // replace your connection string here
        using (var userContext = new UserContext("UserProfile"))
        {
            var u = new User { UserId = Guid.NewGuid(), Name = "John", Profile = new Profile() { ProfileId = Guid.NewGuid(), SomeValue = "x" } };

            userContext.Users.Add(u);
            userContext.SaveChanges();

            var users = userContext.Users.ToList();

            foreach (var user in users)
            {
                Console.WriteLine(user.Name);
                if (user.Profile != null)
                {
                    Console.WriteLine(user.Profile.ProfileId);
                }
            }

            var profiles = userContext.Profiles.Where(p => p.User.Name == "John").ToList();
        }
    }
}

public class UserContext : DbContext
{
    public UserContext(string connectionString)
        : base(connectionString)
    {
    }

    public DbSet<User> Users { get; set; }

    public DbSet<Profile> Profiles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<User>().Property(u => u.UserId).HasColumnName("ID");
        modelBuilder.Entity<Profile>().Property(p => p.ProfileId).HasColumnName("ID");

        modelBuilder.Entity<User>().HasOptional(u => u.Profile).WithRequired(p => p.User);
    }
}

public class User
{
    public Guid UserId { get; set; }

    public string Name { get; set; }

    public Profile Profile { get; set; }
}

public class Profile
{
    public Guid ProfileId { get; set; }

    public User User { get; set; }

    public string SomeValue { get; set; }
}

Upvotes: 2

Related Questions