Attaching detached entries with same key - entity-framework-4.1

I am using Code First Entity Framework 4.1. The two entities that I am using are "State" and "User". Each State entry has a "CreatedBy" User and "ModifiedBy" User properties as given below.
public class State {
public virtual User CreatedBy { get; set; }
public virtual User ModifiedBy { get; set; }
}
The User entity doesn't have any back reference to State entity, that is State => User is "Unidirectional".
The problem occurs when there is a detached State entity which has same "CreatedBy" and "ModifiedBy" User properties. When I try to attach State Entity to the dbContext, the EntityFramework complains that duplicate entry found by ObjectStateManager. I was looking for a simple solution for this issue.

One solution would be to check if a User with the same key is already in the context and if yes, replace the detached User references in your State entity by the objects which are attached to the context. Say, state is the new State entity to attach:
if (state.CreatedBy != null)
{
var attachedCreatedBy = context.ChangeTracker.Entries()
.Where(e => e.Entity is User
&& e.Cast<User>().Entity.Id == state.CreatedBy.Id)
.Select(e => e.Entity)
.SingleOrDefault();
if (attachedCreatedBy != null)
state.CreatedBy = attachedCreatedBy;
}
if (state.ModifiedBy != null)
{
var attachedModifiedBy = context.ChangeTracker.Entries()
.Where(e => e.Entity is User
&& e.Cast<User>().Entity.Id == state.ModifiedBy.Id)
.Select(e => e.Entity)
.SingleOrDefault();
if (attachedModifiedBy != null)
state.ModifiedBy = attachedModifiedBy;
}
context.States.Attach(state); // now it should not throw an exception anymore
Well, I would not call this a "simple solution" though. But I don't know another one. If you had foreign key properties CreatedById and ModifiedById in State it would become easier. You could just set the navigation properties CreatedBy and ModifiedBy to null and only set the foreign key properties to the Ids of the related users.

Related

Entity Framework Core: Update() method INSERTs instead of UPDATEs on dependent entity

