Update navigation property's non-id field using EF6 in WinForms - c#

I want to update a non-id field of an object's navigation property when the user is on the form for modifying that object. Some of my "Department" table's fields are like this...
Id
DirectorId (object of Employee table)
NoOfEmployees
Location
and Employee table's fields are as follows:
Id
Name
Post
Now when the user is presented with modification of Department table, I also show the core info about the employees (with post of "Director") with current director selected in its ComboBox and all the relevant information in corresponding TextBoxes etc. As soon as the user chooses a different Director, relevant fields also change accordingly.
Now what i want is that if user changes some of the Director's information (e.g. Name or Age) at this point, the changes should reflect in DB instead of compelling the user to go to employee modification form and do changes there.
I tried the simple approach of modifying few fields of a database object but that approach is not working when the object-to-be-modified is navigation property (as discussed above). Here is the code...
using (CompanyDBContext db = new CompanyDBContext() {
int id = ((Department)cbDept.SelectedItem).Id;
Department currentDept = db.Departments.Where(x => x.Id == id).First();
Department newDept = new Department();
newDept.Id = currentDept.Id;
newDept.Employee.Id = (int)cbDir.SelectedValue; // --> has no effect;
// raises NullRefException "Obj. ref. not set to an instance of an obj."
.....;
.....;
db.Entry(currentDept).CurrentValues.SetValues(newDept);
if (dirNameChanged) {
var director = new Employee() {
Id = currentDept.DirectorId,
Name = tbDirName.Text.Trim()
};
db.Employees.Attach(director); // --> raises exception
db.Entry(director).Property(x => x.Name).IsModified = true;
}
db.SaveChanges();
}
.Attach() methods throws InvalidOperationException exception saying
Attaching an entity of type 'CompanyDatabase.Employee' failed because
another entity of the same type already has the same primary key value.
This can happen when using the 'Attach' method or setting the state of
an entity to 'Unchanged' or 'Modified' if any entities in the graph have
conflicting key values. This may be because some entities are new and have
not yet received database-generated key values. In this case use the 'Add'
method or the 'Added' entity state to track the graph and then set the state
of non-new entities to 'Unchanged' or 'Modified' as appropriate.
But then using Add() method also raises similar exception...
Any work around?
P.S. Dept object is changing alright. Changing the Director object is also fine i.e. if user chooses a new Director for the Dept. Only changing the non-id fields of Director (Employee) is giving problem.

db.Employees.Attach(director); // --> raises exception
The exception message indicates that the context already contains (is tracking) Employee object with the same PK (Id == director.Id). So instead of creating new object (new Employee()) you are expected to use the existing object (EF uses reference identity, i.e. does not allow two different entity instances with one and the same PK).
The standard way of doing that is to use Find method, which will return the currently tracked object with that PK or will load it from the database. Hence the code should be something like this:
if (dirNameChanged)
{
var director = db.Employess.Find(currentDept.DirectorId);
director.Name = tbDirName.Text.Trim();
}
Note that there is no need to Attach it or manipulating the entity/property states - the returned object is attached and tracked by the context, so any property value changes are determined automatically.

Related

Entity Framework Core : populating relationship navigation property

Is it possible in .NET 6 with Entity Framework Core 6 to populate the relationship navigation property by setting the foreign key value and then call SaveChanges?
I tried it but it doesn't seem to work. Although the other way around works perfectly (if I set the navigation property to the related entity).
Screenshots:
setting the foreign key
after save changes, "department" property still null
When trying this, student.department remains null after calling SaveChanges
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges();
While if I do this, the foreign key student.departmentId gets populated after calling SaveChanges:
var student = db.Students.Find(9);
student.department = db.Departments.Find(1);
db.SaveChanges();
When trying this student.department remains null after savechanges
Setting the foreign key value doesn't load the related department. The use case for setting the foreign key directly is typically to avoid actually loading the related entity.
If you want to load the related entity, you might as well just query it and assign it to the navigation property.
After setting the foreign key property on an entity, you can load the related entity if you want to using explicit loading. eg
db.Entry(student).Reference(b => b.Department).Load();
SaveChanges will not automatically load the relationship data unless context is already tracking the corresponding entity (Change Tracking in EF Core). In addition to using one of the options to load the related data (for example the one suggested by #David Browne in his answer), following things will do the trick:
db.Departments.Find(1);
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges(); // student.department will be filled here
Or even
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges();
db.Departments.Find(1); // student.department will be filled here

Cannot insert explicit value for identity column in table 'TABLE' when IDENTITY_INSERT is set to OFF Exception Updating my table

I´m trying to update a list of objects, but when I run context.SaveChanges(), I get an error
Cannot insert explicit value for identity column in table 'TABLE' when IDENTITY_INSERT is set to OFF
I know when the Identity key is generated you cannot register a new entry with this KEY but I'm trying to update my registry not insert a new one and this exception appears. I don't know why this happens now.
My code:
foreach (int i in deletes)
{
var p = context.Item.FirstOrDefault(g => g.participant_id == i);
if (p != null)
{
p.group_id = 0;
context.Entry(p).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
}
}
group_id is not my identity column, it is just another column I added:
ALTER TABLE Items
ADD group_id INTEGER DEFAULT(0);
The error specifically is the result of a configuration mismatch somewhere in your entity definitions where a table is set to use an Identity PK but EF is not configured for an identity column and the DbContext is attempting to insert a row into that table. This is not necessarily the table you think the DbContext is attempting to update. Chances are somewhere prior to SaveChanges being called there has been an entity incorrectly created or associated and treated as a new entity.
Try this, provided your code is roughly identical to your example:
using (var tempContext = new YourAppDbContext())
{
foreach (int i in deletes)
{
var p = tempContext.Item.FirstOrDefault(g => g.participant_id == i);
if (p != null)
p.group_id = 0;
}
tempContext.SaveChanges();
}
Here we isolate this operation to a clean, temporary DbContext instance to ensure there are no polluting tracked changes. This is not a "fix" for the issue, just to identify whether the context was polluted. You can inspect context pending changes to see what it might be tracking. (ChangeTracker) Your issue may also stem from setting the entire entity to Modified state and a misconfigured related entity definition. (unlikely if the entity and associated were read from this DbContext instance) When updating an entity, don't set the EntityState to Modified. This sets EF up to effectively update all columns on that table. In the above example if we just update 1 column, EF will generate an Update statement for just that single column, and will only generate an update statement if that value actually changes.
If the above code works without an issue, then you have rogue tracked changes polluting your DbContext. This either means you have something else going on before this code is called that you need to fix, or your DbContext lifetime scope is far longer than it should be. (or both)
The problem with your example is that we cannot see the scope that your context is covering. Whenever you make any kind of change to a DbContext, those changes are tracked by default by that DbContext. If you have code that is working with detached entities anywhere that suddenly get re-associated with a DbContext via association with an Added entity and treated as new entities themselves, then calling SaveChanges() when you expect to update a single row all the sudden results in strange unrelated exceptions because the DbContext wants to persist queued up changes.
A typical culprit to look out for with issues like this would be first to check over all of your entity definitions for tables using Identity PKs and ensure those entities are configured with DatabaseGeneratedOption.Identity. This alone would prevent the exception, but you'd probably find that EF would be inserting new, duplicate rows somewhere with new PKs. The culprit behind that is usually detached entities being re-associated to an entity.
Given a Order entity associated to a Product. We want to create an order so since we already loaded the Products collection to select and associated to the new Order, we set that reference client side and send the Order to the server. We assume that since we loaded the Products and it's children from a DbContext on a GET, that when we call POST and send that parent back to the server, all should be good.
public void CreateOrder(Order order)
{
context.Orders.Add(order);
context.SaveChanges();
}
order had a Product reference with ProductId = 22 since we selected product #22 for the order. However, the DbContext instance when we call CreateOrder doesn't know Product ID 22 represents a known entity, it isn't tracking Product ID 22, so it gets treated as a new entity. If the Product entity ID isn't configured as an Identity column but is in the DB, you get the error you saw. If it is configured as an Identity column then you'd find the Order saves successfully, but is pointing at a new, duplicate Product with an ID of 56 or whatever the next available ProductId is.
Passing detached entities is a big cause of headaches like this. The safe way to do something like the CreateOrder would be:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
var product = context.Products.Single(x => x.ProductId == order.Product.ProductId);
order.Product = product; // replace with the product tracked by this DbContext.
context.Orders.Add(order);
context.SaveChanges();
}
All associations on a detached entity need to be checked.
Devs don't like the hit to the database so they can be tempted to merely attach the Product:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
context.Products.Attach(order.Product);
context.Orders.Add(order);
context.SaveChanges();
}
.. and in most cases that will appear to work. Until it doesn't. Attach will throw an exception the moment you attempt to attach an entity with an ID that the DbContext is already tracking. If any code prior had loaded or attached an instance of that Product, then the call will fail. This can result in random-like exceptions appearing at runtime. The safe way to attach the entity would be:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
var existingProduct = context.Product.Local.SingleOrDefault(x => x.ProductId == order.Product.ProductId);
if (existingProduct == null)
context.Products.Attach(order.Product);
else
order.Product = existingProduct;
context.Orders.Add(order);
context.SaveChanges();
}
This checks the local tracking cache to see if the DbContext is tracking that product. (Does not hit DB) If it isn't then we can safely attach it. If it is, we replace the product reference on the order with the tracked one.
This would have to be done for every reference. Any that are missed would result in potential errors or duplicate rows.
Try
var p = context.DbSet<Participant>().Where(g => g.participant_id == i).FirstOrDefault();
And did you check your Participant class in EF? It still can be a foreign key there.

