Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF Code First One to Many and Reverse One To One Relationship

I am trying to create one-to-many and reverse one-to-one relationship using code first. Here is what I ma trying to do

1) One-to-Many between two classes and it works as expected.

    public class X
    {
        [Key]
        public int XId { get; set; }
        public ICollection<Y> Y { get; set; }

    }

    public class Y
    {
        [Key]
        public int YId { get; set; }
        public int XId { get; set; }
        public X X { get; set; }
    }

    public class DataContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Y>()
                .HasRequired(y => y.X)
                .WithMany(x => x.Y)
                .HasForeignKey(y => y.XId);
        }
    }

Now what I want to do is to create Reverse One-to-One optional relationship between Y and X, such that the X will contain a foreign key of Y...How is it possible? Here is what I am trying to do and it throws some Multiplicity Error

       public class X
        {
            [Key]
            public int XId { get; set; }
            public ICollection<Y> Y { get; set; }
            public int YId {get; set; }
            [ForiegnKey("YId")]
            public Y YOptional { get; set; }
        }

        public class Y
        {
            [Key]
            public int YId { get; set; }
            public int XId { get; set; }
            public X X { get; set; }
            public X XOptional {get; set; }
        }

        public class DataContext : DbContext
        {
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Y>()
                    .HasRequired(y => y.X)
                    .WithMany(x => x.Y)
                    .HasForeignKey(y => y.XId);

                modelBuilder.Entity<X>()
                    .HasOptional(x => x.YOptional)
                    .WithOptionalDependent(y=> y.XOptional);
            }
        }
like image 807
Ammar Khan Avatar asked Feb 11 '23 08:02

Ammar Khan


2 Answers

You can't have a relationship between two entities that is defined differently from either end. So you can't do 1:* from one direction and 1:1 from another.

Let me make a guess that you don't really want it to be 1:1 from the dependent end. From that end it will always only point to one thing.

In mappings, unlike in life, unless you have many to many, a child only has one parent.

You can, however, create a 0..1 : * relationaship (zero or one to many). Where the parent can have one or more children (e.g. "many") but the child can exist without a parent, but it can never have more than one parent (e.g. "zero or one").

Here is the simplest method of making your classes result in a [zero or one] to many relationship. Notice that I made the foreign key in the class Y a nullable int. WIth this setup, EF conventions will result in a mapping that lets a child exist without a parent.

public class X
  {
    [Key]
    public int XId { get; set; }
    public ICollection<Y> Y { get; set; }

  }

  public class Y
  {
    [Key]
    public int YId { get; set; }
    public int? XId { get; set; }
    public X X { get; set; }
  }

  public class DataContext : DbContext
  {
    public DbSet<X> XSet { get; set; }
    public DbSet<Y> YSet { get; set; }
   }

Here is a screenshot of visual model derived from the above classes and context. I think this achieves the behavior you are seeking if my guess that you may just be perceiving it differently is correct.

enter image description here

like image 65
Julie Lerman Avatar answered Apr 06 '23 18:04

Julie Lerman


Using the actual class names you mentioned in the comments:

Mapping a User that can have many Singles is not a problem. However, when you want to map a 1:1 association between a User and a Single you have to choose which of the two is the "principle" entity. You can't have a foreign key column in both tables because one entity will always be inserted before the other one. The "dependent" entity is inserted next, and it refers to the principal's primary key value.

So if User is the principal entity, you could have a class model similar to this:

public class User
{
    public User()
    {
        this.Singles = new HashSet<Single>();
    }

    public int UserId { get; set; }
    public string Name { get; set; }

    public Single Single { get; set; }

    public virtual ICollection<Single> Singles { get; set; }
}

public class Single
{
    public int SingleId { get; set; }
    public string Name { get; set; }
    public virtual User User { get; set; }
    public int SuperUserId { get; set; }
    public User SuperUser { get; set; }
}

And two options for mappings:

Option 1: User as principal

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().HasMany(u => u.Singles)
        .WithRequired(s => s.SuperUser).HasForeignKey(s => s.SuperUserId);
    modelBuilder.Entity<User>().HasOptional(s => s.Single)
        .WithOptionalPrincipal(s => s.User).Map(m => m.MapKey("UserId"));
}

In the data model, Single now has two foreign keys, UserId and SuperUserId. This is how to create a User and a Single in User.Single and User.Singles:

var superUser = new User { Name = "superUser1" };
var single = new Single { Name = "single" };
superUser.Singles.Add(single);
db.Users.Add(superUser);
superUser.Single = single;
db.SaveChanges();

And EF will first insert the User, then the Single having both foreign keys equal to the User's primary key.

Option 2: Single as principle

You can also make Single the principal entity in the 1:1 association:

modelBuilder.Entity<User>().HasOptional(s => s.Single)
    .WithOptionalDependent(s => s.User).Map(m => m.MapKey("SingleId"));

Now there's only one foreign key in Single (SuperUserId) and a foreign key in User (SingleId). If you execute the same code, now EF will throw

Unable to determine a valid ordering for dependent operations.

This is because there is a chicken-and-egg problem: the Single must be created before the dependent User can be created, but the User must be created before the Single can be added to its Singles collection. This could be solved by assigning the Single later:

var superUser = new User { Name = "superUser1" };
var single = new Single { Name = "single" };
superUser.Singles.Add(single);
db.Users.Add(superUser);
db.SaveChanges();

superUser.Single = single;
db.SaveChanges();

You'd want to wrap this in a TransactionScope, so I think this option is less viable.

Note
As you see, in a 1:1 mapping the foreign key can't be mapped to a property in the class model. There is no HasForeignKey in the fluent API after WithOptionalDependent or WithOptionalPrincipal. Also, this association can only be mapped by the fluent API. In data annotations there is not attribute to indicate the principal end of an association.

like image 28
Gert Arnold Avatar answered Apr 06 '23 16:04

Gert Arnold