Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to define Self referencing one to one relationship #12868

Closed
niltor opened this issue Aug 2, 2018 · 2 comments
Closed

How to define Self referencing one to one relationship #12868

niltor opened this issue Aug 2, 2018 · 2 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@niltor
Copy link

niltor commented Aug 2, 2018

How to define Self referencing one to one relationship?

I have a model:

public class User
{
    public string Name{get;set;}
    // your wife or husband
    public User Spouse{get;set;}
}

Of course ,you just have one wife or husband !
I wan't to select personA (include person A' couple)!

when I use code first ,and use dotnet ef database update ,
The error here:

There are no primary or candidate keys in the referenced table!

Further technical details

EF Core version: (2.1.1)
Database Provider: ( Microsoft.EntityFrameworkCore.SqlServer)
Operating system: Win10 1803
IDE: (Visual Studio 2017 15.7.5)

@ajcvickers
Copy link
Member

@niltor This is actually quite tricky if you want to have a single navigation property Spouse, so let me first show how to do it without that:

public class User
{
    public string Name { get; set; }
    public User Husband { get; set; }
    public User Wife { get; set; }
}

public class BloggingContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(b =>
        {
            b.HasKey(e => e.Name);
            b.HasOne(e => e.Husband).WithOne(e => e.Wife);
        });
    }
}

public class Program
{
    public static void Main()
    {
        using (var context = new BloggingContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Add(new User {Name = "Arthur", Wife = new User{ Name = "Wendy" } });
            context.SaveChanges();
        }

        using (var context = new BloggingContext())
        {
            foreach (var user in context.Set<User>().Include(e => e.Husband).Include(e => e.Wife).ToList())
            {
                Console.WriteLine($"{user.Name} is married to {(user.Wife ?? user.Husband).Name}");
            }
        }
    }
}

In this case there is a single relationship with a navigation property pointing to Wife and an inverse navigation property pointing back to the husband. This acts as a normal 1:1 relationship. A person can be set to have a Wife, or a person can be set to have a Husband.

Now the problem with having a single navigation property Spouse is that it can't be used for both directions of the relationship--that is, it can't be used to point both from a Husband to a Wife and a Wife to a Husband. To work around this, we can define the relationship as not having an inverse navigation property: b.HasOne(e => e.Spouse).WithOne(). We can then use this relationship for both Husband and Wife, but for each couple the relationship needs to be set for both directions--that is the Wife needs to be set on the Husband, and the Husband needs to be set on the Wife. (Since this can create cycles you may run into #1699.) Here's the full example:

public class User
{
    public string Name { get; set; }
    public User Spouse { get; set; }
}

public class BloggingContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(b =>
        {
            b.HasKey(e => e.Name);
            b.HasOne(e => e.Spouse).WithOne();
        });
    }
}

public class Program
{
    public static void Main()
    {
        using (var context = new BloggingContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var arthur = new User {Name = "Arthur"};
            var wendy = new User {Name = "Wendy"};

            context.AddRange(arthur, wendy);
            context.SaveChanges();

            arthur.Spouse = wendy;
            wendy.Spouse = arthur;

            context.SaveChanges();
        }

        using (var context = new BloggingContext())
        {
            foreach (var user in context.Set<User>().Include(e => e.Spouse).ToList())
            {
                Console.WriteLine($"{user.Name} is married to {user.Spouse.Name}");
            }
        }
    }
}

@ajcvickers ajcvickers added closed-no-further-action The issue is closed and no further action is planned. customer-reported labels Aug 3, 2018
@niltor
Copy link
Author

niltor commented Nov 19, 2018

@ajcvickers THX!

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

2 participants