EF6: Multiple added entities may have the same primary key - c#

I've inherited a database and I need to insert data using EF6. I get the error:
DbUpdateException: Unable to determine the principal end of the 'POSModel.FK_KitMemberTaxRaw_KitMemberSaleReturnRaw_KitMemberSaleReturnRowId' relationship. Multiple added entities may have the same primary key.
I deserialize XML to the POCO objects using DataContractSerializer.
I'm using the object references from the xml document's structure to define the relationships. The POCO objects are generated using a t4 script provided from the NuGet package (which does not work with either deserializer well at all!)
I've decorated KitMemberTaxRaw like so:
[ForeignKey("KitMemberSaleReturnRaw")]
public virtual KitMemberSaleReturnRaw KitMemberSaleReturnRaw { get; set; }
[ForeignKey("KitMemberKitMemberSaleReturnRaw")]
public virtual KitMemberKitMemberSaleReturnRaw KitMemberKitMemberSaleReturnRaw { get; set; }
The KitMemberTaxRaw table may be joined to either table KitMemberKitMemberSaleReturnRaw or KitMemberSaleReturnRaw (but not both).
How does EF determine 'the principal end of the relationship'?

The issue turned out to be EF6 was not able to automatically understand a table with links to a parent and an optional grand parent. The navigation properties generated by the Microsoft provided template were correct but insufficient.
To cure the issue I manually created temporary primary keys for the relationship it did not understand.
Note: The DataContractSerializer class I used created the POCO objects creates an array for the instantiated navigation properties. I had to change the template to generate IList<> properties instead of ICollection<>. At run time there were errors because the array could not be dynamically resized.

Related

EFCore - How to exclude owned objects from automatic loading?

I'm trying to wrap my head around EF Cores owned objects and how i can control when to load certain chunks of data.
Basically i'm having a bunch of old legacy tables (some with ~150 columns) and want to model them using a root entity and several owned objects per table to achieve better segmentation and bundle certain functionalities. Example: There is an "article" entity containing ~20 properties for the most important fields of the underlying table. That entity also contains an OwnedObject "StorageDetails" wrapping a dozen more fields (and all the functions concerned with storing stuff).
Problem: I can't find a way to control if an owned object should be loaded immediatly or not. For some of them i would prefer to load them explicitly using Include() ...
public class Article : EntityBase
{
public string ArticleNumber { get;set; }
// Owned object, shares article number as key.
public StorageDetails StorageStuff { get; set; }
// An Entity from another table having a foreign key reference
public SomeOtherEntity OtherStuff { get; set; }
}
public class StorageDetails : OwnedObject<Article>
{
public Article Owner { get; set; }
}
// Somewhere during model creation ...
builder.OwnsOne(article => article.StorageStuff);
builder.HasOne(article => article.OtherStuff )
...
Defining the model with OwnsOne and loading an article immediatly loads the StorageStuff. To load the OtherThing i have to Inlcude() it in a query, which is basically what i want to achieve for the owned object.
Is that possible? If not, what other approach could you point me to?
Update: I wasn't exact right - the implicit eager loading of owned entity types is really a default and can be changed via SetIsEagerLoaded metadata API (EF Core 3.0+)
modelBuilder.Entity<Article>().OwnsOne(e => e.StorageStuff)
.Metadata.PrincipalToDependent?.SetIsEagerLoaded(false);
or AutoInclude fluent API (EF Core 5.0+)
modelBuilder.Entity<Article>().Navigation(e => e.StorageStuff)
.AutoInclude(false);
This allows eager loading them via Include / ThenInclude. But due to EF Core implementation specifics, owned entity types cannot use explicit/lazy loading. Trying to do so leads to runtime exception. So my recommendation from the original answer still applies.
Original:
With owned types - it's not possible (currently), because this behavior is "by design". And is documented in Querying owned types section of the EF Core documentation:
When querying the owner the owned types will be included by default. It is not necessary to use the Include method, even if the owned types are stored in a separate table.
It's a bit vague by saying "by default", but you can safely read it as "always", because there is no option or Exclude method.
Since currently the only way to control loading related data is navigation property to a real entity, make the types you want to control a "real entities", i.e. don't mark them as owned, define explicit or shadow PK, and map these "entities" with Table Splitting:
It is now possible to map two or more entity types to the same table where the primary key column(s) will be shared and each row will correspond to two or more entities.
To use table splitting an identifying relationship (where foreign key properties form the primary key) must be configured between all of the entity types sharing the table:

