简体   繁体   中英

EF6 One-To-One relationship with separate ID property

We need to define a One-To-One relationship between following entities:

public class Foo
{
    [Key, Column("Foo_ID")]
    public int Id { get; set; }

    public Bar Bar { get; set; }
}

public class Bar
{
    [Key, Column("Bar_ID")]
    public int Id { get; set; }

    [Column("Bar_Foo_ID")]
    public int? FooId { get; set; }

    public Foo Foo { get; set; }   
}

We need to migrate a legacy software and have both of them running side by side for now. So we can't change any relations. Unfortunately there are no foreign keys defined in the database.

modelBuilder.Entity<Foo>()
            .HasOptional(a => a.Bar)
            .WithRequired(x => x.Foo)
            .WillCascadeOnDelete(true);

When we query Foo and Include(x => x.Bar) it creates a SQL query with LEFT OUTER JOIN bars ON Foo_ID = Bar_ID which is wrong.

We need to change this to a LEFT OUTER JOIN bars on Foo_ID = Bar_Foo_ID , but I'm not sure how Entity Framework supports that as we don't have foreign keys in our DB and Bar_ID as PrimaryKey.

I know that there can be potentially more than one Bars for one Foo , but is there a way to enforce a One-To-One relationship?

When a Foo is created, a Bar is always created for it.

For legacy data, you can use the following:

1 : 0-1 relationships are basically a special form of 1 : many relationships.

So you can configure the relation as 1:many in entity framework and potentially add a unique index to the foreign key property to enforce your constraint of max. one related entry.

public class Foo
{
    [Key, Column("Foo_ID")]
    public int Id { get; set; }

    // this can't be a single reference property unfortunately... but it will only contain 0 or 1 object when working with it.
    public ICollection<Bar> Bars { get; set; }
}

public class Bar
{
    [Key, Column("Bar_ID")]
    public int Id { get; set; }

    [Index(IsUnique = true)]
    [Column("Bar_Foo_ID")]
    public int? FooId { get; set; }

    public Foo Foo { get; set; }   
}

modelBuilder.Entity<Foo>()
            .HasMany(a => a.Bars)
            .WithRequired(x => x.Foo)
            .HasForeignKey(x => x.FooId)
            .WillCascadeOnDelete(true);

The way I understand it, you have an existing column called Bar_Foo_ID in Bar table which has to be used as FK to the PK Bar_ID column in Bar table.

It's possible, but EF6 has limited support for such relationship, in particular does not support explicit FK property. So you have to remove the FooId property from the model:

public class Bar
{
    [Key, Column("Bar_ID")]
    public int Id { get; set; }

    public Foo Foo { get; set; }   
}

and use the following fluent configuration:

modelBuilder.Entity<Foo>()
    .HasOptional(a => a.Bar)
    .WithOptionalPrincipal(x => x.Foo)
    .Map(m => m.MapKey("Bar_Foo_ID"))
    .WillCascadeOnDelete(true);

MapKey is to specify the FK column name. WithOptionalPrincipal is to specify that it's nullable (while WithRequired would be for non nullable).

Note that EF6 treats cascade delete differently for optional relationships. Instead of deleting the associated records, it disassociates them by setting the FK to null . In case you want to actually delete the associated Bar when deleting the Foo , you have to do that manually with code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM