Assume a simplistic data model consisting of two tables, Book and Author, related by a foreign key constraint "Book.AuthorId = Author.Id".
Now say you've got some flat list of books:
Title Author
---------------------------------------------------------
The Selfish Gene Richard Dawkins
Why Is Sex Fun Jared Diamond
The Ancestors Tale Richard Dawkins
How do you import these without making a gazillion round trips to the DB?
I'm new to EF but figured it would allow me to add entities to the object context and then they'd be available in that context even if they aren't saved yet. This seems not to be the case. In principle, I'm doing this:
void Import()
{
ctx = new Database();
foreach (...)
{
Import(bookTitle, authorName);
}
ctx.SaveChanges();
}
void Import(string bookTitle, string authorName)
{
var author = ctx.Authors.Single(a => a.Name == authorName);
if (author == null)
{
author = new Author();
author.Name = authorName;
ctx.AddToAuthors(author);
}
var book = new Book();
book.Author = author;
ctx.AddToBooks(book);
}
But EF never finds an author I've already added, so it makes a new one every time. Hence the two books by Dawkins are related to two different author objects in the graph, which is of course wrong.
Next, upon SaveChanges, EF manages to insert the first Book and Author record, but then crashes on number two with a PRIMARY KEY VIOLATION because it attempts to use Id = 0 for every object.
System.Data.UpdateException: An error occurred while updating the entries.
See the InnerException for details. --->
System.Data.SqlClient.SqlException: Violation of PRIMARY KEY
constraint 'PK_Book'. Cannot insert duplicate key in object 'dbo.Book'.
I attempted to change the definition for the id fields to INT IDENTITY(1,1) in MSSQL and then update the model. Letting the DB define the key values is anyway better than having Entity Framework (or any client) do it. But this appears not to be supported at all. EF seems not to have noticed that my keys are IDENTITY columns when I updated the model. It just leads to
System.Data.UpdateException: An error occurred while updating the entries.
See the InnerException for details. --->
System.Data.SqlClient.SqlException: Cannot insert explicit value for
identity column in table 'Book' when IDENTITY_INSERT is set to OFF.
The exact symptom is provided whether I update the model or not following the switch from "Id INT PRIMARY KEY" to "Id INT IDENTITY(1,1) PRIMARY KEY" for the column definition, which is what leads me to suspect that Microsoft didn't even remember to THINK ABOUT identity columns, though I hope I'm wrong about this!
I realize there's really multiple questions lurking in here, but since nothing in Entity Framework works as expected I choose to put the overarching question as "given this data model and these data to import, what is a good way to do it using Entity Framework 3.5?"
Please refrain from advising me to use EF 4.0 instead, as that is outside of my control. :)
My question was based on a misunderstanding - in fact adding the objects to the object context without saving does make them available on the object context.
What was really going on is this: I'm trigging logic in a .net assembly from powershell script using Reflection.Load. When I made changes in the .net project, rebuilt it and GAC-ed it, then reran my PowerShell script, I was still running the old code. Basically the code from MyAssembly.dll is linked into the powershell session, so when I rerun the script it does not matter that a new version of the code has been deployed to the GAC...
The PK violation then makes sense, since my code does not assign an ID to the entity and there is more than one author - I tried to save both with ID = 0.
The "cannot insert explicit value when IDENTITY INSERT is off" also now makes sense, because I had modified my database but was still running the code that assumed the ID from should be inserted.
In short, what fixed the problem was to close my PowerShell session and start a new one.
Related
I am using EFCore 5.0.0.
When I AddAsync (person); I should be getting a temporary ID, and I use this ID to add the PersonId for School (shown in code below). FInally, I will SaveChangesAsync() where everything will be saved. However, the PersonId is set to 0. I want to get the temporary ID stored instead. How can I do this.
await _dbContext.AddAsync(person);
School school = mySchool;
school.PersonId = person.Id;
await _dbContext.AddAsync(school);
await _dbContext.SaveChangesAsync();
Note: There are many SO post that talks about the temporary ID, but none is related to this post.
Currently accepted answer is valid, but technically incorrect. Assigning navigation property is valid approach, but not mandatory. It's even perfectly valid to not have navigation property at all. As well as explicit FK property. But there is always at least shadow FK property which can be used to setup/maintain the relationship.
So the temporary key concept is part of the EF Core from the very beginning. However EF Core 3.0 introduced a breaking change - Temporary key values are no longer set onto entity instances. The link contains an explanation of the old and new behaviors, the reason and possible solutions:
Applications that assign primary key values onto foreign keys to form associations between entities may depend on the old behavior if the primary keys are store-generated and belong to entities in the Added state. This can be avoided by:
Not using store-generated keys.
Setting navigation properties to form relationships instead of setting foreign key values.
Obtain the actual temporary key values from the entity's tracking information. For example, context.Entry(blog).Property(e => e.Id).CurrentValue will return the temporary value even though blog.Id itself hasn't been set.
Bullet #1 makes no sense, Bullet #2 is what is suggested in the other answer. Bullet #3 is the direct answer/solution to your question.
And applying it to your example requires just changing
school.PersonId = person.Id;
to
school.PersonId = _contexy.Entry(person).Property(e => e.Id).CurrentValue;
Of course when you have navigation property and the related entity instance, it's better to use it and let EF Core do its magic. The temporary key is really useful when you don't have navigation property, or you don't have related entity instance and know the key, but don't want to do roundtrip to load it from database (and using fake stub entity instance can lead to unexpected side effects/behaviors). It works well with both explicit and shadow FK properties.
I've never seen linking entities in EF Core using the temporary id.
Typically what you would do is assign the entity and let EF sort out the ids and relationships.
i.e. in this instance, the School will be linked to the Person.
await _dbContext.AddAsync(person);
School school = mySchool;
school.Person = person;
await _dbContext.AddAsync(school);
await _dbContext.SaveChangesAsync();
I am trying to update a parent entity, GuildMemberTeam, with child entities, GuildMember, Team and GuildMemberChallenge which also has a child entities, GuildMember and Challenge but am getting the following inner exception:
Inner Exception 1: SqlException: Violation of PRIMARY KEY constraint
'PK_Challenge'. Cannot insert duplicate key in object 'dbo.Challenge'.
The duplicate key value is (15ae8798-8567-457b-812a-5820cf7843e5). The
statement has been terminated.
The only new entity is the GuildMemberTeam as all the others already exist, but these are checked and recreated as follows:
public void AddChallenge(Challenge challenge)
{
if (challenge != null)
{
var id = challenge.Id == default(Guid) ? Guid.NewGuid() : challenge.Id;
Challenge = new Challenge(id, challenge.Name, challenge.Phase, challenge.Type, challenge.Star, challenge.Gear, challenge.Level, challenge.Reward);
}
}
This works for all the other entities apart from Challenge where i get the error. Can anyone please help me understand what i am doing wrong.
It doesn't change the fact that the problem is that you are trying to insert the same row twice (same Guid=Id) into the dbo.Challenge table.
This might be due to a debugging issue or something. You can either delete the row from the table with a
DELETE FROM [Challenge] WHERE Id = '15ae8798-8567-457b-812a-5820cf7843e5' and try running the app again.
If this doesn't solve your problem your entity management is faulty and you have to revise the ID handling. Implement ID checking before you try to save your context or something like that.
The other issue might be that your classes are not defined properly and EF doesn't recognize the relations. The relationships you are talking about are not parent-child, they are either one-to-many, many-to-many, many-to-one or none. DB RELATIONS
Each of your POCO-s should contain and instance of the other class, thus you define a relationship. E.g. if your GuildMemberChallenge contains an IEnumerable and a property with type of challenge.
If none of the above are a solution I need some more code (your classes, the repository) to figure it out.
Update:
When you are adding a new GuildMemberChallenge, which I assume you are trying to do now. You should set it's Challenge property to an existing entity if it exists, if it doesn't you can create one, but at the moment you are trying to create a Challenge that already exists in the database.
You are creating new Challenge but pass id of existing Challenge if it is set.
var id = challenge.Id == default(Guid) ? Guid.NewGuid() : challenge.Id;
I think you, that if you create new entity you should always create new Id
var id = Guid.NewGuid();
I use NHibernate 4,
I would like to implement a function which permit to refresh an modified object.
My object contains an ID and an association one-to-many with objects with ID2 ad a string
In my association, i insert a new object association, so ID2 is null because it generate by hilow generator when save the main object in database.
so when i cause refresh for to reload the original object, I have an assertion failure (null identifier) when there is an new object with ID = null (it's normally ok, no problem when i saveOrUpdate.
I put notFound = ignore in my mapping but with no effect when execute.
Please give me a way for resolve my problem
Thank you in advance.
In my association, i insert a new object association, so ID2 is null because it generate by hilow generator when save the main object in database.
There is a somewhat inexact statement here. The hilo generator strategy generates the id before the object is inserted in database. It does it on Save, without Save actually inserting the entity into database until the session is flushed.
If your id stays null, this is very likely because you have not yet persisted the entity in the session. Call Save on it, and your id will no more be null, while the entity will not be yet in database unless you have flushed the session (or changed the generator strategy for one requiring immediate insert, as identity).
Now refreshing an object not already in database looks anyway as an error to me. What do you expect from such an operation?
not-found="ignore" is not meant for handling refreshing of non existent entities. It is there for allowing ignoring an invalid foreign key in database.
I choose Database First:
Here is an example table that is experiencing this issue. As you can see the EntityId column is the Primary Key:
The imported table in the model browser shows that it has the Primary Key:
But the code for the generated class does not have the EntityId column decorated with a Key attribute:
At run time I get this error:
Additional information: One or more validation errors were detected
during model generation: EntityType 'Entity' has no key defined.
Define the key for this EntityType.
Why do I have to manually decorate the EntityId column with the Key Attribtue? Shouldnt EntityFramework take care of all that considering it is Database first?
Typically speaking I have experience with EF4 through EF 6.1.3 and a teeny bit with Entity Core (was EF7 and then MS fun with naming). Typically if you are doing database first you do not get an adornment from your t4 template. I just looked at mine just now and I have no adornment for the key, just for constructor and the reference to the navigation of teOrder back to it.
I can save an Entity just fine and my code runs with this:
using (var context = new EntityTesting.TesterEntities())
{
var nPerson = new tePerson { FirstName = "Test", LastName = "Tester" };
context.tePerson.Add(nPerson);
context.SaveChanges();
}
What I would suggest is:
Go to your (name).edmx Entity File and on the design surface wipe out the object and then replace it on the surface. In countless times this has fixed issues with the generated objects. When you SAVE it should auto run the T4 templates (*.tt files). If not you can select them, right click and select 'Run Custom Tool'. This just generates the POCO objects or the Context objects.
If this is really a fault with the table it is typically with it NOT having a key. You are showing it does though. Is there anyway to mirror the exact same table logic and confirm the key has nothing out of the ordinary and is just a plain old key Primary Key?
Create a brand new table with similar structure but not an exact copy and a new Entity File and confirm you can create it.
Tables are pretty straight forward with EF, or as straight forward as EF can be. You create them in SQL, ensure you have a key, add it to a design surface, save, it generates the objects for you. If you have other things under the hood like custom procs hooked to it or other out of the ordinary nav items that would be one thing. The only other thing would be if it has a very old SQL Type as the key. I know that the 'Text' type and EF do not play nice together. There may be other types that behave the same way.
This issue was fixed by including the "metadata" part of the connection string.
At first my connection string looked like this:
data source=.;initial catalog=TestDatabase;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework;
Which produced the error.
Changing my connection string to this:
metadata=res://*/DbContexts.TestContext.csdl|res://*/DbContexts.TestContext.ssdl|res://*/DbContexts.TestContext.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=TestDatabase;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
Allowed operations on the Context to be performed with no error encountered
I'm running into a situation using the Entity Framework (EF) that has me totally stumped. I'm doing a simple update and the error I'm getting is
Violation of PRIMARY KEY constraint 'PK__tblProducts_Mark__03E07F87'. Cannot insert duplicate key in object 'healthc.tblProducts_MarketSegmentGroups'.
The statement has been terminated.
Let me give you some background on the problem.
I am using Web Forms and have a button click event fire to save some data in several text box controls on my page.
I have a table in my database called tblMetaProducts, which is a table used to store product information from various vendors we work with. The entity for this table is called Products.
I have another table called tblTechAssessment, which holds data for technical questions about the vendor's product, (e.g. what operating system can the software run on, version number etc.). The entity for this table is called TechnicalAssessment. A product can have many technical assessments and they are related by the product id.
I finally have a lookup table in the database called tblProducts_MarketSegmentGroups, which holds a product id and another id (which we don't care about for this problem). The entity for this table is called ProductMarketSegmentGroup. a product can have many product market segment groups and they are releated by the product id.
Here is the code I'm executing to perform the EF save
private void UpdateTechnicalAssessments(int productID)
{
var technicalAssessments = VendorDirectoryController.GetTechnicalAssessments(productID);
var technicalAssessmentTypes = Enum.GetValues(typeof(TechnicalAssessmentType)).Cast<TechnicalAssessmentType>();
foreach (var technicalAssessmentType in technicalAssessmentTypes)
{
var typeName = technicalAssessmentType.ToString();
var id = "SaveToProduction" + typeName + "TextBox";
var results = ProductInformationPanel.FindDescendantsByType<TextBox>().Single(x => x.ID == id).Text;
technicalAssessments.Single(x => x.QuestionID == (int)technicalAssessmentType).Results = results;
}
VendorDirectoryController.SaveChanges();
}
The SaveChanges() method drills down to my domain layer and calls the dataContext.SaveChanges() method.
So my questions are:
1) What can I do to get this to save my TechnicalAssessment entities?
2) Why does my save affect the ProductMarketSegmentGroup entity?
You might be hitting a bug in EF. I also stubmled on something similar (even though I use stored procedures).
The solution was to apply hotfix mentioned in: hotfix: Principal entity in an SQL application generates unnecessary updates - do it still effect EF 4.3.1?
The solution I ended up using was to have our database admin create a proc for the update. I was never able to figure out why the navigation property was causing such a fuss.