Entity Framework 6 adding extra inherited column

I have the class PERTActivity_T which inherits from MetadataTyper class, so as other tables.
Opposite to its siblings PERTActivity_T already has the properties:
[ForeignKey("BSContext")]
public int? BSContextId { get; set; }
public BSContext BSContext { get; set; }
But now I need all tables that inherit from MetadataTyper to have those properties as well. So I removed them from PERTActivity_T and included them in MetadataTyper but when I generate a migration it removes the relationship from BSContextId to BSContext and adds an extra field called BSContext_Id (with an underscore) and adds the foreign key to it.
This happens only for PERTActivity_T the other tables that inherit from MetadataTyper migrate fine.
At first I tried to edit the migration manually to remove all changes to PERTActivity_T since it is already created the way I need it, it all seamed to work fine but when I use a mapper to edit PERTActivity_T or any table related to it EF throws an error saying BSContext_Id is unknown.
How can I make EF understand that PERTActivity_T doesn't need any additional changes?
Turns out that my problem was that BSContext already contained a navigation property to PERTActivity_T and then I duplicated it.
All I had to do was remove the duplicated navigation property and the migration generated fine.

Saving OData entities from api with id to database in entity framework

I have multiple projects that return the same OData entities through a API endpoint. Now i want to call all of the projects and store them in my calling projects database with entity framework.
To add them to the db the ID gets overwritten but i want to save the id that the entity has in the projects database as well. so i can still access them if need be and to check if the data isn't already in my database. Because of this i need to add another MainProjectID and projectID column to the entity.
I tried making a new class that has a reference to the entity i want to save but this used new id's for the entities. I also tried inheriting the class but this gave me key conflict issues, and generics don't work either in entity framework(i'm not saying they should). So i'm kinda at a loss right now.
I basically want to save the id as a non-key. Is there any way i can do this without writing entirely new classes and parsing them manually ?
Any help would be greatly appreciated.
We have multiple alternatives here:
In a distributed system, best way to cope with these kinds of ID clashes is to make IDs globally unique. If you can modify how IDs are generated, that would be my choice to go. You can use a UUID (or Microsoft implementation GUID) that will produce a universal unique identifier. Or if that seems like an overkill you can devise a simple mechanism that combines ID with projectID. However you should ensure that the method you will use will not produce any collisions (no two different id-projectId pair will map to same value).
This will ensure that same entity is used throughout your application and no overlaps occur if you try to put records from different sources into the same table. You only need to implement a mechanism to record which ID originated from which source. You can use a reference entity at aggregator for this purpose. You also need to disable auto increment nature of the ID column so that your global unique values are used in table.
You can use different entities for producing and aggregating applications. I don't know your application, but that seems like an OK approach to me since the aggregating application has a different idea about the entity. The aggregating application cares for which application produced the entity, that might make putting the source application identifier into the entry justifiable. Your entities will only differ in that and when you receive the OData object from API you'll need copy all other properties and put project identifier yourself.
You can use the previous solution, but you can use a derived class in order to not to repeat your object properties. This is a better design alternative. However with this method you'll have some problems with the primary key (as you've stated you had). Consider this example
public class Base {
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Name")]
public string Name { get; set; }
}
public class Derived : Base {
[Key]
public int projectId {get; set; }
}
If you don't put [Key] to Derived then you'll have only ID as primary key. When you put [Key] to Derived then you'll have only projectId as primary key. You need to define a composite key and you can do this by removing the [Key] annotation from projectId and using the onModelCreating override of DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Derived>().HasKey(a => new { a.ID, a.projectId })
.Property(c => c.ID).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
modelBuilder.Entity<Derived>().Property(c => c.projectId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
}
You can mix these alternatives. You can drop the primary key on ID field, and then you can insert a new Entity that will model 1-M relationship between ID's and project ID's.