Entity Framework Core1.1 - Bulk Insert or Update - InvalidOperationException

I'm running into a problem with inserting OR updating roughly 950 entities.
var coins = JsonConvert.DeserializeObject<List<Currency>>(json);
var sw = new Stopwatch();
sw.Start();
using (var ctx = CryptoContext.Get)
{
var existingCoins = ctx.Coins.ToList();
foreach (var coin in coins)
{
var existing = existingCoins.FirstOrDefault(c => c.CMC_Id == coin.CMC_Id);
if (existing != null)
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
} else
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Added;
}
}
ctx.SaveChanges();
var el = sw.ElapsedMilliseconds;
}
The code runs in the background of my netcoreapp1.1, with SQLite, and retrieves a list of currencies. This is done every 5 minutes with FluentScheduler. Because they're not entirely large objects I do all comparisons in memory, and try to add or update each one. My entity has a database-given ID of Id, and the API I'm retrieving from guarantees that CMC_Id is unique.
The initial insertion works fine. I get an error on the second "Update". I believe what's happening is that I'm tracking multiple entities as modified that each have an Id of 0
I was trying to follow this: https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
And the error I get is: "The instance of entity type 'Currency' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context."
I am unsure how to proceed with updating each row.
Issue here multiple entities with same key are asked to be tracked.
When you set EntityEntry.State to something then EF Core will start tracking the entity in the specific state. Since in your code, you are querying the database to find out existing entity, EF Core will start tracking the entity with given key therefore it throws above exception while setting the EntityEntry.State because there is already entity with same key being tracked.
More precisely you are trying to AddOrUpdate. There are multiple ways to achieve the behavior. Which one is the best depends on if you are adding one entity without relation or a complex graph.
The simplest method would be to just check existence instead of tracking the entity from database. Options for that would be to use AsNoTracking in your query so that EF does not start tracking it. Even more optimized way would be to just get count from database. If you are querying on PK property then count will be either 0 (non-existent) or 1 (existing entity). If it does not exist then you call Add otherwise Update.
var updatedBlog = new Blog { Id = 1, Title = "Updated" };
var exist = db.Blogs.Count(b => b.Id == updatedBlog.Id) != 0;
if (exist)
{
db.Update(updatedBlog);
}
else
{
db.Add(updatedBlog);
}
db.SaveChanges();
Since Add or Update methods start tracking whole graph, if your graph is in one consistent state, (all entities are new or all are being modified) then it would work just fine.
If your graph is somewhat inconsistent that state of each node in graph can be different (e.g. Updating a blog but it has new posts). Then you should use EntityEntry.State on individual entity. This makes sure that state is applied to only given entity and no other related entity in graph. Though you need to do above kind of check for each node in the graph. Another alternative is to use Attach method to attach whole graph in Unchanged state and then set state for individual node.
If you are having auto-generated Key values then probably you will have PK value set only when it is update else it would be CLR default. For single entity without relations, you can make that check yourself instead of querying database like above code and make decision. For graphs, you can use
db.ChangeTracker.TrackGraph(updatedBlog, n => n.Entry.State = n.Entry.IsKeySet ? EntityState.Modified : EntityState.Added);
This will set state of each node based on PK value being set or not.
Hope this helps :)

