I'm just starting to learn EF. The problem I'm facing is with TPH. The example below is from the EF recipes by apress. The table is basically this:
CREATE TABLE [Chapter2].[Employee](
[EmployeeId] [int] IDENTITY(1,1) NOT NULL,
[EmployeeType] [int] NULL,
[FirstName] [nvarchar](50) NOT NULL,
[LastName] [nvarchar](50) NOT NULL,
[Salary] [decimal](18, 0) NOT NULL,
[Wage] [decimal](18, 0) NOT NULL,
CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED
(
[EmployeeId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
After creating table and importing it into EF. I made two entities, one FullTimeEmployee and HourlyEmployee. I'm setting the condition under employee type to 1 for the full time one and 2 for the hourly one. I'm, of course deleting the property from the main Employee entity.
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
<EntityContainerMapping StorageEntityContainer="EFRecipesModel1StoreContainer" CdmEntityContainer="EFRecipesEntities1">
<EntitySetMapping Name="Employees">
<EntityTypeMapping TypeName="IsTypeOf(EFRecipesModel1.Employee)">
<MappingFragment StoreEntitySet="Employee">
<ScalarProperty Name="EmployeeId" ColumnName="EmployeeId" />
<ScalarProperty Name="LastName" ColumnName="LastName" />
<ScalarProperty Name="FirstName" ColumnName="FirstName" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(EFRecipesModel1.FullTimeEmployee)">
<MappingFragment StoreEntitySet="Employee">
<ScalarProperty Name="EmployeeId" ColumnName="EmployeeId" />
<ScalarProperty Name="Salary" ColumnName="Salary" />
<Condition ColumnName="EmployeeType" Value="1" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(EFRecipesModel1.HourlyEmployee)">
<MappingFragment StoreEntitySet="Employee">
<ScalarProperty Name="EmployeeId" ColumnName="EmployeeId" />
<ScalarProperty Name="Wage" ColumnName="Wage" />
<Condition ColumnName="EmployeeType" Value="2" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
THe error I'm getting is:
Error3023: Problem in maaping fragments starting at line 51 column: Employee.Salary in table Employee must be mapped. It has no default value and is not nullable.
I read around and saw a suggestino up update the SSDL which in a way didn't make sense to me:
<edmx:StorageModels>
<Schema Namespace="EFRecipesModel1.Store" Provider="System.Data.SqlClient" ProviderManifestToken="2012" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
<EntityType Name="Employee">
<Key>
<PropertyRef Name="EmployeeId" />
</Key>
<Property Name="EmployeeId" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="EmployeeType" Type="int" Nullable ="false" DefaultValue="1"/>
<Property Name="FirstName" Type="nvarchar" MaxLength="50" Nullable="false" />
<Property Name="LastName" Type="nvarchar" MaxLength="50" Nullable="false" />
<Property Name="Salary" Type="decimal" Precision="18" Scale="0" Nullable="false" />
<Property Name="Wage" Type="decimal" Precision="18" Scale="0" Nullable="false" />
</EntityType>
<EntityContainer Name="EFRecipesModel1StoreContainer">
<EntitySet Name="Employee" EntityType="Self.Employee" Schema="Chapter2" store:Type="Tables" />
</EntityContainer>
</Schema>
</edmx:StorageModels>
note that I added the Nullable and Default value for EmployeeType. This still doesn't solve the problem.
Could I get some help as to why I'm having such a problem mapping properly?
To me it makes more sense to add DefaultValue to Salary and Wage in the store model:
<Property Name="Salary" Type="decimal" Nullable="false" DefaultValue="0"/>
<Property Name="Wage" Type="decimal" Nullable="false" DefaultValue="0"/>
And also in the conceptual model (in the edmx designer).
It would also make sense to add them as default constraints to the fields in the database (although EF does not copy these constraints to the edmx when the model is generated from the database).
Anyhow, the fields must have a default value since they are not nullable in the database, and none of the concrete entities supply values for both fields.
I ran into a similar issue and what I done was;
Right-click the "Main" entity, and select Properties. Set the Abstract attribute to true, which marked the "Main" entity as abstract.
Just thought I would share this as setting a default value for me stopped ef from complaining but the value was not available when needed in my program .
Related
I am developing MVC 3 Applicaiton.
I have Model first approch.
I have Company Entity(Abstract).
Lead and Customer is inherited from the company entity.
When I tried to validate the model, Its gives an errror.
Error 41 Error 3023: Problem in mapping fragments starting at line
70:Column Companies.Status in table Companies must be mapped: It has
no default value and is not nullable.
Here is the mapping of tables.
And Here is the EDMX code in HTML View.
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="Model1.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator">
<EntityContainer Name="Model1StoreContainer">
<EntitySet Name="Companies" EntityType="Model1.Store.Companies" store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="Companies">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="Name" Type="nvarchar(max)" Nullable="false" />
<Property Name="Status" Type="nvarchar(max)" Nullable="false" />
<Property Name="__Disc__" Type="nvarchar" MaxLength="Max" Nullable="false" />
</EntityType>
</Schema></edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="Model1" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
<EntityContainer Name="Model1Container" annotation:LazyLoadingEnabled="true">
<EntitySet Name="Companies" EntityType="Model1.Company" />
</EntityContainer>
<EntityType Name="Company" Abstract="true">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Type="String" Name="Name" Nullable="false" />
</EntityType>
<EntityType Name="Lead" BaseType="Model1.Company" >
<Property Type="String" Name="Status" Nullable="false" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
<EntityContainerMapping StorageEntityContainer="Model1StoreContainer" CdmEntityContainer="Model1Container">
<EntitySetMapping Name="Companies">
<EntityTypeMapping TypeName="IsTypeOf(Model1.Company)">
<MappingFragment StoreEntitySet="Companies">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="Name" ColumnName="Name" />
<Condition ColumnName="__Disc__" Value="Company" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="Model1.Lead">
<MappingFragment StoreEntitySet="Companies">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="Status" ColumnName="Status" />
<Condition ColumnName="__Disc__" Value="Lead" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping></edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<edmx:Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
<edmx:Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
</DesignerInfoPropertySet>
</edmx:Connection>
<edmx:Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true" />
<DesignerProperty Name="EnablePluralization" Value="True" />
<DesignerProperty Name="DatabaseGenerationWorkflow" Value="$(VSEFTools)\DBGen\Generate T-SQL Via T4 (TPH).xaml" />
</DesignerInfoPropertySet>
</edmx:Options>
<!-- Diagram content (shape and connector positions) -->
<edmx:Diagrams>
<Diagram Name="Model1" >
<EntityTypeShape EntityType="Model1.Company" Width="1.5" PointX="2.375" PointY="0.875" Height="1.2636116536458335" />
<EntityTypeShape EntityType="Model1.Lead" Width="1.5" PointX="3.375" PointY="2.625" Height="1.0992643229166665" />
<InheritanceConnector EntityType="Model1.Lead" >
<ConnectorPoint PointX="3.125" PointY="2.1386116536458335" />
<ConnectorPoint PointX="3.125" PointY="2.325" />
<ConnectorPoint PointX="4.125" PointY="2.325" />
<ConnectorPoint PointX="4.125" PointY="2.625" />
</InheritanceConnector>
</Diagram>
</edmx:Diagrams>
</edmx:Designer>
</edmx:Edmx>
Whats is the issue ?
Put the DefaultValue attribute on the SSDL Status property. It would look like this (I used the empty string):
<Property Name="Status" Type="nvarchar(max)" Nullable="false" DefaultValue=""/>
Since the base entity is abstract you won't be able to create entities of this type so this DefaultValue will not really be used but it should make EF stop complaining.
In your storage model, the Companies.Status column doesn't allow null values.
Since all entities that inherit from Company except the Lead entity will not have the Status property set, you need to either allow null in the Companies.Status column or set a default value to use for the other entities.
My question relates to the "table joining" function in the ADO.NET Entity Framework.
Imagine a database that contains a table "Product" and another table "ProductPrice". The price table stores a history of all price changes for a product, with a start and end date, where the line containing the current price is indicated by a NULL value in the end date column. This database structure could be useful for statistical purposes, for example the average daily sales volume could be mapped to each change in the product price. However, for the online ordering website, only the current price is required.
Here's the structure of the two tables:
Product
ProductID (PK, int, NOT NULL, auto increment)
Name (varchar 50, NOT NULL)
ProductPrice
ProductPriceID (PK, int, NOT NULL, auto increment)
ProductID (INT, NOT NULL)
StartDate (DATETIME, NOT NULL)
EndDate (DATETIME)
Price (MONEY, NOT NULL)
Here's an example of an SQL statement to retrieve the product plus the current price:
SELECT Product.ProductID, Product.Name, ProductPrice.Price AS CurrentPrice
FROM Product
LEFT JOIN ProductPrice
ON Product.ProductID = ProductPrice.ProductID
AND ProductPrice.EndDate IS NULL
I'd like to use the Entity Framework to join the entities Product and ProductPrice together, so that I can access the current price directly from the Product entity, as in the following example:
var product = (from p in context.Product where p.ProductID == 2 select p).FirstOrDefault();
Console.WriteLine(product.Name);
Console.WriteLine(product.CurrentPrice);
Unfortunately, I'm getting stuck with errors that I can't resolve.
Here are the entities from the storage model:
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductID" />
</Key>
<Property Name="ProductID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Name" Type="varchar" Nullable="false" MaxLength="50" />
</EntityType>
<EntityType Name="ProductPrice">
<Key>
<PropertyRef Name="ProductPriceID" />
</Key>
<Property Name="ProductPriceID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="ProductID" Type="int" Nullable="false" />
<Property Name="Price" Type="money" Nullable="false" />
<Property Name="StartDate" Type="datetime" Nullable="false" />
<Property Name="EndDate" Type="datetime" />
</EntityType>
<Association Name="FK_ProductPrice_Product">
<End Role="Product" Type="TestingModel.Store.Product" Multiplicity="1" />
<End Role="ProductPrice" Type="TestingModel.Store.ProductPrice" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Product">
<PropertyRef Name="ProductID" />
</Principal>
<Dependent Role="ProductPrice">
<PropertyRef Name="ProductID" />
</Dependent>
</ReferentialConstraint>
</Association>
And from the conceptual model:
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductID" />
</Key>
<Property Name="ProductID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="false" FixedLength="false" />
<Property Name="SKU" Type="String" Nullable="false" MaxLength="50" Unicode="false" FixedLength="false" />
<Property Type="Decimal" Name="CurrentPrice" Nullable="false" Precision="19" Scale="4" />
</EntityType>
And finally the mapping between the two:
<EntitySetMapping Name="Product">
<EntityTypeMapping TypeName="TestingModel.Product">
<MappingFragment StoreEntitySet="Product">
<ScalarProperty Name="ProductID" ColumnName="ProductID" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="SKU" ColumnName="SKU" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(TestingModel.Product)">
<MappingFragment StoreEntitySet="ProductPrice">
<ScalarProperty Name="CurrentPrice" ColumnName="Price" />
<Condition ColumnName="EndDate" IsNull="true" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
And here are the error messages that I'm currently struggling with:
Error 1 Error 3024: Problem in mapping fragments starting at line 76:Must specify mapping for all key properties (Product.ProductID) of the EntitySet Product.
Error 2 Error 3025: Problem in mapping fragments starting at line 76:Must specify mapping for all key properties (ProductPrice.ProductPriceID) of table ProductPrice.
I'm not sure if this is even possible with the Entity Framework, maybe I should just do the join manually myself in LINQ.
Any suggestions would be greatly appreciated.
You do not meet the conditions for entity splitting:
You should only map an entity type to multiple tables if the following conditions are true:
The tables to which you are mapping share a common key.
The entity type that is being mapped has entries in each underlying table. In other words, the entity type represents data that has a one-to-one correspondence between the two tables; the entity type represents an inner join of the two tables.
You are smarter than EF. You know that the condition EndDate == null yields one record. EF has no clue to know that and thus never knows that it can create one object from the two tables. In terms of the exception message: PriceId and EndDate == null should somehow deliver all key properties of your ProductPrice records, which is impossible, obviously.
Alternatives:
A. You can create a one-to-many association between the two and query:
products.Where(p => p.ProductId == 2)
.Select(p => new
{
Product = p,
CurrentPrice = p.ProductPrices.Where(pp => pp.EndDate == null)
.FirstOrDefault()
})
Not as convenient as you'd like, but maybe not too bad when wrapped in a repository.
B. Create a database view and use separate paths for viewing and updating (which is not an uncommon thing to do).
You need to do a foreign key between the two tables (i.e. on column ProductID)
Then to retrieve all the products together with their prices you need to do the following:
var x = from p in context.Product where p.ProductID == 2 && p.ProductPrice.EndDate == null select new { p.ProductID, p.Name, p.ProductPrice.Price }.FirstOrDefault();
Further to this question:
Entity Framework TPH with multiple abstract inheritance and VS.2008 sp1 .net 3.5 c#
I decided to add Organizations and a School. Organization(abstract) inherits from Party, and School(concrete) inherits from Organization.
I get the error:
Error 1 Error 3034: Problem in Mapping Fragments starting at lines 73, 93: Two entities with different keys are mapped to the same row. Ensure these two mapping fragments do not map two groups of entities with different keys to the same group of rows.
C:\Users\xxx\Documents\Visual Studio 2008\Projects\TEST\TEST\Model1.edmx 74 29 TEST
I've seen the 3034 errors alot along my travels in EF. But typically they relate to navigational properties. I haven't seen errors like this in inheritence.
Here's my edmx xml:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="test_1Model.Store" Alias="Self"
Provider="System.Data.SqlClient" ProviderManifestToken="2005"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
<EntityContainer Name="test_1ModelStoreContainer">
<EntitySet Name="Student" EntityType="test_1Model.Store.Student"
store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="Student">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" Nullable="false"
StoreGeneratedPattern="Identity" />
<Property Name="PartyInfo" Type="varchar(max)" Nullable="false" />
<Property Name="PersonInfo" Type="varchar(max)" Nullable="true" />
<Property Name="StudInfo" Type="varchar(max)" Nullable="true" />
<Property Name="OrgInfo" Type="varchar(max)" Nullable="true" />
<Property Name="SchoolInfo" Type="varchar(max)" Nullable="true" />
<Property Name="TypeOfParty" Type="varchar(max)" Nullable="false" />
</EntityType>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="test_1Model" Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="test_Entities">
<EntitySet Name="PartySet" EntityType="test_1Model.Party" />
</EntityContainer>
<EntityType Name="Party" Abstract="true">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Int32" Nullable="false" />
<Property Name="PartyInfo" Type="String" Nullable="false"
MaxLength="Max" Unicode="false" FixedLength="false" />
</EntityType>
<EntityType Name="Person" BaseType="test_1Model.Party" Abstract="true" >
<Property Name="PersonInfo" Type="String" Nullable="false" />
</EntityType>
<EntityType Name="Student" BaseType="test_1Model.Person" >
<Property Name="StudInfo" Type="String" Nullable="false" />
</EntityType>
<EntityType Name="Organization" BaseType="test_1Model.Party" Abstract="true" >
<Property Name="OrgInfo" Type="String" Nullable="false" />
</EntityType>
<EntityType Name="School" BaseType="test_1Model.Organization" >
<Property Name="SchoolInfo" Type="String" Nullable="false" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S"
xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
<EntityContainerMapping
StorageEntityContainer="test_1ModelStoreContainer"
CdmEntityContainer="test_Entities">
<EntitySetMapping Name="PartySet">
<EntityTypeMapping TypeName="IsTypeOf(test_1Model.Party)">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="PartyInfo" ColumnName="PartyInfo" />
<ScalarProperty Name="Id" ColumnName="Id" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(test_1Model.Person)">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="PersonInfo" ColumnName="PersonInfo" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="test_1Model.Student">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="StudInfo" ColumnName="StudInfo" />
<ScalarProperty Name="PersonInfo" ColumnName="PersonInfo" />
<ScalarProperty Name="PartyInfo" ColumnName="PartyInfo" />
<Condition ColumnName="TypeOfParty" Value="STUDENT" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(test_1Model.Organization)">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="OrgInfo" ColumnName="OrgInfo" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="test_1Model.School">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="OrgInfo" ColumnName="OrgInfo" />
<ScalarProperty Name="PartyInfo" ColumnName="PartyInfo" />
<ScalarProperty Name="SchoolInfo" ColumnName="SchoolInfo" />
<Condition ColumnName="TypeOfParty" Value="SCHOOL" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<edmx:Designer xmlns="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing"
Value="EmbedInOutputAssembly" />
</DesignerInfoPropertySet>
</edmx:Connection>
<edmx:Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true" />
</DesignerInfoPropertySet>
</edmx:Options>
<!-- Diagram content (shape and connector positions) -->
<edmx:Diagrams>
<Diagram Name="SqlServer_Model" >
<EntityTypeShape EntityType="test_1Model.Party" Width="1.5" PointX="1.25" PointY="3.25" Height="1.4033821614583326" />
<InheritanceConnector EntityType="test_1Model.Person" ManuallyRouted="false">
<ConnectorPoint PointX="2" PointY="4.6533821614583326" />
<ConnectorPoint PointX="2" PointY="5.75" />
</InheritanceConnector>
<EntityTypeShape EntityType="test_1Model.Organization" Width="1.5" PointX="3.875" PointY="5.625" Height="1.2110807291666665" />
<InheritanceConnector EntityType="test_1Model.Organization">
<ConnectorPoint PointX="2.75" PointY="3.9516910807291663" />
<ConnectorPoint PointX="4.625" PointY="3.9516910807291663" />
<ConnectorPoint PointX="4.625" PointY="5.625" />
</InheritanceConnector>
<EntityTypeShape EntityType="test_1Model.School" Width="1.5" PointX="3.875" PointY="7.875" Height="1.2110807291666657" />
<InheritanceConnector EntityType="test_1Model.School">
<ConnectorPoint PointX="4.625" PointY="6.8360807291666665" />
<ConnectorPoint PointX="4.625" PointY="7.875" />
</InheritanceConnector>
</Diagram>
</edmx:Diagrams>
</edmx:Designer>
</edmx:Edmx>
I know this question is ages old, but I spent a long time researching a similar problem in a recent adventure with EF 5.0 (and 6.0.0-beta1), and this article led me to a valid solution. Thanks to the author.
I have a complex hierarchy mapped using TPH:
A1 - B1 - C1
A1 - B1 - C2
A1 - B2 - C3
A1 - B2 - C4
A1 - B2 - C5
A1 - B2 - C6 - D1 - E1
A1 - B2 - C6 - D1 - E2
A1 - B2 - C6 - D1 - E3
A1 - B2 - C6 - D2 - E4
A1 - B2 - C6 - D2 - E5
A1 - B2 - C6 - D2 - E6
A1 - B2 - C6 - D3 - E7
A1 - B2 - C6 - D3 - E8
A1 - B2 - C6 - D3 - E9
Where bold indicates abstract, and italic indicates additional mappings specific to that class (and subclasses) to the same table (of course).
Basically the solution was to create 4 discriminator columns in the table, and map each level in the hierarchy to a different discriminator column. Note that the abstract classes in level B and below also need to be mapped and have a discriminator value specified.
A lot of time wasted on this, all the examples and documentation of TPH seem to only cover the basics. In the real world things can get a little more complex!
Hope this helps someone.
You seem to be trying to do table-per-hierarchy mapping (because I see only one table in your SSDL), but I don't see that you have set up a discriminator mapping in the CSDL. There should be a Condition node, like this:
<Condition ColumnName="NotificationType" Value="Announcement"/>
In short, if you are trying to use a table per hierarchy mapping, you should review the instructions for configuring this and follow them, because you have not completed all of the steps.
If, on the other hand, you intend to do table per type mapping, then you seem to have missed a couple of tables when you updated from your database.
I'm trying to do a Table Per Hierarchy model in Entity Framework(VS 2008 sp1, 3.5).
Most of my models have been very simple, an abstract type with multiple sub-types that inherit from it.
However, I've been struggling with this latest challenge. I have STUDENTS that I'd like to inherit from PERSONS(abstract) that should inherity from PARTIES(abstract).
Every time I do this I get a "Error 2078: The EntityType 'Model.PERSONS' is Abstract and can be mapped only using IsTypeOf." I guess the problem is PARTIES is already defined as IsTypeOf in the entity set.
So is this even possible? I can work around it by making PERSONS abstract = false and assigning a bogus conditional mapping. But this seems like a silly workaround.
Edit the model using XML Editor: find the mapping and add IsTypeOf to the corresponding EntityTypeMapping tag.
Here is an example resembling your case:
SQL Server DDL:
CREATE TABLE [dbo].[Student] (
[Id] [int] IDENTITY(1,1) NOT NULL,
[PartyInfo] [varchar](max) NOT NULL,
[PersonInfo] [varchar](max) NOT NULL,
[StudInfo] [varchar](max) NOT NULL,
CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ( [Id] ASC )
)
EDMX:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="test_1Model.Store" Alias="Self"
Provider="System.Data.SqlClient" ProviderManifestToken="2005"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
<EntityContainer Name="test_1ModelStoreContainer">
<EntitySet Name="Student" EntityType="test_1Model.Store.Student"
store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="Student">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" Nullable="false"
StoreGeneratedPattern="Identity" />
<Property Name="PartyInfo" Type="varchar(max)" Nullable="false" />
<Property Name="PersonInfo" Type="varchar(max)" Nullable="false" />
<Property Name="StudInfo" Type="varchar(max)" Nullable="false" />
</EntityType>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="test_1Model" Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="test_Entities">
<EntitySet Name="PartySet" EntityType="test_1Model.Party" />
</EntityContainer>
<EntityType Name="Party" Abstract="true">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Int32" Nullable="false" />
<Property Name="PartyInfo" Type="String" Nullable="false"
MaxLength="Max" Unicode="false" FixedLength="false" />
</EntityType>
<EntityType Name="Person" BaseType="test_1Model.Party" Abstract="true" >
<Property Name="PersonInfo" Type="String" Nullable="false" />
</EntityType>
<EntityType Name="Student" BaseType="test_1Model.Person" >
<Property Name="StudInfo" Type="String" Nullable="false" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S"
xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
<EntityContainerMapping
StorageEntityContainer="test_1ModelStoreContainer"
CdmEntityContainer="test_Entities">
<EntitySetMapping Name="PartySet">
<EntityTypeMapping TypeName="IsTypeOf(test_1Model.Party)">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="PartyInfo" ColumnName="PartyInfo" />
<ScalarProperty Name="Id" ColumnName="Id" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(test_1Model.Person)">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="PersonInfo" ColumnName="PersonInfo" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="test_1Model.Student">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="PartyInfo" ColumnName="PartyInfo" />
<ScalarProperty Name="PersonInfo" ColumnName="PersonInfo" />
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="StudInfo" ColumnName="StudInfo" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<edmx:Designer xmlns="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing"
Value="EmbedInOutputAssembly" />
</DesignerInfoPropertySet>
</edmx:Connection>
<edmx:Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true" />
</DesignerInfoPropertySet>
</edmx:Options>
<!-- Diagram content (shape and connector positions) -->
<edmx:Diagrams>
<Diagram Name="SqlServer_Model" />
</edmx:Diagrams>
</edmx:Designer>
</edmx:Edmx>
I've got a project in written in the EDM. Does anyone know how to get the value of the discriminator column from an entity in a TPH inheritance tree? I can't add the property to the entity. Is there any other way to get the value?
Thanks,
Roy
There is a simple way to solve the problem for the DBMS that suports updatable views.
You can simply create a view or a Defining Query having an additional discriminator column.
The original column should be mapped to the class property. In case when DBMS does not support the updatable views you can use Defining Query and then map stored procedures for Insert/Update/Delete operations.
Here is an example for Oracle database:
SQL:
CREATE TABLE TEST.TPH_TABLE (
ID NUMBER(9),
COLUMN_A VARCHAR2(20),
COLUMN_B VARCHAR2(20),
BASE_COLUMN VARCHAR2(20),
CONSTRAINT PK_TPH_TABLE PRIMARY KEY (ID)
);
EDMX:
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="Model1.Store" Alias="Self" Provider="Devart.Data.Oracle" ProviderManifestToken="ORA" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
<EntityContainer Name="Model1StoreContainer">
<EntitySet Name="TPH_TABLE" EntityType="Model1.Store.TPH_TABLE" >
<DefiningQuery>
SELECT ID, BASE_COLUMN, COLUMN_A, COLUMN_B,
CASE WHEN COLUMN_A IS NOT NULL THEN '1' ELSE NULL END AS "IS_TABLE_A"
FROM TPH_TABLE
</DefiningQuery>
</EntitySet>
</EntityContainer>
<EntityType Name="TPH_TABLE">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="int" Nullable="false" />
<Property Name="BASE_COLUMN" Type="VARCHAR2" MaxLength="20" />
<Property Name="COLUMN_A" Type="VARCHAR2" MaxLength="20" />
<Property Name="COLUMN_B" Type="VARCHAR2" MaxLength="20" />
<Property Name="IS_TABLE_A" Type="VARCHAR2" MaxLength="1" />
</EntityType>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="Model1" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="Entities">
<EntitySet Name="TPH_TABLE" EntityType="Model1.TPH_TABLE" />
</EntityContainer>
<EntityType Name="TPH_TABLE" Abstract="true">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Int32" Nullable="false" />
<Property Name="BASE_COLUMN" Type="String" MaxLength="20" Unicode="true" FixedLength="false" />
</EntityType>
<EntityType Name="TABLE_A" BaseType="Model1.TPH_TABLE" >
<Property Name="COLUMN_A" Type="String" Nullable="true" />
</EntityType>
<EntityType Name="TABLE_B" BaseType="Model1.TPH_TABLE" >
<Property Name="COLUMN_B" Type="String" Nullable="true" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
<EntityContainerMapping StorageEntityContainer="Model1StoreContainer" CdmEntityContainer="Entities">
<EntitySetMapping Name="TPH_TABLE">
<EntityTypeMapping TypeName="Model1.TABLE_A">
<MappingFragment StoreEntitySet="TPH_TABLE">
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="BASE_COLUMN" ColumnName="BASE_COLUMN" />
<ScalarProperty Name="COLUMN_A" ColumnName="COLUMN_A" />
<Condition ColumnName="IS_TABLE_A" Value="1" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="Model1.TABLE_B">
<MappingFragment StoreEntitySet="TPH_TABLE">
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="BASE_COLUMN" ColumnName="BASE_COLUMN" />
<ScalarProperty Name="COLUMN_B" ColumnName="COLUMN_B" />
<Condition ColumnName="IS_TABLE_A" IsNull="true" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<edmx:Designer xmlns="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
</DesignerInfoPropertySet>
</edmx:Connection>
<edmx:Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true" />
</DesignerInfoPropertySet>
</edmx:Options>
<!-- Diagram content (shape and connector positions) -->
<edmx:Diagrams>
<Diagram Name="Model4">
<EntityTypeShape EntityType="Model1.TPH_TABLE" Width="1.5" PointX="3" PointY="0.5" Height="1.2636116536458335" IsExpanded="true" />
<EntityTypeShape EntityType="Model1.TABLE_A" Width="1.5" PointX="1.75" PointY="2.75" Height="1.099264322916667" />
<EntityTypeShape EntityType="Model1.TABLE_B" Width="1.5" PointX="4.25" PointY="2.75" Height="1.099264322916667" />
<InheritanceConnector EntityType="Model1.TABLE_B" ManuallyRouted="false">
<ConnectorPoint PointX="4.375" PointY="1.7636116536458335" />
<ConnectorPoint PointX="4.375" PointY="2.75" />
</InheritanceConnector>
<InheritanceConnector EntityType="Model1.TABLE_A" ManuallyRouted="false">
<ConnectorPoint PointX="3.125" PointY="1.7636116536458335" />
<ConnectorPoint PointX="3.125" PointY="2.75" />
</InheritanceConnector></Diagram></edmx:Diagrams>
</edmx:Designer>
</edmx:Edmx>