问题
I am running into an issue with EF6, though I'm fairly sure that this applies to previous versions that support this type of mapping. I fear I know the answer to the question at hand, but I hope that I am doing something wrong, or there is a better workaround than what I present here. All classes are gutted for clarity.
So I have
public abstract class SoftwareFirmware
{
public long Id { get; private set; }
public ICollection<DeviceType> DeviceTypes { get; private set; }
public SoftwareFirmware()
{
DeviceTypes=new HashSet<DeviceType>();
}
}
and
public class DeviceType
{
public long Id { get; set; }
public virtual ICollection<Firmware> AvailableFirmwareVerions { get; private set; }
public virtual ICollection<Software> AvailableSoftwareVerions { get; private set; }
public DeviceType()
{
AvailableFirmwareVerions = new HashSet<Firmware>();
AvailableSoftwareVerions = new HashSet<Software>();
}
}
which as you can see have a many to many relationship defined. I've defined two classes which derive from SoftwareFirmware
, the aptly named
public class Firmware : SoftwareFirmware {}
and
public class Software : SoftwareFirmware {}
I'm using Table Per Hierarchy inheritance, so Software
and Firmware
are stored in the same table with a discriminator column. Finally, I've mapped the relationships in the derived DbContext
's OnModelCreating
method with
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany(firmware=>firmware.DeviceTypes);
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany(sofware=>sofware.DeviceTypes);
The problem at hand is that Entity Framework does not seem to support inheritance with this mapping, as I receive the following when EF tries to generate the database:
DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' is not valid. Type 'Software' of FromRole 'DeviceType_AvailableSoftwareVerions_Target' in AssociationType 'DeviceType_AvailableSoftwareVerions' must exactly match with the type 'SoftwareFirmware' on which this NavigationProperty is declared on.
From this I gather that a type that inherits from SoftwareFirmware
is not good enough for the NavigationProperty, it must be a SoftwareFirmware
type. If I tear the DeviceType
collection out of the SoftwareFirmware
base class and duplicate it in each of the derived classes, things work, but that's certainly less than ideal.
So finally, my question is- is there another way to configure this so that I can keep my navigation property in my base class? If not, is there a cleaner workaround than what I've described?
UPDATE: so it would seem that SQL Server Management Studio did me wrong, as I had diagrammed out the database previously without the overloaded version of WithMany that takes an expression and it did not include the junction tables. It seems like SSMS doesn't play nice with schema changes in terms adding new diagramming even when the database has been dropped and recreated- it must be restarted. Major pain, but I digress...
As a last ditch effort, I reverted to the parameterless version of WithMany
for the mappings, deleted and recreated the database by restarting the application, restarted SSMS, and lo! The junction tables were created. All I needed to do is add an Ignore
for the base SoftwareFirmware
class's DeviceTypes
property and everything generated cleanly. So my FluentAPI mapping code looks like this:
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany();
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany();
modelBuilder.Entity<SoftwareFirmware>().Ignore(s => s.DeviceTypes);
which generates this schema- pretty much exactly the schema I wanted (ignore the extra properties):
However, since the parameterless call to WithMany
only hooks up a navigation property on one side, updates to Software.DeviceTypes
and Firmware.DeviceTypes
aren't tracked by EF so I'm back where I started.
回答1:
The issue is that you have a single SoftwareFirmware.DeviceTypes property but you are then trying to use it as part of two separate relationships. SoftwareFirmware.DeviceTypes can't be the inverse of both DeviceType.AvailableFirmwareVerions and DeviceType.AvailableSoftwareVerions.
What you're trying to model is a bit strange because you're kind of treating them as distinct relationships, but also not. There are two options here...
Option 1: It's two separate relationships
Remove SoftwareFirmware.DeviceTypes and add a DeviceTypes property on Firmware and Software.
This is actually what you are doing when you put Ignore on the SoftwareFirmware.DeviceTypes property and use the empty overload of WithMany - which is why it works. You're telling EF that there are two relationships (one Software -> DeviceType and the other Firmware -> DeviceType) and that there is no navigation property that points back the other way. Since you ignored SoftwareFirmware.DeviceTypes it's just not part of your model.
Option 2: It's one relationship
Remove the two navigation properties on DeviceType and replace them with a single navigation to the SoftwareFirmware base class. You can always add some façade properties that filter the contents to Software and Firmware (as shown below)
public class DeviceType
{
public long Id { get; set; }
public virtual ICollection<SoftwareFirmware> AvailableVerions { get; private set; }
public virtual IEnumerable<Firmware> AvailableFirmwareVerions
{
get
{
return this.AvailableVerions.OfType<Firmware>();
}
}
public virtual IEnumerable<Software> AvailableSoftwareVerions
{
get
{
return this.AvailableVerions.OfType<Software>();
}
}
public DeviceType()
{
AvailableVerions = new HashSet<SoftwareFirmware>();
}
}
回答2:
This problem sounds familiar. (checking my email ...yep it was over a year ago!) I had someone send me a sample where a fluent api relationship was failing. They did not have a many to many but I think it's the same problem. I spent a long time looking at it and asked Rowan Miller (on the team) and he said that the fluent api can't comprehend the property coming from the base type.
i.e. the fluent API can't see the DEVICETYPE property when it's looking at AvailableSoftwareVerions or at AvailableFirmwareVersions. (I can't tell you WHY this is. You'd think it could find it via reflection but maybe it just wasn't designed with this scenario in mind.)
This still didn't make sense to me so he explained further (and I'll update his explanation with your types which was a little confusing since you have extra levels of inheritance and things are named a bit inconsistently ...but I
Conceptually the classes don’t really make sense, because a DeviceType can have many Software(s) or Firmware(s)… but the inverse navigation property is defined on SoftwareFirmware. So what happens when something that isn’t a Firmware or Software has a DeviceType? It’s inverse is configured as > DeviceType.AvailableSoftwareVersions but that can’t work. Even taking EF out of the picture the correct way to model that is to have the Project property be on Report.
That was with EF5. If my memory is correct and it's the same problem, then maybe it hasn't changed for EF6. Perhaps we should look to see if there's an issue in there for solving this problem. However, his further explanation suggests that it's not a bug but a protection.
(I'm going to ping him to verify that I'm inferring the previous problem to this one correctly).
In that email, Rowan also suggested using getter logic instead of a navigation properties as a workaround.
来源:https://stackoverflow.com/questions/20481097/issue-with-many-to-many-relationship-tph-inhertitance-in-entity-framework-6