Entity Framework - Saving changes made to a detached entity

Say changes have been made to a detached entity in EF 4. If we want to save theses changes when we re-attached the entity, is it possible to do this with ApplyCurrentValues without querying the DB to get the original entity? I don't think so, but I'd like somebody to confirm that.
using (var ctx = new BAEntities())
{
var firstCust = (from c in ctx.Contacts select c).First();
Console.WriteLine(firstCust.FirstName);
ctx.Contacts.Detach(firstCust);
firstCust.FirstName = "Modified Value";
ctx.Contacts.Attach(firstCust);
ctx.ApplyCurrentValues("Contacts", firstCust);//Does not work
//ctx.ObjectStateManager.ChangeObjectState(firstCust, EntityState.Modified); //Works with that line
ctx.SaveChanges( );
}
Thank you
I can confirm your guess. It doesn't work this way.
When you call Attach with an entity as parameter EF adds the entity to the context in state Unchanged. Basically you are telling EF with Attach that all property values the entity has at that time represent the current values in the database.
ApplyCurrentValues is kind of an "automapper" that just copies the property values of the object you pass into ApplyCurrentValues to the attached entity that has the same key. This copy happens based on the property name.
If a property value of the attached entity is different to the property value of the object you pass into ApplyCurrentValues EF marks the property as Modified. If not the state stays Unchanged. Obviously with your procedure all property states will stay unchanged and nothing gets written to the database.
In theory you could do crazy stuff to make it work like:
firstCust.FirstName = "Modified Value";
var dummyCust = new Contact { FirstName = "UnlikelyNameThatWillNeverOccur" };
ctx.Contacts.Attach(dummyCust);
ctx.ApplyCurrentValues("Contacts", firstCust);
Here the FirstName property would be marked as Modified. But you had to do this for every property and the result will be the same as setting the state of the whole entity to Modified as you did in the commented code line.
You can by the way set a single property to Modified:
ctx.Contacts.Attach(firstCust);
ctx.ObjectStateManager.GetObjectStateEntry(firstCust)
.SetModifiedProperty("FirstName");
This will send an UPDATE statement to the database that only sets the FirstName column value (while setting the state of the whole entity to Modified will create an UPDATE statement that sets all column values to the current property values).

