Search code examples
c#entity-frameworkef-code-firstef-code-first-mapping

EF Code first, how can you map a bidirectional one to one or zero relationship in EF?


I have two classes a User and an Image table:

public class User
{
    public Guid? ImageId {get; set;}
    public Image Image {get; set;}
}

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

Both the user and the image can exist on their own without the other entity, but if they do have a relation, a user can only be associated with one image, and an image can only have user, if they are not null. How do I map this? Currently I have:

public UserMapping()
    {            
        HasOptional(x => x.ProfileImage).WithOptionalPrincipal(x => 
        x.User).Map(x => x.MapKey("UserId"));
    }

And nothing on the ImageMapping since from other answers it was stated don't map the relationship twice or it freaks out. However, the migration file ends up generating an additional User_Id property on the Image table then:

CreateTable(
            "dbo.Images",
            c => new
                {
                    Id = c.Guid(nullable: false),
                    UserId = c.Guid(),
                    User_Id = c.Guid(),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Users", t => t.User_Id)
            .Index(t => t.User_Id);

Which is wrong. How can I do the mapping appropriately?

EDIT: I've also found thishttp://stackoverflow.com/questions/21082085/entity-framework-optional-1-to-1-relation-on-both-ends and tried what is shown in the original question that claims to work, but it doesn't, still creates User_Id.


Solution

  • Look at this page for more details.

    Basically a one to one relationship is that in which the PK of the principal entity passes as PK and FK in the dependent entity. As far I can see here you need to map two entities as optional between each other and that's not really possible in EF, at least not as One to Zero-Or-One.

    I understand that you want both sides to be optional but turns out that EF needs to know which one of your entities is the principal. So here is a way to change the relationships. I'd suggest you to define your principal entity in the relationship and the other become optional. E.g:

    User as the principal:

    //Your entities
    public class Image
    {
        public Guid UserId { get; set; }
    
        public virtual User User { get; set; }
    }
    
    public class User
    {
        public Guid UserId { get; set; }
        
        public virtual Image Image { get; set; }
    }
    
    //Mappings:
    public class ImageMappingConfiguration : EntityTypeConfiguration<Image>
        {
            public ImageMappingConfiguration()
            {
                HasKey(x => x.UserId);
            }
        }
    
     public class UserMappingConfiguration : EntityTypeConfiguration<User>
        {
            public UserMappingConfiguration()
            {
                HasKey(x => x.UserId);
    
                HasOptional(x => x.Image)
                    .WithRequired(x => x.User);
            }
        }
    

    You'll get this after add-migration:

    public override void Up()
    {
        CreateTable(
                "dbo.Images",
                c => new
                {
                    UserId = c.Guid(nullable: false)
                })
            .PrimaryKey(t => t.UserId)
            .ForeignKey("dbo.Users", t => t.UserId)
            .Index(t => t.UserId);
    
        CreateTable(
                "dbo.Users",
                c => new
                {
                    UserId = c.Guid(nullable: false)
                })
            .PrimaryKey(t => t.UserId);
    }
    

    See the primary key of User passing as PK and FK to Image? That's the way EF handles One to Zero-Or-One relations.

    After update-database:

    UPDATE! Two One-To-Many relationships with Unique Constraints.

    Let's give a try to this approach. Let me know if this works for you.

    public sealed class Image
    {
        public Image()
        {
            Users = new List<User>();
        }
    
        public Guid Id { get; set; }
    
        public Guid? UserId { get; set; }
    
        public List<User> Users { get; set; }
            
        public User User { get; set; }
    }
    
    public sealed class User
    {
        public User()
        {
            Images = new List<Image>();
        }
                
        public Guid Id { get; set; }
    
        public Guid? ImageId { get; set; }
    
        public List<Image> Images { get; set; }
    
        public Image Image { get; set; }
    }
    
    //Mappings:
    public class ImageMappingConfiguration : EntityTypeConfiguration<Image>
    {
        public ImageMappingConfiguration()
        {
            HasKey(x => x.Id);
    
            Property(x => x.UserId)
                .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute("IX_ImageMustBeUnique")
                {
                    IsUnique = true
                }));
    
    
            HasMany(x => x.Users)
                .WithOptional(x => x.Image)
                .HasForeignKey(x => x.ImageId)
                .WillCascadeOnDelete(true);
        }
    }
    
    public class UserMappingConfiguration : EntityTypeConfiguration<User>
    {
        public UserMappingConfiguration()
        {
            HasKey(x => x.Id);
    
            Property(x => x.ImageId)
                .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute("IX_UserMustBeUnique")
                {
                    IsUnique = true
                }));
    
            HasMany(x => x.Images)
                .WithOptional(x => x.User)
                .HasForeignKey(x => x.UserId);
        }
    }
    

    Usage:

    //test adding a user and an image.
    var user = new User
    {
        Id = Guid.NewGuid(),
    };
    var image = new Image
    {
        Id = Guid.NewGuid(),
    };
    
    using (var ctx = new Context())
    {
        ctx.Users.Add(user);
        ctx.Images.Add(image);
        ctx.SaveChanges();
    
        //associate them
        user.Images.Add(image);
        image.Users.Add(user);
        ctx.SaveChanges();
    
        //try to add a second image to the user
        var image2 = new Image
        {
            Id = Guid.NewGuid()
        };
    
        try
        {
            user.Images.Add(image2);
            ctx.SaveChanges();
        }
        catch (DbUpdateException ex)
        {
            Console.WriteLine(ex);
        }
    }