Entity Framework object real update - no duplication - entity-framework

I'm trying to update an object in my database using entity framework 4.1 with code-first and SQL Server 2008. My object's class has a ID field of int type. The problem is that when I update my object and invoke SaveChanges on my DbContext so the database creates a copy of the datarow rather then update the one I already have. I don't want this, I just would like a clean update like in the old-fashioned SQL command UPDATE. How can I fix this?
Nation nation = (nationDB.Nations.Where(m => m.name == "France")).SingleOrDefault();
if (nation != null)
{
Nation modNation = nationDB.Nations.Find(nation.ID);
modNation.Level++;
}
nationDB.SaveChanges();
public class Nation
{
public int ID { get; set; }
public int name { get; set; }
public int level { get; set; }
}

To update it you must first read it from the database, change it, then save it.
If you create a new entity in code, even with an existing key, EF will treat it as a new entity.

I think that the ObjectStateManager has registered your object as being in the Added state. Try to set its status to Modified (by calling GetObjectStateEntry(Object) and checking/modifying the State property).

Related

Can't create related entity in ASP.NET Core with EF Core

I have a problem creating a related entity in Entity Framework Core 2.0. I've just created the solution, consisting of an Asp.Net Core backend project, and a UWP project to act as client. Both solutions share model. The two models are:
public class UnitOfWork {
public int UnitOfWorkId { get; set; }
public string Name { get; set; }
public Human Human { get; set; }
}
public class Human {
public int HumanId { get; set; }
public string Name { get; set; }
public List<UnitOfWork> WorkDone { get; set; }
}
As you can see, model is very simple. One human has many units of work. By the way, the backend is connected to an Azure SQL database. I've seen the migration classes, and the database schema looks good to me.
The problem I have is when I want to create a unit of work referencing an existing human, using HTTP. The controller is fairly simple:
[HttpPost]
public UnitOfWork Post([FromBody] UnitOfWork unitOfWork) {
using (var db = new DatabaseContext()) {
db.UnitsOfWork.Add(unitOfWork);
var count = db.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
}
return unitOfWork;
}
Again, nothing fancy here.
How can I create an unit of work, and assign it to an existing human? If I try it with an existing human, in this way
var humans = await Api.GetHumans();
var firstHuman = humans.First();
var unitOfWorkToCreate = new UnitOfWork() {
Name = TbInput.Text,
Human = firstHuman,
};
I get this error:
Cannot insert explicit value for identity column in table 'Humans' when IDENTITY_INSERT is set to OFF
I feel that setting IDENTITY_INSERT to ON will solve my problem, but this is not what I want to do. In the client, I'll select an existing human, write down a name for the unit of work, and create the latter. Is this the correct way to proceed?
EDIT: Following #Ivan Stoev answer, I've updated the UnitOfWork controller to attach unitofwork.Human. This led to
Newtonsoft.Json.JsonSerializationException: 'Unexpected end when deserializing array. Path 'human.workDone', line 1, position 86.'
Investigating - seen here - EFCore expects to create collections (like human.WorkDone) in the constructor, so I did it, and no more nulls deserializing. However, now I have a self-referencing loop:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected with type 'PlainWorkTracker.Models.UnitOfWork'. Path 'human.workDone'.
Any ideas? Thanks!
The operation in question is falling into Saving Disconnected Entities category.
Add methods marks all entities in the graph which are not currently tracked as new (Added) and then SaveChanges will try to insert them in the database.
You need a way to tell EF that unitOfWork.Human is an existing entity. The simplest way to achieve that is to Attach it (which will mark it as Unchanged, i.e. existing) to the context before calling Add:
db.Attach(unitOfWork.Human);
db.Add(unitOfWork);
// ...

Entity Framework 6.X and one-to-one relationship

I have the following model:
public partial class Driver
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Nickname { get; set; }
public virtual AspNetUser AspNetUser { get; set; }
......
}
public partial class AspNetUser
{
public string Id { get; set; }
public string UserName { get; set; }
public virtual Driver Driver { get; set; }
......
}
and the following mapping:
this.HasOptional(c => c.Driver)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
It creates correct DB model, adds nullable AspNetUserId FK to Driver table.
But how to link one object with another in code. I don't have AspNetUserId property, so, I try to set object, like this:
_db.Drivers.Attach(driver);
_db.AspNetUsers.Attach(aspNetUser);
driver.AspNetUser = aspNetUser;
_db.SaveChanges();
but then I got an exception :
"An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details."
"Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. See
http://go.microsoft.com/fwlink/?LinkId=472540 for information on
understanding and handling optimistic concurrency exceptions."
How to solve it with EF 6.X ?
This is happening when the Driver is already associated with AspNetUser. When you attach the driver with AspNetUser property being null, EF assumes the original value of AspNetUserId being null and generates update statement with AspNetUserId IS NULL additional criteria, which of course does not match the existing record, the command returns 0 records affected and EF generates the exception in question.
The solution is (1) to load the original Driver.AspNetUser property value from the database before setting the new value. Also, in order to correctly handle the case when the new AspNetUser is already associated with a different Driver, you should (2) load AspNetUser.Driver property as well:
_db.Drivers.Attach(driver);
_db.AspNetUsers.Attach(aspNetUser);
_db.Entry(driver).Reference(e => e.AspNetUser).Load(); // (1)
_db.Entry(aspNetUser).Reference(e => e.Driver).Load(); // (2)
driver.AspNetUser = aspNetUser;
_db.SaveChanges();