General best practice for updating a record and its associated relationships in Linq-to-SQL

I have a very general question about updating a record in Linq-to-SQL. Suppose, in my data model, I have a base record (Table - Person) with an m:m relationship to a set of Categories (Table - Category). Therefore, I have an associative table (PersonCategory) that has foreign keys to both a PersonID and CategoryID.
When I want to update a Person, I may have new PersonCategory records to add and may want to remove other PersonCategory records. What is the best practice for doing such a thing? Would I want to remove all records in Person.RelatedPersonCategories and then add new in? Is LINQ-to-SQL smart enough to negotiate what records in the PersonCategory table are actually being added, edited, or deleted (based on looking at its foreign key potentially)?
Thanks for any help!
As long as there is an active instance of the DataContext class to track changes, LINQ to SQL will happily insert/update/delete rows in an associated table everytime objects in the collection that maps the relationship in the model are modified, and the DataContext.SubmitChanges() method is called.
For example:
using (var db = new DataContext())
{
var person = db.Persons.Where(p => p.Name == "Foo").SingleOrDefault();
if (person != null)
{
// Inserts a new row in the 'PersonCategory' table
// associated to the current 'Person'
// and to the 'Category' with name 'Employee'
person.PersonCategories.Add(new PersonCategory() { CategoryName = "Employee" });
// Updates the 'CategoryName' column in the first row
// of the 'PersonCategory' table associated to the current 'Person'
person.PersonCategories(0).CategoryName = "Consultant";
db.SubmitChanges();
}
}
Things are a little different if you are making changes to the model objects in "disconnected" mode, that is when the DataContext instance that was used to initially create those objects no longer is around.
In this case insert/delete operations on associated tables will work just fine when the object having the modified collection is attached to a new DataContext with the Table(TEntity).Attach method, followed by DataContext.SubmitChanges().
However, modifications on any of the existing objects in the collection will not automatically be applied in the associated table. In order to do that, you must manually call the Table(TEntity).Attach method for each object in the collection.
Here is a quote from the MSDN documentation:
When a new entity is attached,
deferred loaders for any child
collections (for example, EntitySet
collections of entities from
associated tables) are initialized.
When SubmitChanges is called, members
of the child collections are put into
an Unmodified state. To update members
of a child collection, you must
explicitly call Attach and specify
that entity.
Here is a concrete example:
// The 'Person' object has been detached
// from the originating 'DataContext', which is now disposed
person.PersonCategories.Add(new PersonCategory() { CategoryName = "Employee" });
person.PersonCategories(0).CategoryName = "Consultant";
using (var db = new DataContext())
{
// Will detect added and deleted objects
// in the 'PersonCategory' collection
db.Person.Attach(person);
// Required to detect and modifications
// to existing objects in the 'PersonCategory' collection
foreach (var personCategory in person.PersonCategories)
{
db.PersonCategory.Attach(personCategory);
}
db.SubmitChanges();
}
From my experience you don't have to do anything special here. PersonCategory is just an entity, and you add or remove instances from the related table instance on a data context as you would for any other entity, via InsertOnSubmit() and DeleteOnSubmit(). Updates to existing PersonCategory objects are handled by the change tracking system.
You certainly don't need to remove all items first and then add new ones back in.
It gets more complex when you look at the model code - provided you have foreign keys set up correctly in the database, the generated model code should completely handle association management as well.
Eg,
Person will have an EntitySet<PersonCategory>
PersonCategory will have an EntityRef<Person> and EntityRef<Category>
Category will have an EntitySet<PersonCategory>.
Whenever changes are made to these fields, the associated objects are updated as well:
If I change the Person property on a PersonCategory to null, the code will also update the related EntitySet<PersonCategory> accordingly by removing that PersonCategory from the prior person's EntitySet<PersonCategory>
If I add a new PersonCategory to a person's EntitySet<PersonCategory>, the code will automatically set the Person property to the new parent Person as well.
So yes, the basic answer is LINQ to SQL is smart enough to handle this for you - as long as you are generating the model code from your database.

Categories

Resources