问题
Got the following datamodel:
class EntityA
{
Guid Id { get; set; }
//Property1 and Property2 will never be the same
EntityB Property1 { get; set; }
EntityB Property2 { get; set; }
}
class EntityB
{
int Id { get; set; }
EntityA EntityAProperty { get; set; }
}
But I can't manage to configure the relation. EntityA references two different EntityB. Please give me some advise on how to configure it.
Tried something like (for property1 and property2):
e.HasOne(x => x.Property1)
.WithOne()
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
or
e.HasOne(x => x.Property1)
.WithOne(x => x.EntityB)
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
First one tells me that
The best match for foreign key properties {'Id' : int} are incompatible with the principal key {'Id' : Guid}.
The second one tells me that I can not use the property EntityB for two relations.
回答1:
There is a problem with your navigation property EntityB.EntityAProperty
. For each record in EntityA
there will be two records in EntityB with the same value in that property, so it can't be used as your one-to-one FK, as EF would try to put a unique constraint on it (in the column EntityB.EntityAPropertyId
).
All the solutions I can think of require changing your model:
Solution 1: Remove the problematic navigation property
EntityB.EntityAProperty
. MakeEntityB
the principal part of your relationships, instead ofEntityA
(perhaps this is not acceptable according to the semantics of your model). This means that in the physical tables you will have these columns:EntityA: Id (PK) Property1Id (FK on EntityB.Id, unique constraint) Property2Id (FK on EntityB.Id, unique constraint) EntityB: Id (PK)
This will not guarantee full referential integrity, you will have to add some checkings in code when creating/updating
EntityA
instances, specifically checking that aProperty1Id
value is not used in another entity'sProperty2Id
, and the other way around. Also, check that the value is not the same in both properties in the same instance ofEntity1
.The changes in your entities:
class EntityA { Guid Id { get; set; } EntityB Property1 { get; set; } EntityB Property2 { get; set; } int Property1Id { get; set; } //New int Property2Id { get; set; } //New } class EntityB { int Id { get; set; } //Removed EntityA property }
The fluent API mappings in
OnModelCreating
:modelBuilder.Entity<EntityA>() .HasOne<EntityB>(a => a.Property1) .WithOne() //No navigation property in EntityB .HasForeignKey<EntityA>(a => a.Property1Id); modelBuilder.Entity<EntityA>() .HasOne<EntityB>(a => a.Property2) .WithOne() //No navigation property in EntityB .HasForeignKey<EntityA>(a => a.Property2Id);
Solution 2: The other way around: the principal entity is
EntityA
, so the FK columns and unique constraints are placed inEntityB
table. This seems the best implementation semantically, but has a problem: remember that you can't use only one column inEntityB
for the ID inEntityA
because of the unique constraint violation (you would have two records with the sameEntityA.Id
). So you would have to add two columns inEntityB
, one forProperty1
and one forProperty2
. As a record inEntityB
is used only in one of those properties at the same time, then one of the two columns would be null in each record. It really looks a little forced and the FK columns are sparse, with null value in 50% of the records.These would be the tables:
EntityA: Id (PK) EntityB: Id (PK) EntityAProperty1Id (FK on EntityA.Id, unique constraint) EntityAProperty2Id (FK on EntityA.Id, unique constraint)
This solution requires you to do the same checkings in code than the previous one.
The changes in your entities:
class EntityA { Guid Id { get; set; } EntityB Property1 { get; set; } EntityB Property2 { get; set; } } class EntityB { int Id { get; set; } //Removed EntityA property EntityA EntityAProperty1 { get; set; } //New EntityA EntityAProperty2 { get; set; } //New int EntityAProperty1Id { get; set; } //New int EntityAProperty2Id { get; set; } //New }
The fluent API mappings in
OnModelCreating
:modelBuilder.Entity<EntityB>() .HasOne<EntityA>(b => b.EntityAProperty1) .WithOne(a => a.Property1) .HasForeignKey<EntityB>(b => b.EntityAProperty1Id); modelBuilder.Entity<EntityB>() .HasOne<EntityA>(b => b.EntityAProperty2) .WithOne(a => a.Property2) .HasForeignKey<EntityB>(b => b.EntityAProperty2Id);
Consider a different solution: Solution 3: Give up on your one-to-one relationships and use a one-to-many with exactly two values. Instead of having two properties
Property1
andProperty2
, define a collection property and deal with its values by position: the first one isProperty1
, the second one isProperty2
. I don't know if this makes sense functionally, perhaps the uses of each property are totally different and putting them together in a list doesn't make sense, but it would be easier to deal with them this way. You can keep the public properties as they are, and use an underlying private field that is the one that gets mapped by EF and saved to the database tables. You would tell EF to ignore the public properties and use theirget
andset
methods to use the undelying list. With this you can keep using your navigation propertyEntityB.EntityAProperty
. You still would have to write some code to check that there are only two values in the list.Another solution, a bit off-topic: Solution 4: Consider using inheritance in your
EntityB
entity. You define two classesEntityB1
andEntityB2
, that only extend the parentEntityB
without adding any property. You define the properties inEntityA
using the derived types, not the parent one:class EntityB { int Id { get; set; } } class EntityB1 : EntityB {} class EntityB2 : EntityB {} class EntityA { Guid Id { get; set; } EntityB1 Property1 { get; set; } EntityB2 Property2 { get; set; } }
As
Property1
andProperty2
have different data types you no longer have two relationships with the same entity. EF should be able to figure it all out by convention. Probably you won't need to add explicit fluent API mappings.
I would recommend solution 1, but only if it makes sense semantically that EntityB
is the principal part in the relationship. Otherwise I would recommend solution 4.
More information about the way one-to-one relationships are mapped by EF Core to the physical tables can be found here. A FK column is added only in the dependent table. No column is added to the principal table. The same that is done for a one-to-many relationship, except that, to make sure that the relationship is one-to-one and not one-to-many, a unique constraint is created also on that FK column.
EF does this by convention for one-to-one relationship between two entities that have a navigation property in both sides of the relationship (which is the case in Solution 2 scenary, but can't be used in Solution 1).
This page also contains info that can be relevant for your issue.
Sorry, this answer got longer that I intended. I got a bit carried away.
来源:https://stackoverflow.com/questions/49219379/ef-core-two-one-to-one-on-one-principal-key