Question

If i have SQL Server tables like this:

Location
----------
LocationId int PK 
Field1 int
Field2 int
etc

Score
------------------------------------
LocationId int PK, FK
IsSingleLevel bit PK (discriminator)
Field1 int
Field2 int
etc

Technically this is mapped as a Location 1..0..* Score, but with the PK of LocationId/IsSingleLevel, it's a Location 1..0..2.

When i drag this on the EDMX, set everything up as expected (abstract entity, remove discriminator from base entity, etc).

EF give this error three times (one for base entity, and one for the two derived entities):

Error 6 Error 3025: Problem in mapping fragments starting at line 2146:Must specify mapping for all key properties (LocationScore.LocationId, LocationScore.IsSingleLevelScore) of table LocationScore.

I followed the example here.

The error occurs because i have the discriminator as part of the PK in the database, and discriminators are not mapped on the model, so i get the error.

I can't have LocationId as the only field in the PK, because then a location could only have 1 score, i need it to have two scores (one single level, one overall).

The end result is i want to be able to do this:

Locations.Include("LocationOverallScore").Single();
Locations.Include("LocationSingleLevelScore").Single();

Where LocationOverallScore and LocationSingleLevelScore are derived entities from the LocationScore base (abstract entity).

Is TPH not the right design here? Is my database design wrong? The aim is i don't want to have 2 physical tables for the different scores - as the table is huge, and i don't want to repeat the columns.

There's two possible workarounds i can think of:

1 - Make a view (LocationScore), which UNION's the two tables (so it would return 2 rows per LocationId) - but i still don't think i can "TPH" this. I don't want to manually perform the JOIN, i want to eager load.

2 - Add an IDENTITY column to Score, and this can be the PK.

Can you guys think of another solution?

Was it helpful?

Solution

I found a solution. I'm not 100% happy with it, but it does satisfy my requirement of not having two tables.

In the database side:

LocationScore:

  • LocationScoreId: IDENTITY, PK
  • LocationId: FK
  • Unique Index on LocationId/IsSingleLevelScore

I imported that into my EDMX - and included the LocationId FK (i never usually do this - but it's required in this instance).

Created the derived entities, mapped the fields, set the discriminator.

Created an association between Location -> LocationOverallScore, Location -> LocationScore (based on LocationId referential constraint).

All works fine.

The one downside is that because LocationId is not part of the PK on the LocationScore table, it's a 1..* between Location and LocationOverallScore, when in reality it should only be a 1..1.

I enforce this business requirement in the model via a hook property:

public class Location
{
   // EF navigational properties - required
   public ICollection<LocationOverallScore> LocationOverallScores { get; set; }
   public ICollection<LocationSingleLevelScore> LocationSingleLevelScores { get; set; }

   // Hook properties
   public LocationOverallScore OverallScore
   {
      get { return LocationOverallScores.SingleOrDefault(); }
   }
   public LocationSingleLevelScores SingleLevelScore
   {
      get { return LocationSingleLevelScores .SingleOrDefault(); }
   }
}

So the .SingleOrDefault() will throw an exception if there is more than one record - which there never will be, because of the unique index.

So i can now do this:

var location = ctx.Locations.Include("LocationOverallScores").Single();
var overallScore = location.OverallScore;

That's what i'll be going with for now.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top