It seems like EF Core is doing an INSERT instead of an UPDATE, and thus MySQL complains about a duplicate key. However, I am using the Update method on the DbSet and the entities do have primary keys set. This results in a DUPLICATE ENTRY error in MySql.
Running VS 2019, EF Core 3.1.1 and ASP.NET Core 3.1
Model (I do not use Fluent config for relationships, just Convention):
public class Vehicle
{
public long Id {get; set; } // PRIMARY KEY, AUTO INCREMENT
public string RegistrationNumber { get; set; }
public VehicleSession Session { get; set; }
}
public class VehicleSession
{
public long VehicleId { get; set; }
public Vehicle Vehicle { get; set; }
public string DeviceUuid { get; set; } // PRIMARY KEY
public string AuthenticationToken { get; set; }
public string OSName { get; set; }
public string OSVersion { get; set; }
public string DeviceModel { get; set; }
}
Database:
Table 'vehiclesessions' where the PRIMARY KEY is DeviceUuid and has key 'device2':
Controller:
A request comes in from somewhere. DeviceUuid is fetched from HTTP headers.
public async Task<ActionResult<VehicleLoginResponse>> Login(VehicleLogin login)
{
Request.Headers.TryGetValue(BaseConstants.DEVICE_UUID, out StringValues deviceUuid);
Vehicle vehicle = await vehicleService.Authenticate(login.Username, login.Password); // returns a Vehicle by asking dbContet: dbContext.Vehicles.FirstOrDefault(v => v.Username.Equals(username));
VehicleSession vs = await vehicleService.CreateSession(vehicle, login, deviceUuid);
// ...
}
Service:
This service creates a new VehicleSession object, assigns it to the Vehicle property, and does an .Update on the Vehicle, so that the new session is saved with it.
public async Task<VehicleSession> CreateSession(Vehicle vehicle, VehicleLogin vehicleLogin, string deviceUuid)
{
VehicleSession vs = new VehicleSession()
{
Vehicle = vehicle,
AuthenticationToken = someTokenFetchedFromSomewhere,
DeviceModel = vehicleLogin.DeviceModel,
DeviceUuid = deviceUuid, // is 'device2'
OSName = vehicleLogin.OSName,
OSVersion = vehicleLogin.OSVersion
};
vehicle.Session = vs;
dbContext.Vehicles.Update(vehicle);
await dbContext.SaveChangesAsync();
return vs;
}
It doesn't matter if I replace the new VehicleSession and assignment, with just editing an already existing Vehicle.Session, same error:
public async Task<VehicleSession> CreateSession(Vehicle vehicle, VehicleLogin vehicleLogin, string deviceUuid)
{
if (vehicle.Session == null)
vehicle.Session = new VehicleSession(); // never executes this line
vehicle.Session.AuthenticationToken = someTokenFetchedFromSomewhere;
vehicle.Session.DeviceModel = vehicleLogin.DeviceModel;
vehicle.Session.DeviceUuid = deviceUuid;
vehicle.Session.OSName = vehicleLogin.OSName;
vehicle.Session.OSVersion = vehicleLogin.OSVersion;
await dbContext.SaveChangesAsync();
return vehicle.Session;
}
When doing so, I get an error saying:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred
while updating the entries. See the inner exception for details. --->
MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry
'device2' for key 'vehiclesessions.PRIMARY' --->
MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry
'device2' for key 'vehiclesessions.PRIMARY'
The SQL produced:
Failed executing DbCommand (125ms) [Parameters=[#p0='?' (Size = 95), #p1='?' (Size = 95), #p2='?' (Size = 4000), #p3='?' (Size = 4000), #p4='?' (Size = 4000), #p5='?' (DbType = Int64)], CommandType='Text', CommandTimeout='30']
INSERT INTO `VehicleSessions` (`DeviceUuid`, `AuthenticationToken`, `DeviceModel`, `OSName`, `OSVersion`, `VehicleId`)
VALUES (#p0, #p1, #p2, #p3, #p4, #p5);
fail: Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'blabla'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'device2' for key 'vehiclesessions.PRIMARY'
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'device2' for key 'vehiclesessions.PRIMARY'
Why am I getting this error? It seems like an INSERT is done instead of an UPDATE, even though the Vehicle object is fetched from dbContext, passed along and has PRIMARY id set.
So, I think I might have found the issue.
As noted above, I first did a search to retrieve the Vehicle, like this:
Vehicle vehicle = dbContext.Vehicles.FirstOrDefault(v => v.Username.Equals(username));
In this case, the Vehicle.Session, if one existed, was not populated. Later on, I did the update as the code above shows, and it failed as noted above.
But, if I change the fetch code to this:
Vehicle entityVehicle = dbContext.Vehicles
.Include(x => x.Session)// <-- NEW!
.FirstOrDefault(v => v.Username.Equals(username));
then it works.
It doesn't matter if I assign the .Session a new VehicleSession(...) or if I change the properties in the existing object. It also doesn't matter if I use dbContext.Vehicles.Update(vehicle); before await dbContext.SaveChangesAsync();, the call to .Update(...) is not needed.
The only thing that made a difference, was to use .Include(...).
My own theory is:
When fetching the object from db, the EF Core "tracker" starts tracking. Property Session is NULL. When Session later is populated, the tracker detects that it was NULL but now is not NULL, thus, it figures that an INSERT is needed.
And then, if you populate the Session on fetch, tracker sees it is there, and should be updated.

Grails Database Mapping

my problem is my database mapping and i don't get it working.
I've done this Tutorial.
class Comment {
String comment
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
User user
// delete a comment for a feedback if the feedback item is deleted
static belongsTo=[feedback:Feedback]
static mapping = {
feedback column: 'COMMENT_FEEDBACK_ID', joinTable: false
}
static constraints = {
comment (blank:false, nullable: false, size:5..500)
user (nullable: true) // Comments are allowed without a user
}
String toString(){
if (comment?.size()>20){
return comment.substring(0,19);
} else
return comment;
}}
class Feedback {
String title
String feedback
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
// relationship to the other classes
User user
static hasMany=[comments:Comment]
static mapping = {
comments column: 'FEEDBACK_COMMENT_ID', joinTable: false
}
// constrains are defined as static
static constraints ={
title(blank:false, nullable: false, size:3..80)
feedback(blank:false, nullable:false, size:3..500)
user(nullable:false)
}}
class User {
String name
String email
String webpage
static constraints = {
name (blank:false, nullable:false, size:3..30, matches:"[a-zA-Z1-9_]+")
email (email:true)
webpage (url:true)
}
String toString(){
return name;
}
}
When I try to delete a User which is connected to a feedback/ a comment, I get an error:
Cannot delete or update a parent row: a foreign key constraint fails
(guestbook.comment, CONSTRAINT FK_mxoojfj9tmy8088avf57mpm02
FOREIGN KEY (user_id) REFERENCES user (id))
What should the mapping look like?
You have multiple problems with the domain design, first remove the user from comment ,as the user already have a comment from feedback. if you still wish to keep that design, then define belongsTo to User,in both Comment and Feedback.
Try this...
Add hasOne Feedback to User
class User {
String name
String email
String webpage
hasOne = [feedback:Feedback ]
static constraints = {
name (blank:false, nullable:false, size:3..30, matches:"[a-zA-Z1-9_]+")
email (email:true)
webpage (url:true)
}
String toString(){
return name;
}
}
Add Feedback belongsTo User and cascade on delete for Comment
class Feedback {
String title
String feedback
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
// relationship to the other classes
User user
static belongsTo = [User]
static hasMany=[comments:Comment]
static mapping = {
comments cascade: 'all-delete-orphan',column: 'FEEDBACK_COMMENT_ID', joinTable: false
}
// constrains are defined as static
static constraints ={
title(blank:false, nullable: false, size:3..80)
feedback(blank:false, nullable:false, size:3..500)
user(nullable:false)
}}
Simply remove User from Comment
class Comment {
String comment
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
//User user
// delete a comment for a feedback if the feedback item is deleted
/static belongsTo=[User,feedback:Feedback]
static belongsTo=[feedback:Feedback]
static mapping = {
feedback column: 'COMMENT_FEEDBACK_ID', joinTable: false
}
static constraints = {
comment (blank:false, nullable: false, size:5..500)
//user (nullable: true) // Comments are allowed without a user
}
String toString(){
if (comment?.size()>20){
return comment.substring(0,19);
} else
return comment;
}}

Change item queue in #Html.EnumDropDownListFor

There's this enum
public enum UserStatus
{
Employee = 0,
Evaluation,
Dismissed,
Registered,
}
And on view
#Html.EnumDropDownListFor(model => model.User.Metadata.Status)
So it show me Employee as default option and all other items with enum queue (E,E,D,R). But i'd like to show items in this queue (Evaluation, Registered, Employee, Dismissed) (Mainly Evaluation must be first).
I cant change the enum, and i cant set as default in GET controller (due to model realization).
Any ideas how solve this problem?
I don't think you can change the list during runtime on how it appears. The easiest way i can think to handle such problem ( where you can't change the sequence in which the enum values appear ) is to add attribute on each enum value that defines the sequence number and then extract all the items in a specific enum to create a list which can be binded to the view. This might be an extra work but would solve your issue.
Here's a sample code :
public class Sequence : Attribute
{
public int SequenceNum { get; set; }
}
public enum UserStatus
{
[Sequence(SequenceNum=3)]
Employee = 0,
[Sequence(SequenceNum = 2)]
Evaluation,
[Sequence(SequenceNum = 4)]
Dismissed,
[Sequence(SequenceNum = 1)]
Registered,
}
In your model class :
public IEnumerable<SelectListItem> ListData { get; set; }
public UserStatus Status { get; set; }
In your controller :
List<KeyValuePair<int,string>> KeyValueList = new List<KeyValuePair<int,string>>();
// Get all the enum values
var values = Enum.GetValues(typeof(UserStatus)).Cast<UserStatus>();
// Iterate through each enum value to create a keyvalue list
foreach (var item in values)
{
var type = typeof(UserStatus);
var memInfo = type.GetMember(item.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(Sequence),false);
KeyValueList.Add(new KeyValuePair<int,string>(((Sequence)attributes[0]).SequenceNum,item.ToString()));
}
// Sort the keyvalue list based on the *SequenceNum* attribute
KeyValueList.Sort((firstPair, nextPair) =>
{
return nextPair.Value.CompareTo(firstPair.Value);
});
// Add SelectListItem collection to a model property - Apparently you can add the generate collection to a ViewData ( if you don't wish to create another property )
model.ListData = KeyValueList.Select(x => new SelectListItem { Value = x.Key.ToString(), Text = x.Value }).ToList();
In your View :
#Html.DropDownListFor(m => m.Status, Model.ListData));
When you post the data, the model property Status would be populated with the selected Enum value.
Hope it helps.

Entity 4.1 Updating an existing parent entity with new child Entities

I have an application where you can create a new type of product and add to that product some ingredients. The product and the ingredients are both entities saved in a database. The product entity has a collection of ingredient entities.
(simplified version)
public class Product
Public Sub New()
Me.Ingredients = New List(Of Ingredient)()
End Sub
Property Ingredients as ICollection(Of Ingredient)
end class
When I save the product for the first time, all goes well: I just add it to the context and call SaveChanges.
myDataContext.Products.Add(product)
myDataContext.SaveChanges()
Both the product (parent) and the ingredients (children) are saved and linked to each other. All is well.
However when I add/remove an ingredient to an existing product, I start running into problems. I first clear the existing ingredients collection in the product entity and then add the updated list of ingredients again (I don't re-use ingredients add the moment). I then change the state of the product entity to modified and call savechanges. On the state changing I, however, get the exception "An object with the same key already exists in the ObjectStateManager".
myDataContext.Entry(product).State = EntityState.Modified
After "some" searching I figured out that the problem is that all the ingredients have a primary key of 0 (as they aren't added yet) and when you change the state of the parent entity (product), all child entities (ingredients) are attached to the context with the key of 0, which causes the problem as the keys are no longer unique.
I have been searching for a solution but can't figure out how to solve this problem. I tried adding the ingredients to the context before changing the state, but then the link between the product and ingredients is missing... How do I update an existing parent entity with new, not yet added child entities?
I use Entity Framework 4.1 and Code First.
Hope you can help me!
I first clear the existing ingredients collection in the product
entity and than add the updated list of ingredients again.
Well, this is kind of brute-force-attack to update the child collection. EF doesn't have any magic to update the children - which means: adding new children, deleting removed children, updating existing children - by only setting the state of the parent to Modified. Basically this procedure forces you to delete the old children also from the database and insert the new one, like so:
// product is the detached product with the detached new children collection
using (var context = new MyContext())
{
var productInDb = context.Products.Include(p => p.Ingredients)
.Single(p => p.Id == product.Id);
// Update scalar/complex properties of parent
context.Entry(productInDb).CurrentValues.SetValues(product);
foreach (var ingredient in productInDb.Ingredients.ToList())
context.Ingredients.Remove(ingredient);
productInDb.Ingredients.Clear(); // not necessary probably
foreach (var ingredient in product.Ingredients)
productInDb.Ingredients.Add(ingredient);
context.SaveChanges();
}
The better procedure is to update the children collection in memory without deleting all children in the database:
// product is the detached product with the detached new children collection
using (var context = new MyContext())
{
var productInDb = context.Products.Include(p => p.Ingredients)
.Single(p => p.Id == product.Id);
// Update scalar/complex properties of parent
context.Entry(productInDb).CurrentValues.SetValues(product);
var ingredientsInDb = productInDb.Ingredients.ToList();
foreach (var ingredientInDb in ingredientsInDb)
{
// Is the ingredient still there?
var ingredient = product.Ingredients
.SingleOrDefault(i => i.Id == ingredientInDb.Id);
if (ingredient != null)
// Yes: Update scalar/complex properties of child
context.Entry(ingredientInDb).CurrentValues.SetValues(ingredient);
else
// No: Delete it
context.Ingredients.Remove(ingredientInDb);
}
foreach (var ingredient in product.Ingredients)
{
// Is the child NOT in DB?
if (!ingredientsInDb.Any(i => i.Id == ingredient.Id))
// Yes: Add it as a new child
productInDb.Ingredients.Add(ingredient);
}
context.SaveChanges();
}
I found this recent article on the GraphDiff extension for DbContext.
Apparently it is a generic, reusable variant of Slauma's solution.
Example code:
using (var context = new TestDbContext())
{
// Update DBcompany and the collection the company and state that the company 'owns' the collection Contacts.
context.UpdateGraph(company, map => map.OwnedCollection(p => p.Contacts));
context.SaveChanges();
}
On a side note; I see the author has proposed to the EF team to use his code in issue #864 Provide better support for working with disconnected entities.
I reckon, This is more simpler solution.
public Individual
{
.....
public List<Address> Addresses{get;set;}
}
//where base.Update from Generic Repository
public virtual void Update(T entity)
{
_dbset.Attach(entity);
_dataContext.Entry(entity).State = EntityState.Modified;
}
//overridden update
public override void Update(Individual entity)
{
var entry = this.DataContext.Entry(entity);
var key = Helper.GetPrimaryKey(entry);
var dbEntry = this.DataContext.Set<Individual>().Find(key);
if (entry.State == EntityState.Detached)
{
if (dbEntry != null)
{
var attachedEntry = this.DataContext.Entry(dbEntry);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
base.Update(entity);
}
}
else
{
base.Update(entity);
}
if (entity.Addresses.Count > 0)
{
foreach (var address in entity.Addresses)
{
if (address != null)
{
this.DataContext.Set<Address>().Attach(address);
DataContext.Entry(address).State = EntityState.Modified;
}
}
}
}
after many many months struggling with understanding this entire crappy Entity Framework I hope this can help someone and not go through any of the frustration I have endured.
public void SaveOrder(SaleOrder order)
{
using (var ctx = new CompanyContext())
{
foreach (var orderDetail in order.SaleOrderDetails)
{
if(orderDetail.SaleOrderDetailId == default(int))
{
orderDetail.SaleOrderId = order.SaleOrderId;
ctx.SaleOrderDetails.Add(orderDetail);
}else
{
ctx.Entry(orderDetail).State = EntityState.Modified;
}
}
ctx.Entry(order).State = order.SaleOrderId == default(int) ? EntityState.Added : EntityState.Modified;
ctx.SaveChanges();
}
}

Entity Framework 4.1 Code-First and Inserting New One-to-Many Relationships

I am having trouble peristing a new object graph to the context with a one-to-many relationship. I am using the Entity Framework 4.1 release, and implementing a Code-First approach. I am using an existing SQL 2008 database and implemented a context derived from DbContext. I have two classes, Person and Address. A person can contain 0 or more Addresses, defined as such.
public class Person
{
public Person()
{
Addresses = new List<Address>();
}
public int PersonId { get; set; }
***Additional Primitive Properties***
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int AddressTypeId { get; set; }
***Additional Primitive Properties***
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
I am trying to create a new instance of Person with two addresses. However, when I add this structure to the context and save, only the first Address in the collection is persisted. The second has the Person navigation property set to null, and is not associated with the Person object, however, the first one in the list is associated.
var person = new Person();
var mailingAddress = new Address() { AddressTypeId = 1 };
person.Addresses.Add(mailingAddress);
var billingAddress = new Address() { AddressTypeId = 2 };
person.Addresses.Add(billingAddress);
context.People.Add(entity);
context.SaveChanges();
It does not throw an exception, but the second item in the Address collection is just not saved.
Does anybody have any good ideas on why only the first would be saved? Thank you.
After hours of troubleshooting/trial and error, I've solved my problem.
My POCO classes are also used in a disconnected environment, where
the objects are detached from the context, modified, and then re-attached.
In order to determine which navigation property collection items were affected, I overrode
the Equals and GetHashCode methods in the Address class to determine equality. Apparently this affects the ability for EF 4.1 to insert a complete collection of navigation property objects???
Here are the original equality methods which caused the issue:
public override bool Equals(object obj)
{
Address address = obj as Address;
if (address == null) return false;
return address.AddressId == this.AddressId;
}
public override int GetHashCode()
{
return this.AddressId.GetHashCode();
}
In order to correct the problem, I created a custom equality comparer
for the navigation object rather than including it directly in the address class.
public class AddressEqualityComparer : IEqualityComparer<Address>
{
public bool Equals(Address address1, Address address2)
{
if (address1.AddressId == address2.AddressId)
return true;
else
return false;
}
public int GetHashCode(Address address)
{
return address.AddressId.GetHashCode();
}
}
My context.People.Add method call worked as expected after I made this change.
If anyone knows why overriding the equality methods in the class causes
EF 4.1 to only insert the first item in the collection, that would be
great information.
As hinted at already, it's because the GetHashCode method is using the ID of all the siblings, which will be 0 at point of comparison by Entity Framework. Comment just that out and you will good to go.
I had the same exact issue and this piece let me to that. I didn't even bother to look at my EntityBase code...it's so old and hasn't changed in forever until now.
So a big thank you for your research!
Here is another way to attempt to add the code. Worth a shot. This code may not be exact, I typed freehand.
var person = new Person();
person.Addresses.Add(new Address()
{
AddressTypeId = 1
}),
new Address()
{
AddressTypeId = 2
});
context.People.Add(entity);
context.SaveChanges();