Entity Framework magically interprets the following table structure as a many-to-many relationship.
table foo (int id)
table foo_bar (int foo_id, int bar_id)
table bar (int id)
But if the join table has any additional fields it will instead be interpreted as two one-to-many relationships.
I am using a database in which the join table has a surrogate key as primary key. Because of this EF interprets it as two one-to-many relationships.
table foo (int id)
table foo_bar (int surrogate_pk, int foo_id, int bar_id)
table bar (int id)
Is it possible to modify EF:s interpretation to make it an actual many-to-many relationship in the model? Can it be done using the designer?
it's possible, but it requires quite a bit of manual work in the EDMX file, and I haven't been able to make EF use the surrogate key as actual primary key on the link table. You have to make EF use a combination key of both foo_id and bar_id as primary key.
in your storage model you have to change the EntityType of the link table from
<EntityType Name="foo_bar">
<Key>
<PropertyRef Name="surrogate_pk" />
</Key>
<Property Name="surrogate_pk" Type="bigint" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="foo_id" Type="int" Nullable="false" StoreGeneratedPattern="None" />
<Property Name="bar_id" Type="int" Nullable="false" StoreGeneratedPattern="None" />
</EntityType>
to:
<EntityType Name="foo_bar">
<Key>
<PropertyRef Name="foo_id" />
<PropertyRef Name="bar_id" />
</Key>
<Property Name="foo_id" Type="int" Nullable="false" StoreGeneratedPattern="None" />
<Property Name="bar_id" Type="int" Nullable="false" StoreGeneratedPattern="None" />
</EntityType>
So you make the surrogate key invisible to EF, and tell it to use the combination of the two foreign keys as primary key.
In your conceptual model, you need to have the many-many association defined:
<Association Name="foo_bar_association">
<End Role="foo" Type="foo" Multiplicity="*" />
<End Role="bar" Type="bar" Multiplicity="*" />
</Association>
and in your mappings, an AssociationSetMapping:
<AssociationSetMapping Name="foo_bar_association" TypeName="foo_bar_association" StoreEntitySet="foo_bar">
<EndProperty Name="foo">
<ScalarProperty Name="id" ColumnName="foo_id" />
</EndProperty>
<EndProperty Name="bar">
<ScalarProperty Name="id" ColumnName="bar_id" />
</EndProperty>
</AssociationSetMapping>
By far the easiest way to get this right, is to remove the surrogate key from the db, generate the EDMX, and then put this model on your original DB. The result will be the same. EF doesn't really need the surrogate key for anything, the table is invisible in a many-many association
I am positive that this cannot be done using the designer. I don't know if there is a way to do it in EDMX manually, but I have never seen an example of it. One possible workaround might be to not map the surrogate key at all. If you can generate that on the database, you might not need it in your model.
Related
Using Schema First, I have a database structure, as so
ExternalDataItems
---
edataitem_id PK -- surrogate auto-increment - NOT for FK relation here
datahash UX -- Candidate Key / Unique Index (binary(20))
ExternalMaps
---
emap_id PK
ext_datahash FK on ExternalDataItems.datahash - NOT referencing the surrogate PK
and after generating the SSDL/CSDL1 has this
<Association Name="FK_ExtMaps_ExtDataItems">
<End Multiplicity="1" Role="ExternalDataItems" Type="Store.ExternalDataItems" />
<End Multiplicity="*" Role="ExternalMaps" Type="Store.ExternalMaps" />
<ReferentialConstraint> <!-- error on this element -->
<Principal Role="ExternalDataItems">
<PropertyRef Name="datahash" />
</Principal>
<Dependent Role="ExternalMaps">
<PropertyRef Name="ext_datahash" />
</Dependent>
</ReferentialConstraint>
</Association>
which generates an error on the <ReferentialConstraint> element
Running transformation: Properties referred by the Principal Role ExternalDataItems must be exactly identical to the key of the EntityType ExternalDataItem referred to by the Principal Role in the relationship constraint for Relationship FK_ExtMaps_ExtDataItems. Make sure all the key properties are specified in the Principal Role.
The "Principal Role" (?) for ExternalDataItems SSDL looks like the following, for the PK, and the UX does not appear to be present2, except as a simple scalar property:
<EntityType Name="ExternalDataItems">
<Key>
<PropertyRef Name="edataitem_id" />
</Key>
..
<Property Name="datahash" Type="binary" MaxLength="20" Nullable="false" />
</EntityType>
How can I add this Relation - using a FK to a non-PK Candidate Key? (After this "works" I'll also want to also have a Navigation Property to the CSDL.)
Furthermore, the association line does not appear in the design surface - which I suspect is just fallout from this error. I am using Entity Framework version 6.1.1 (latest published on nuget) and Visual Studio 2013 Ultimate Update 4.
1The standard EDMX "Update from Database" didn't appear to pick up these FK relations (which may be related to this bug) and the results above are after using the Huagati DBML/EDMX tooling. If I tried to "Add Association" prior the designer would incorrectly try and use the primary key - which is not supported by any FK relation - and did not provide options for choosing alternative properties.
2Attempting to add a <UniqueConstraint> element as described in "Unique Constraints in the Entity Framework" results in the friendly XML validation error:
The element 'ElementType' .. has an invalid child element 'UniqueConstraint'.
Looks like "missing important feature"
.. The Entity Framework currently only supports basing referential constraints on primary keys and does not have a notion of a unique constraint.
You can remove foreign key in DB scheme and use LINQ to join tables:
from item in ExternalDataItems
join map in ExternalMaps on item.datahash = map.ext_datahash
select new { item.edataitem_id, map.emap_id };
Also you can create the VIEW with these joined tables and use the one.
In a database I have Reservations and OldReservations tables, where OldReservations is a copy ( of Reservations table ) and is used to store old reservations. Both tables use autogenerated identity keys, but first has a seed of 0 and second has a seed of 1000, so key values in the two tables don't overlap. Here are the steps I did to create TPC inheritance:
1) I derived OldReservation entity from Reservation entity
2) I removed the overlapping properties from OldReservation entity
3) I've then mapped the OldReservations table fields in the XML of the EDMX file:
<EntitySetMapping Name="Reservations">
<EntityTypeMapping TypeName="IsTypeOf(BAModel.Reservation)">
<MappingFragment StoreEntitySet="Reservations">
<ScalarProperty Name="ReservationID" ColumnName="ReservationID" />
<ScalarProperty Name="ReservationDate" ColumnName="ReservationDate" />
<ScalarProperty Name="EventID" ColumnName="EventID" />
<ScalarProperty Name="ContactID" ColumnName="ContactID" />
<ScalarProperty Name="RowVersion" ColumnName="RowVersion" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(BAModel.OldReservation)">
<MappingFragment StoreEntitySet="OldReservations">
<ScalarProperty Name="ReservationID" ColumnName="ReservationID" />
<ScalarProperty Name="ReservationDate" ColumnName="ReservationDate" />
<ScalarProperty Name="EventID" ColumnName="EventID" />
<ScalarProperty Name="ContactID" ColumnName="ContactID" />
<ScalarProperty Name="RowVersion" ColumnName="RowVersion" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
After I do the above steps the project compiles without any errors, but when I try to insert a new row into OldReservations table I get:
UpdateException: A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns. ---> System.ArgumentException: An item with the same key has already been added.
var reservation = new OldReservation();
reservation.ReservationDate = DateTime.Now;
reservation.ContactID = 129;
context.Reservations.AddObject(reservation);
context.SaveChanges();
This exception is thrown before EF manages to send insert command to the database. Any ideas why I'm getting the exception?
Thank you
You're getting this exception because you're using 'IsTypeOf(BAModel.Reservation)' instead of just 'BAModel.Reservation'. Because of this not just BAModel.Reservation but also types derived from BAModel.Reservation are mapped to the Reservations Table. So what happens is that an OldReservation is mapped to the Reservations Table and the OldReservations Table.
According to cascade deleting, i wrote below codes but there is an error :
An error occurred while updating the entries. See the inner exception for details.
using (doctorEntities de = new doctorEntities())
{
var delete_base_print = (from Table_infobase_print tip in de.Table_infobase_print
where tip.ID == ((Doctor.Table_infobase_print)(datagrid_table_infobase_print.SelectedItem)).ID
select tip).First();
de.DeleteObject(delete_base_print);
de.SaveChanges();
}
<Association Name="FK_Table_infodetail_print_Table_infobase_print">
<End Role="Table_infobase_print" Type="doctorModel.Table_infobase_print" Multiplicity="1">
<OnDelete Action="Cascade" />
</End>
<End Role="Table_infodetail_print" Type="doctorModel.Table_infodetail_print" Multiplicity="*" >
</End>
<ReferentialConstraint>
<Principal Role="Table_infobase_print">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="Table_infodetail_print">
<PropertyRef Name="ID_infobase" />
</Dependent>
</ReferentialConstraint>
</Association>
<OnDelete Action="Cascade" />
This only covers the loaded entities in your object context (it will cascade deleted related entities), but not the database itself. Make sure you have set up cascaded delete constraint on the database for the corresponding table.
Edit:
To set cascading deletes in SQL Server, set the delete rule for the foreign key relationship to cascade. For this open up SQL Server Management Studio, open up the table in question for design and show the foreign key relationship. Set Cascade as Delete Rule within the INSERT and UPDATE specification.
OK this problem has it all.
Conceptually I have a Resource entity which can have many Child Resources and many Parent Resources. The Resource table has two fields, ID and Name with ID being the primary key.
To complete the many to many relationship I created a ResourceHierarchy table which has two fields, (Parent_ID, Child_ID) and two foreign keys with each one referencing the primary key of the Resource table (ID) and the ResourceHierarchy table has a composite primary key of (Parent_ID, Child_ID)
Now we already know that each Resource can act as a Parent or a Child to other resources, however logically not all Resources will have a Parent but that's besides the point. As an example lets say I have the following Resources in my Resource table.
ID Name
10000 Little House
10001 Font Door
10002 Roof
10003 Roof Tile
10004 Tile Monster
And in the ResourceHierarchy table we have the following relationships.
Parent_ID Child_ID
10000 10001
10000 10002
10002 10003
10004 10003
Then Entity Framework generates the Entity, so far so good...
If you were to check the generated code in the edmx file you would see that the ResourceHierarchy table is being treated as a relationship and the ResourceHierarchy table is not accessible via code because it's not being treated as an entity.
If this is all I wanted then it would work out perfectly.
However the problem starts when I want to add a quantity column to the Resource entity hierarchy. For example the little house has just one Front Door and one Roof, but the Roof and Tile Monster Resources can have many Roof Tiles.
So if we add a Quantity column to the Resource table then we get the following.
ID Name Quantity
10000 Little House 1
10001 Font Door 1
10002 Roof 1
10003 Roof Tile 5
10004 Tile Monster 1
This creates the problem that the Roof and Tile Monster must share the 5 Roof Tiles. So naturally I would try to add the quantity column to the ResourceHierarchy table, however as soon as I do this and refresh the generated code is now treating the ResourceHierarchy table as an entity and not a relationship as it was previously. And now in order to get back to the Resource table I have to go through this non conceptual "Entity/Relationship" which isn't very straight forward. It's like I have a Entity in my conceptual model which would only be used to traverse back to the Resource Entity, and I'm not even sure if Resource.Children.Add(r) would create new rows in the ResourceHierarchy table in the db. It's like I would be picking off properties i.e. Quantity, off of an entity that I am only using as a relationship.
Ideally the ResourceHierarchy table would have the Quantity column look like this.
Parent_ID Child_ID Quantity
10000 10001 1
10000 10002 1
10002 10003 8
10004 10003 13
AND the Resource Entity would still have Children, Parents navigation properties and somehow access the Quantity column as a property of the Resource Entity.
I've tried to merge the generated code from having a quantity column and not having a quantity column but an exception is thrown which I interpret as the ResourceHierarchy table can either be a relationship or an entity, but not both.
Please HELP!
The edmx changes drastically with the addition and exclusion of the quantity column on the ResourceHierarchy table in the db.
Here is a sample comparison, the only difference is Resource is ResourceType and ResourceHierarchy is ResourceTypeHierarchy.
The SSDL Storage Model has no changes except one extra Property in the ResourceTypeHierarchy EntityType so I won't include it below.
WITHOUT QUANTITY COLUMN ON RESOURCETYPEHIERARCHY
RESOURCETYPEHIERARCHY IS A RELATIONSHIP
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="MyModel" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityContainer Name="MyEntities">
<EntitySet Name="ResourceTypes" EntityType="MyModel.ResourceType" />
<AssociationSet Name="ResourceTypeHierarchy" Association="MyModel.ResourceTypeHierarchy">
<End Role="ResourceType" EntitySet="ResourceTypes" />
<End Role="ResourceType1" EntitySet="ResourceTypes" /></AssociationSet></EntityContainer>
<EntityType Name="ResourceType">
<Key>
<PropertyRef Name="ID" /></Key>
<Property Type="Int32" Name="ID" Nullable="false" />
<Property Type="String" Name="Type" Nullable="false" MaxLength="25" FixedLength="false" Unicode="false" />
<NavigationProperty Name="Parents" Relationship="MyModel.ResourceTypeHierarchy" FromRole="ResourceType" ToRole="ResourceType1" />
<NavigationProperty Name="Children" Relationship="MyModel.ResourceTypeHierarchy" FromRole="ResourceType1" ToRole="ResourceType" /></EntityType>
<Association Name="ResourceTypeHierarchy">
<End Type="MyModel.ResourceType" Role="ResourceType" Multiplicity="*" />
<End Type="MyModel.ResourceType" Role="ResourceType1" Multiplicity="*" /></Association></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="MyModelStoreContainer" CdmEntityContainer="MyEntities">
<EntitySetMapping Name="ResourceTypes">
<EntityTypeMapping TypeName="IsTypeOf(MyModel.ResourceType)">
<MappingFragment StoreEntitySet="ResourceType">
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="Type" ColumnName="Type" /></MappingFragment></EntityTypeMapping></EntitySetMapping>
<AssociationSetMapping Name="ResourceTypeHierarchy" TypeName="MyModel.ResourceTypeHierarchy" StoreEntitySet="ResourceTypeHierarchy">
<EndProperty Name="ResourceType1">
<ScalarProperty Name="ID" ColumnName="Parent_ID" /></EndProperty>
<EndProperty Name="ResourceType">
<ScalarProperty Name="ID" ColumnName="Child_ID" /></EndProperty></AssociationSetMapping></EntityContainerMapping>
</Mapping>
</edmx:Mappings>
WITH QUANTITY COLUMN ON RESOURCETYPEHIERARCHY
RESOURCETYPEHIERARCHY IS NOW AN ENTITY
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
<EntityContainerMapping StorageEntityContainer="MyModelStoreContainer" CdmEntityContainer="MyEntities">
<EntitySetMapping Name="ResourceTypes">
<EntityTypeMapping TypeName="IsTypeOf(MyModel.ResourceType)">
<MappingFragment StoreEntitySet="ResourceType">
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="Type" ColumnName="Type" /></MappingFragment></EntityTypeMapping></EntitySetMapping>
<EntitySetMapping Name="ResourceTypeHierarchies">
<EntityTypeMapping TypeName="IsTypeOf(MyModel.ResourceTypeHierarchy)">
<MappingFragment StoreEntitySet="ResourceTypeHierarchy">
<ScalarProperty Name="Child_ID" ColumnName="Child_ID" />
<ScalarProperty Name="Parent_ID" ColumnName="Parent_ID" />
<ScalarProperty Name="Quantity" ColumnName="Quantity" /></MappingFragment></EntityTypeMapping></EntitySetMapping>
<AssociationSetMapping Name="FK_Child" TypeName="MyModel.FK_Child" StoreEntitySet="ResourceTypeHierarchy">
<EndProperty Name="ResourceTypeHierarchy">
<ScalarProperty Name="Child_ID" ColumnName="Child_ID" />
<ScalarProperty Name="Parent_ID" ColumnName="Parent_ID" /></EndProperty>
<EndProperty Name="ResourceType">
<ScalarProperty Name="ID" ColumnName="Child_ID" /></EndProperty></AssociationSetMapping>
<AssociationSetMapping Name="FK_Parent" TypeName="MyModel.FK_Parent" StoreEntitySet="ResourceTypeHierarchy">
<EndProperty Name="ResourceTypeHierarchy">
<ScalarProperty Name="Child_ID" ColumnName="Child_ID" />
<ScalarProperty Name="Parent_ID" ColumnName="Parent_ID" /></EndProperty>
<EndProperty Name="ResourceType">
<ScalarProperty Name="ID" ColumnName="Parent_ID" /></EndProperty></AssociationSetMapping></EntityContainerMapping>
</Mapping>
</edmx:Mappings>
I believe this is working as espected. What I mean is, if you put an attribute on a table that establishes a relation between two entities, that table MUST (from data representation theory at least) be represented as a proper entity. If you think about this carefully you'll understand why. The quantity you mentioned is an attribute of the relation not of any of the related entities. As such that table must be treated as an entity and not as a relationship.
Now on how to overcome this, one thing that comes to my mind (although I'm not sure if this will completly solve your problem) is to treat the relationship as you originally thought (without the quantity) and have another table (that will be mapped to an Entity in you model) that stores the quantity of a certain relation. I think that this table can even have a foreign key constraint on you db to the original relationship table, although this foreign key can't be mapped to a relation on your model (because you have no entity for the endpoint), but this still allows you to mantain data integrity on you storage.
Hope this helps,
VĂtor
My data model contains two tables with composite primary keys and an associative table. Part of the composite primary key is common between the tables.
SitePrivilege
-------------
SiteId
PrivilegeId
UserSite
--------
SiteId
UserId
UserSitePrivilege
-----------------
UserId
SiteId
PrivilegeId
I have created a SitePrivilege entity and a UserSite entity. I have mapped a many-to-many association between them to UserSitePrivilege.
<Association Name="UserSiteSitePrivilege">
<End Type="PrivilegeModel.UserSite" Multiplicity="*" Role="UserSite" />
<End Type="PrivilegeModel.SitePrivilege" Multiplicity="*" Role="SitePrivilege" />
</Association>
...
<AssociationSetMapping Name="UserSiteSitePrivilege" TypeName="PrivilegeModel.UserSiteSitePrivilege" StoreEntitySet="UserSitePrivilege">
<EndProperty Name="SitePrivilege">
<ScalarProperty Name="PrivilegeId" ColumnName="PrivilegeId" />
<ScalarProperty Name="SiteId" ColumnName="SiteId" />
</EndProperty>
<EndProperty Name="UserSite">
<ScalarProperty Name="SiteId" ColumnName="SiteId" />
<ScalarProperty Name="UserId" ColumnName="UserId" />
</EndProperty>
</AssociationSetMapping>
The above code produces this error:
Each of the following columns in table
UserSitePrivilege is mapped to
multiple conceptual side properties:
UserSitePrivilege.SiteId is mapped to
UserSiteSitePrivilegeSitePrivilege.SiteId,
UserSiteSitePrivilege.UserSite.SiteId
So I added a referential constraint.
<Association Name="UserSiteSitePrivilege">
<End Type="PrivilegeModel.UserSite" Multiplicity="*" Role="UserSite" />
<End Type="PrivilegeModel.SitePrivilege" Multiplicity="*" Role="SitePrivilege" />
<ReferentialConstraint>
<Principal Role="UserSite">
<PropertyRef Name="SiteId"/>
</Principal>
<Dependent Role="SitePrivilege">
<PropertyRef Name="SiteId"/>
</Dependent>
</ReferentialConstraint>
</Association>
...
<AssociationSetMapping Name="UserSiteSitePrivilege" TypeName="PrivilegeModel.UserSiteSitePrivilege" StoreEntitySet="UserSitePrivilege">
<EndProperty Name="SitePrivilege">
<ScalarProperty Name="PrivilegeId" ColumnName="PrivilegeId" />
<ScalarProperty Name="SiteId" ColumnName="SiteId" />
</EndProperty>
<EndProperty Name="UserSite">
<ScalarProperty Name="SiteId" ColumnName="SiteId" />
<ScalarProperty Name="UserId" ColumnName="UserId" />
</EndProperty>
</AssociationSetMapping>
Now it produces this error:
Properties referred by the Principal
Role UserSite must be exactly
identical to the key of the EntityType
PrivilegeModel.UserSite referred to by
the Principal Role in the relationship
constraint for Relationship
PrivilegeModel.UserSiteSitePrivilege.
Make sure all the key properties are
specified in the Principal Role.
How do I correctly model this relationship?
Overlapping FKs like this are not supported in 3.5 SP1.
i.e.
UserSitePrivilege
----------
UserId
SiteId
PrivilegeId
PK => UserId, SitedId, PrivilegeId
FK1 => UserId, SiteId
FK2 => SiteId, PrivilegeId
FK1 overlaps with FK2. This is going to be supported as of Beta2 of EF 4. This is because FK Associations (which is available in Beta2) are much more flexible than Independent Associations (what you have in 3.5 SP1 and 4.0 Beta 1).
See this post for more on FK Associations
In the meantime your only option is probably to hide all of this behind DefiningQueries and CUD procedures etc.
Alex
If your primary key is a compound key, all your foreign key relationships must also be using the entire compound key (all key columns) for reference - I don't see any way to get around this, this is a basic tenet of relational database design, really.
This is definitely one of the major reasons I would probably choose to use a substitute column on the main table for the primary key, instead of a compound key made up from actual data columns.
UPDATE: yes, based on your comment, you're absolutely right - the DB design is solid. Not quite sure why EF can't deal with this....
Marc