0

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.

SventoryMang
  • 10,275
  • 15
  • 70
  • 113
  • Not an answer to the but I believe if you name it User_Id when mapping the key it will get rid of the duplicate. Unless you directly create UserId in User I believe it defaults to User_Id – Ronan Oct 06 '16 at 18:15
  • I am doing that as seen above. And explicitly declaring that property as the key in the mapping. – SventoryMang Oct 06 '16 at 18:17
  • In the above code you are not creating a UserId field. When you leave it out entity framework creates User_Id instead. In mapping you then have UserId which doesn't match the name of the ef created one which is User_Id – Ronan Oct 06 '16 at 18:19
  • I don't know what you mean by "not creating". It's in the class, it's on the MapKey section, EF default conventions are smart enough to recognize a foreign key by the name of UserId instead of User_Id if it is manually supplied on the entity (see http://www.entityframeworktutorial.net/code-first/code-first-conventions.aspx). If I try and add Property(x => x.UserId) I don't get any options for relational stuff other than isoptional. Can you provide an example of properly creating it. – SventoryMang Oct 06 '16 at 18:46

1 Answers1

0

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);
    }
}
Community
  • 1
  • 1
Karel Tamayo
  • 3,690
  • 2
  • 24
  • 30
  • I marked your answer because it appears to be the closest thing, unfortunately I can't have one principal and one dependent, both can exist individually on their own. So seems like it's not possible just in normal DB setup anyway. – SventoryMang Oct 06 '16 at 22:00
  • Yeap. It looks like that's the case. Have you give it a shot to maybe create two One-To-Many relationships and set unique constrains? The cons is that you will have lists of User and Image but I think it could work. – Karel Tamayo Oct 06 '16 at 22:13
  • Can you give an example or edit your answer? I'm not against two one to many, but then how do you do the unique constraint? – SventoryMang Oct 06 '16 at 22:15
  • Check it out! Let me know how it goes. – Karel Tamayo Oct 06 '16 at 22:56