简体   繁体   中英

Entity Framework: Reusing foreign key

I'm refactoring someone else's code involving EF6, code-first. I am trying to reuse database columns so that they are involved in more than one foreign key relationship.

I am aware that by doing this, I am denormalizing my schema, but in so doing, it will allow me to make more direct relationships between models that were missing in the original design. I would like this to happen with minimal changes to the underlying database schema, as there are hand-written sprocs and UDFs that will be affected.

The models below represent the current state of affairs:

A Frame is comprised of a FrameType and a Moulding . Frames are unique, in that no pair of FrameType or Moulding is repeated, so we have a composite primary key over FrameType_Id and Moulding_Id .

public class Frame
{
    [Key,Column(Order = 0)]
    [ForeignKey("Moulding")]
    public int Moulding_Id { get; set; }
    public Moulding Moulding { get; set; }

    [Key, Column(Order = 1)]
    [ForeignKey("FrameType")]
    public int FrameType_Id { get; set; }
    public FrameType FrameType { get; set; }
}

where we can assume a minimal implementation for FrameType and Moulding

public class FrameType
{
    public int Id{get; set;}
    public ICollection<Frame> Frames{get; set;}
}

public class Moulding
{
    public int Id{get; set;}
    public ICollection<Frame> Frames{get; set;}
}

Elsewhere we have a Product which references both FrameType and Moulding :

public class Product
{
    public int Id{get; set;}

    [ForeignKey("Moulding")]
    public int Moulding_Id { get; set; }
    public Moulding Moulding { get; set; }

    [ForeignKey("FrameType")]
    public int FrameType_Id { get; set; }
    public FrameType FrameType { get; set; }
}

but, critically, does not directly reference Frame .

I would like to add a property to Frame :

public ICollection<Product> Products{get; set;}

and on Product :

public Frame Frame{get; set;}

re-using the Moulding_Id and FrameType_Id fields not only as foreign keys to Moulding and FrameType but also as a composite foreign key directly to Frame .

Is this kind of "reuse" of foreign keys possible in EF?

Yes, it's possible. The only EF6 requirements are (1) the referenced entity property to be a PK, and (2) in case of reusing the parts of the FK, the FK properties to be explicitly defined, since there is no way to map 2 shadow properties to one and the same name. Fortunately your sample model satisfies both conditions.

I personally prefer the fluent configuration, because it's more explicit and IMO easier to follow. Or in case you prefer data annotations, decorate the navigation property with the ForeignKey attribute rather than the inverse, because while bot allow mapping a single FK field, the former is the only way to map the composite FK fields through data annitation.

Applying it to your sample model looks like this:

public class Frame
{
    [Key, Column(Order = 0)]
    public int Moulding_Id { get; set; }

    [Key, Column(Order = 1)]
    public int FrameType_Id { get; set; }

    [ForeignKey("Moulding_Id")]
    public Moulding Moulding { get; set; }

    [ForeignKey("FrameType_Id")]
    public FrameType FrameType { get; set; }

    public ICollection<Product> Products { get; set; }
}

public class Product
{
    public int Id { get; set; }

    public int Moulding_Id { get; set; }

    public int FrameType_Id { get; set; }

    [ForeignKey("Moulding_Id")]
    public Moulding Moulding { get; set; }

    [ForeignKey("FrameType_Id")]
    public FrameType FrameType { get; set; }

    [ForeignKey("Moulding_Id,FrameType_Id")]
    public Frame Frame { get; set; }
}

However, there is a small issue - the above introduces a multiple cascade paths and normally would require you to turn cascade delete off. Which in turn will require fluent configuration, and once you use fluent configuration, there is no need of ForeignKey data annotation, so remove it from Product.Frame property and use the following:

modelBuilder.Entity<Product>()
    .HasRequired(e => e.Frame)
    .WithMany(e => e.Products)
    .HasForeignKey(e => new { e.Moulding_Id, e.FrameType_Id })
    .WillCascadeOnDelete(false);

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