Tracking property names of revisions

I am currently attempting to implement a revision history screen in an MVC app. I need to be able to retrieve the names of fields which have changed in each revision using Envers. So I am following directions here: http://envers.bitbucket.org/#envers-tracking-modified-entities-revchanges
I am using the second option since we have a custom revision entity. It looks like this:
[RevisionEntity(typeof(MyRevisionListener))]
public class RevisionEntity : DefaultTrackingModifiedEntitiesRevisionEntity
{
public virtual Person User { get; set; }
}
As you can see I am inheriting from DefaultTrackingModifiedEntitiesRevisionEntity in order to make sure the class has the property to hold the modified entities' names.
Per the documentation this should create a table called RevChanges in which this information is stored with reference to the revisions table:
Envers provides a simple mechanism that creates REVCHANGES table which
stores entity names of modified persistent objects. Single record
encapsulates the revision identifier (foreign key to REVINFO table)
and a string value.
I am never seeing this table created. I tried creating such a table myself along with a related class and wiring up the mappings, but I don't see how Envers would know to put the data into that table without me configuring it somehow. I just get an exception saying that the object is different from the target type when the get method is called on the new type.
How can I get this to work?
If you use a custom revision entity, you need to map this just like you do with normal entites.
http://envers.bitbucket.org/#revisionlog

Entity Framework (Database-First) multiple relations to same table naming conventions control

Let's suppose that we have this situation:
Tables in database:
Country (id, country_name), Person (id, login), CountryManager (id_country, id_person), CountryStakeholder (id_country, id_person)
If we had to create the model from the database, using Entity Framework Database-First, in VS we'd have a class like this:
class Country {
int id;
string country_name;
virtual ICollection<Person> Person1; // Navigation Properties
virtual ICollection<Person> Person2; // ---------||----------
}
I've simplified the code a lot, but hopefully you got the point.
Seems that when Entity Framework deals with foreign keys it creates generic Navigation Properties. Is there a possibility to control how Navigation Properties are created by name? Person1, Person2 isn't very explainatory, unfortunately.
In VS you can do this with the GUI.
If you show Model Browser then navigate down the tree to:
YourEntityModel > Entity Types > Country
then right-click on the "Person1" Navigation Property and select "Properties" you can then change the name of the navigation property name to whatever you like:
Just change the name, save changes and your done...
(Actually there are lots of ways to get to the Navigation Property properties window - you cn right click on it in the model diagram too)
From the entry "Working with Inverse Navigation Properties" from the book "Programming Entity Framework: Code First":
You can add configuration (using Data Annotations or the Fluent API)
to present this information to the model builder. With Data
Annotations, you’ll use an annotation called InverseProperty. With the
Fluent API, you’ll use a combination of the Has/With methods to
specify the correct ends of these relationships.
You can place the annotations on either end of the relationship (or
both ends if you want). We’ll stick them on the navigation properties
in the Lodging class (Example 4-10). The InverseProperty Data
Annotation needs the name of the corresponding navigation property in
the related class as its parameter.
Example:
[InverseProperty("PrimaryContactFor")]
public Person PrimaryContact { get; set; }
[InverseProperty("SecondaryContactFor")]
public Person SecondaryContact { get; set; }
I recommend using https://visualstudiogallery.msdn.microsoft.com/ee4fcff9-0c4c-4179-afd9-7a2fb90f5838
It allows more flexibility than any database generation thing I've seen.
I'm still working on solving my own problem, but this looks pretty promising. But, unlike the Default Code generation that EF provides, you can customize the mapping.
Like, in all the examples I've seen on renaming the navigation properties -- that alone won't be enough, because EF still needs to be mapped to use those navigation properties (you could hack it though, and have your User2 point to ModifiedByUser, for example).

Categories

Resources