问题
I have a hierarchical relationship defined for one of my tables where the relationship is stored in a separate join table. The join table also contains information about the type of relationship. The (simplified) schema looks like:
Bills
ID int IDENTITY(1,1) NOT NULL (PK)
Code varchar(5) NOT NULL
Number varchar(5) NOT NULL
...
BillRelations
ID int IDENTITY(1,1) NOT NULL (PK)
BillID int NOT NULL
RelatedBillID int NOT NULL
Relationship int NOT NULL
...
I have FK relationships defined on BillID and RelatedBillID to ID in the Bills table.
I'm trying to map this in Entity Framework Code First with little success. My classes look like the following. Ignore the RelationshipWrapper
stuff, it's a wrapper class around an enum
named Relationship
that corresponds to the value in the Relationship column.
public class Bill
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
[StringLength(5)]
public string Code { get; set; }
[Required]
[StringLength(5)]
public string Number { get; set; }
public virtual ICollection<BillRelation> RelatedBills { get; set; }
...
}
public class BillRelation
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public long BillID { get; set; }
[ForeignKey("BillID")]
public virtual Bill Bill { get; set; }
public long RelatedBillID { get; set; }
[ForeignKey("RelatedBillID")]
public virtual Bill RelatedBill { get; set; }
public RelationshipWrapper Relationship { get; set; }
...
}
I've tried this in various incarnations, both using explicitly defined relationships via the ModelBuilder on the DbContext and data annotations only, but the above defines what I'm looking for. I'm using collections because the the foreign key properties aren't primary keys.
Using this set up I get an error: Bill_ID column is not defined
(or something similar).
If I got the ModelBuilder route using the following, I get an error that "The table BillRelations does not exist in the database."
modelBuilder.Entity<Bill>().HasMany( b => b.RelatedBills )
.WithRequired( r => r.Bill )
.Map( m => m.MapKey( "BillID" ).ToTable( "BillRelations" ) );
modelBuilder.Entity<BillRelation>().HasRequired( r => r.RelatedBill )
.WithRequiredDependent()
.Map( m => m.MapKey( "RemoteBillID" ).ToTable( "BillRelations" ) );
I have been able to make it work by defining only one half of the relationship, Bills -> BillRelations, then using a Join in my repository to fill in an [NotMapped]
RelatedBill property on the BillRelations class for each of the related bills in the Bill's RelatedBills collection. I'd rather not do this if I can help it.
The only other solution I've thought of is to model each relationship in a separate table (there are 4 types) and use a standard Bill<->Bill mapping through the join table for each of the 4 relationship types -- again I'd rather not do this if I can avoid it.
Can anyone see what I'm doing wrong or tell me if what I want to do is even possible in EF Code First 4.1?
回答1:
Just a few ideas:
Your mapping with data annotations doesn't work because EF Code First conventions don't recognize which navigation properties belong together. Obviously you want to associate
Bill.RelatedBills
withBillRelation.Bill
. But because there is a second navigation propertyBillRelation.RelatedBill
refering to theBill
entity as well theAssociationInverseDiscoveryConvention
can't be applied to recognize the correct relation. This convention only works if you have exactly one pair of navigation properties on the entities. As a consequence, EF assumes actually three relationships, each with only one exposed end in the model. The relationship whereBill.RelatedBills
belongs to assumes a not exposed foreign key on the other side according to EF default naming conventions - which isBill_ID
with underscore. It doesn't exist in the database, hence the exception.In your Fluent API mapping I would just try to remove
...ToTable(...)
altogether. I believe that it is not necessary as the mapping knows anyway which table the foreign key belongs to. This will possibly fix the second error ("Table ... does not exist....")Your second mapping - the one-to-one relationship - does possibly not work as expected because one-to-one relationships are "usually" mapped with a shared primary key association between the tables in the database. (I am not sure though if shared primary keys are really required by EF.) Because your
BillId
column seems to be a foreign key which is not a primary key at the same time I would try to map the relationship as one-to-many. Moreover because your foreign key columns are exposed as properties in the model you should useHasForeignKey
instead ofMapKey
:modelBuilder.Entity<Bill>() .HasMany( b => b.RelatedBills ) .WithRequired( r => r.Bill ) .HasForeignKey ( r => r.BillID ); // turn off/on cascade delete by chaining // .WillCascadeOnDelete(true/false) // here at the end modelBuilder.Entity<BillRelation>() .HasRequired( r => r.RelatedBill ) .WithMany() .HasForeignKey ( r => r.RelatedBillID ); // turn off/on cascade delete by chaining // .WillCascadeOnDelete(true/false) // here at the end
Edit
It is possible that the whole Fluent mapping is not necessary if you put the [InverseProperty]
attribute on one of the navigation properties - for example in your Bill
class:
[InverseProperty("Bill")]
public virtual ICollection<BillRelation> RelatedBills { get; set; }
In this attribute you specify the name of the associated navigation property on the related entity. This binds Bill.RelatedBills
and BillRelation.Bill
together to a pair of navigation properties being the ends of the same association. I hope that EF will do the correct thing with the remaining navigation property BillRelation.RelatedBill
, i.e. create a one-to-many relationship - I hope... If the default cascading delete won't work for you, you are forced to use Fluent API though since there is no data annotation attribute to configure cascading delete.
来源:https://stackoverflow.com/questions/7935100/define-relationships-for-hierarchical-data-in-entity-framework-code-first