Adding a new entity to collection in attached entity causes ConcurrencyException

I have simplified the code below to show the root of the problem. My real code is using GenericRepository and UnitOfWork pattern but I get the same exception with this simplified code too.
I am using Entity Framework 6, Code First
It uses the following POCO entities
public class Order
{
public int Id {get;set;}
public virtual List<OrderProducts> OrderProducts {get;set;}
...
}
public class Product
{
public int Id {get;set;}
...
}
public class OrderProduct
{
public int OrderId {get;set;}
public int ProductId {get;set;}
public int Quantity
public virtual Order Order { get; set; }
public virtual Product Product{ get; set; }
}
The user is able to create a new product and add it to the order products on the same screen.
//Pull an order from the database:
var existingOrder = db.Orders.FirstOrDefault(x => x.Id == inputModel.OrderId);
//Iterate the OrderProductInputModels (IMs) in the Inputmodel
foreach (var orderProductIM in inputModel.OrderProductIMs )
{
var orderProduct = existingOrder.OrderProducts.SingleOrDefault(o => o.Id == orderProductIM.Id);
//if its an existing order product (already in db)
if (orderProduct != null)
{
//just update its property values
}
//if it has been added
else
{
//we need to create a new product first
var newProduct= new Product() { <set some properties> };
orderProduct= new OrderProduct()
{
Product=newProduct,
Order=existingOrder
}
//Add the OrderProduct to the order
existingOrder.OrderProducts.Add(orderProduct);
}
db.SaveChanges();
On save changes, I get the following error.
[System.Data.Entity.Infrastructure.DbUpdateConcurrencyException] = {"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}
Why is this?
I expected entity framework to see that the existingOrders nested properties were newly added and unattached, update the order and create the new OrderProduct and Product.
Should it not be other way around in your if clause as you are checking for null ( then only it is a new order product else update. Issue is here:
//if its an existing order product (already in db)
if (orderProduct == null)
{
//just update its property values
}
//if it has been added
else
{
When you are looping around all the OrderProducts, you are constantly updating the database but the existingOrder object is not getting refreshed. Update that or add all the objects first and then update the database.
Finally solved it by creating a test project and reverse code first engineering the database. Noticed that OrderProduct entity was not generated. On inspecting the database, the primary key was not set. Once I set the primary key in the database, the issue was resolved. Thanks for all the suggestions.

Entity Framework Code First - Cannot insert duplicate key in object 'dbo.T_CRProviders'

I have some urgent issue which I could not find answer for across the web.
I am using CodeFirst EF 4.3.1 and I am getting an error:
Violation of PRIMARY KEY constraint 'PK_T_CRProviders'. Cannot insert duplicate key in object 'dbo.T_CRProviders'.
My code is:
Models:
public enum CRProviderEnums
{
PE_Abcd = 0,
PE_Efgh
}
[Table("T_CRProviders")]
public class CRProvider
{
[Key]
[Required]
public int Enum { get; set; }
[Required]
public string Name { get; set; }
}
[Table("T_CRSupportedResources")]
public class CRSupportedResource
{
[Key]
public Guid SupportedResourceId { get; set; }
[Required]
public CRProvider Provider { get; set; }
}
DbContext:
public class RSContext : DbContext
{
public DbSet<CRProvider> CRProviders { get; set; }
public DbSet<CRSupportedResource> CRSupportedResources { get; set; }
}
Table T_CRProviders looks like this: Enum (PK), Name
Table T_CRSupportedResources looks like this: SupportedResourceId (PK), Provider_Enum (FK).
In the database table T_CRProviders I already have a provider with the following values:
Enum: 0 (which is PE_Abcd)
Name: "PE_Abcd"
Now my main() calls a method AddSupportedResource. This method adds to table T_CRSupportedResources a new CRSupportedResource which refers to provider 0 (PE_Abcd). The method looks like this:
public void AddSupportedResource()
{
CRSupportedResource supportedResource = new CRSupportedResource()
{
SupportedResourceId = Guid.NewGuid(),
Provider = new CRProvider()
{
Enum = (int)CRProviderEnums.PE_Abcd,
Name = "PE_Abcd"
}
};
using (RSContext myContext = new RSContext())
{
myContext.CRSupportedResources.Add(supportedResource);
myContext.SaveChanges();
}
}
I expect that this method will leave table T_CRProviders untouched, and add a new row to table T_CRSupportedResources which will look like this:
SupportedResourceId: DE532083-68CF-484A-8D2B-606BC238AB61
Provider_Enum (FK): 0 (which is PE_Abcd).
Instead, upon SaveChanges, Entity framework also tries to add Provider to the T_CRProviders table, and since such a provider already exists it throws the following exception:
An error occurred while updating the entries.
Violation of PRIMARY KEY constraint 'PK_T_CRProviders'. Cannot insert duplicate key in object 'dbo.T_CRProviders'.
The statement has been terminated.
My question:
How can I instruct the EF not to update table T_CRProviders upon updating table T_CRSupportedResources?
Btw, in the SQL Server I see that table T_CRSupportedResources has a foreign key named FK_RW_TCRSupportedCloudResources_RW_TCRCloudProviders_Provider_Enum and its Update Rule has the value of No Action.
I expect that this method will leave table T_CRProviders untouched,
and add a new row to table T_CRSupportedResources
No it will not happen. You are creating detached entity graph consisting of existing entity a and new entity. EF doesn't know about the existence of your entity until you inform it about it - there are no DB queries validating existence performed by EF on behind.
If you call Add method all entities in your entity graph are added as new. If you don't want to insert all of them you can start with using Attach and manually change state for new ones. For example like:
myContext.CRSupportedResources.Attach(supportedResource);
myContext.Entry(supportedResource).State = EntityState.Added;
Actually, there is a way to do this.
See the answer to my question in the following link:
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/62f3e5bc-c972-4622-b830-e7d7fe710101

EF 4.1, POCO: Reference type properties are not updated in case AutoDetectChanges=false

EF 4.1, POCO: I turned off AutoDetectChanges (Configuration.AutoDetectChangesEnabled = false) to speed up data update. Then I run Add or Attach with entity state changed to EntityState.Modified. All this causes references to other objects not being updated in database. However all scalar properties are updated successfully.
Profiler shown EF generates SQL update operation for every scalar property, but not for reference type, though I really changed its value in my code. This issue reproduced for every type of entity in my model.
Add operation or Attach with EntityState.Added both work fine. If I turn AutoDetectChanges back on, everything works fine as expected for updated records too.
Help me please to figure out what's wrong. I can not find any good comprehensive documentation on EF's Detect Changes.
UPDATE
I was asked to put some example of code to reproduce the issue. Domain:
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public virtual string City { get; set; }
}
DataContext:
public class DataContext : DbContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Address> Address { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Client>().HasOptional(c => c.Address);
}
}
Add one record to Clint table and one to Address. Point Client to the Address. Then run the following code:
using (var cntx = new DataContext())
{
cntx.Configuration.AutoDetectChangesEnabled = false; // Reason of problem
var client = cntx.Clients.First();
client.Name = "Anna"; // This property will be updated
client.Address = null; // This property will not be updated
cntx.Clients.Attach(client);
cntx.Entry(client).State = EntityState.Modified;
cntx.SaveChanges();
}
This code does generates SQL script like this:
update [dbo].[Clients] set [Name] = 'Anna'
where ([Id] = 1)
Set AutoDetectChangesEnabled to true and run the code again, this time everything alright:
update [dbo].[Clients]
set [Name] = 'Anna', [Address_Id] = null
where (([Id] = 1) and [Address_Id]=1)
Note it does not matter if you change Address's value from specific value to null, or back to specific value, or one concrete value to other concrete value, any change is not tracked while AutoDetectChanges=false. Seems like EF bug.
See Change Tracking on MSDN
Well, I found out the way to set Reference property values that works even with AutoDetectChangesEnabled=false:
cntx.Entry(client).Reference(c => c.Address).CurrentValue = null;
However I definitely do not like it. 1) Code looks ugly; 2) You have to have access to context to make it work, which is not my case, I'd like this property being set outside of repository which only has access to DbContext. Is any other simpler way to let EF know the property value is changed?
Updated: Ok, I found simpler workaround: just run cntx.ChangeTracker.DetectChanges() before running cntx.SaveChanges(). It helps EF generate correct SQL update script