dizar47 dizar47 - 21 days ago 6
C# Question

Entity Framework: One to zero relationship

I've got

public class MyUser : IdentityUser<int,MyLogin,MyUserRole,MyUserClaim>
{
public virtual MyUserProfile Profile { get; set; }
}

public class MyUserProfile
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}


My implementation of IdentityUser has INT PK. I want to have one navigation property in MyUser and don't have on the other side. I prefer data annotations. Is it possible to use IdentityUser PK for navigating? (EF v 6.1.3)

UPD #1.
Dependent type - MyUserProfile. And it DOES NOT have to contain navigation property of MyUser type.

Answer

I'd question why it is you're doing this, a better solution may be to keep it on a single table as it will be faster unless they are both massive entities and the profile is optional (but even then, you could have a separate column stating that the extra data is or isn't available).

Since you seem to be asking for a 1:0..1 relationship you can implement it as follows. I know you wanted Data Annotations, but I'd really recommend being explicit here*, so that any changes to your model won't cause a potentially unintended knock-on effect.

public class MyUser : IdentityUser<int,MyLogin,MyUserRole,MyUserClaim>
{
     public virtual MyUserProfile Profile { get; set; }
}

public class MyUserProfile
{
     public int Id { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }

     public MyUser User { get; set; }
}

On your DbContext, override OnModelCreating with the following:

modelBuilder.Entity<MyUserProfile>()
            .HasRequired(m => m.User)
            .WithOptional(m => m.Profile);

** Full disclosure: I also always prefer Data Annotations over the Fluent Configuration Syntax, but I haven't avoided it to date and there's really no downside to it. For something as fundamental as this, you don't want it to be affected by a slight change in attribute.

This generates the following schema that keeps things lean as the IDs are synchronised between the two tables as we'd expect:

        CreateTable(
            "dbo.MyUsers",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Test = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.MyUserProfiles",
            c => new
                {
                    Id = c.Int(nullable: false),
                    Test = c.String(),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.MyUsers", t => t.Id)
            .Index(t => t.Id);

As @grek40 mentioned, it can be done without the navigation property on the other end by flipping the fluent configuration of the relationship (see the comment for more info), but this generates the following schema where the IDs can get out of sync:

        CreateTable(
            "dbo.MyUsers",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Test = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.MyUserProfiles",
            c => new
                {
                    Id = c.Int(nullable: false),
                    MyUser_Id = c.Int(nullable: false),
                    FirstName = c.String(),
                    LastName = c.String(),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.MyUsers", t => t.Id)
            .ForeignKey("dbo.MyUsers", t => t.MyUser_Id)
            .Index(t => t.Id)
            .Index(t => t.MyUser_Id);

With that said, both schemas are valid and appear to work in my quick testing.