EF6 DbUpdateConcurrencyExcpetion in Disconnected Scenario - c#

I'm struggling with a DbUpdateConcurrencyException in a disconnected scenario, using EF6 (Entity Framework 6) with a Oracle DB.
Basically my code looks like this (simplyfied):
//retrieve transfer object from db (with statusid = 2)
//do something
transfer.StatusId = 3;
using (TransferContext tc = new TransferContext())
{
tc.Entry(transfer).State = EntityState.Modified;
tc.SaveChanges();
}
EF logs the following SQL:
update "T_TRANSFER_SC"
set "STATUS_ID" = :p0
where (("ID" = :p1)
and ("STATUS_ID" = :p2))
-- :p0: '3' (Type = Decimal)
-- :p1: '1' (Type = Decimal)
-- :p2: '3' (Type = Decimal)
-- Completed in 3 ms with result: 0
Both the ID as well as STATUS_ID property are set to: Concurreny Mode = Fixed.
As you can see the parameters p0 and p2 have the value 3, but p2 should have the value 2 (at least from my understanding), which is the old value of the property.
If I execute the code I get an DbUpdateConcurrencyException exception:
"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."
The exception is relatable, because there is no record with ID = 1 and STATUS_ID = 3 in the database (record in db has values 1 and 2). What I don't understand, is why EF does not use the old value of STATUS_ID in the Where-Clause.
Am I missing something, or is this always the case when working with different contexts (disconnected)? And how do I fix this?

Related

Why does trying to update through an Entity reference throw this exception in EF6?

I have a DevExpress GridControl with rows from the database. The currently selected row is captured using a binding, and it's name is SelectedItem.
I then try to edit the data for that row like so:
var entry = context.Entry(SelectedItem);
if (entry != null)
{
entry.Entity.Name = addForm.InstrumentName;
entry.Entity.InstrumentType = addForm.InstrumentType;
entry.Entity.Units = addForm.Units;
entry.Entity.RollOver = addForm.RollOver;
entry.State = EntityState.Modified;
try { context.SaveChanges(); }
catch (Exception ex)
{
//ex is of type DbUpdateConcurrencyException
}
}
However, if I use FirstOrDefault to pull a fresh entity using just the SelectedItem.ID, then I can make changes and save the record.
Now, I've been laboring under the idea that the former method is the preferred one for making changes to an entity, especially when that entity reference is already at hand. Am I wrong? If so, why?
If I'm not, then can someone help to explain why I might get a concurrency error?
Exception Details:
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.
Resulting Log Output (as requested):
UPDATE [dbo].[Instrument]
SET [EquipmentID] = #0, [Name] = #1, [InstrumentType] = #2, [Units] = #3, [RollOver] = #4
WHERE (([ID] = #5) AND [Timestamp] IS NULL)
SELECT [Timestamp]
FROM [dbo].[Instrument]
WHERE ##ROWCOUNT > 0 AND [ID] = #5
-- #0: '0' (Type = Int32)
-- #1: 'Hour meter' (Type = String, Size = 50)
-- #2: '0' (Type = Int32)
-- #3: 'hours1' (Type = String, Size = 25)
-- #4: '999999.00' (Type = Decimal, Precision = 18, Scale = 2)
-- #5: '2' (Type = Int32)
-- Executing at 4/26/2018 3:57:12 PM -04:00
-- Completed in 2 ms with result: SqlDataReader

SET IDENTITY_INSERT [table] ON not working

I want to insert some records where I specify the Id, for the purpose of migrating data where I would like to maintain the integrity of existing relationships.
To do this I ran this command directly in SSMS on the table:
SET IDENTITY_INSERT [CRMTItem] ON;
Yet, when I insert an item from C# with Id of 1, the Id is still incrementing from around 850.
I deleted the entities from EDMX and updated again from DB but with the same result.
Here is my insert code, where as you can see I am ensuring that the Id is indeed 1 before inserting, yet this just gets ignored..
var crmtItem = new CRMTItem();
crmtItem.Id = adv.PrimaryId;
crmtItem.ProjectTitle = adv.ProjectTitle;
crmtItem.CreatedByUser = (adv.CreatedBy == null) ? (Guid?)null : new Guid(adv.CreatedBy);
crmtItem.Opportunity = (adv.Opportunity == null) ? (Guid?)null : new Guid(adv.Opportunity);
crmtItem.BidNoBid = adv.Bnb;
crmtItem.SPUrl = adv.SPUrl;
crmtItem.BnbId = (adv.BnbId == null) ? (Guid?)null : new Guid(adv.BnbId);
crmtItem.Stage = adv.ProjectStage;
crmtItem.Confidential = adv.Confidential;
crmtItem.OpportunityStatus = adv.OpportunityStatus;
crmtItem.OpportunityNumber = adv.OpportunityNumber;
crmtItem.CRMTNumber = adv.CrmtNumber;
crmtItem.ProjectNumber = adv.ProjectNumber;
crmtItem.Sector = adv.Sector;
crmtItem.Service = adv.Service;
crmtItem.CreatedDate = adv.CreatedDate;
crmtItem.Archive = adv.Archive;
crmtItem.ProjectManager = adv.ProjectManager;
crmtItem.WorkTeam = adv.WorkTeam;
crmtItem.Custodian = adv.Custodian;
db.CRMTItems.Add(crmtItem);
if (adv.PrimaryId == 1 || adv.PrimaryId == 2 || adv.PrimaryId == 3)
{
await db.SaveChangesAsync();
}
I also tried adding this line before inserting the item
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[CRMTItem] ON");
But it still doesn't work.
Based on another SO question I found, I tried this next:
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[CRMTItem] ON");
db.CRMTItems.Add(crmtItem);
if (adv.PrimaryId == 1)
{
await db.SaveChangesAsync();
}
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[CRMTItem] OFF");
transaction.Commit();
And now I get an error
Explicit value must be specified for identity column in table 'CRMTItem' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.
Am i missing something? Why must it be so difficult to control my own data? If I can't achieve this, I will be forced to creat a temporary column in my table just to store the id from the original (CDS) table, which is absolutely ridiculous, after all it is MY DATA, why can't I choose the value of the columns!?!?!
When you generate your model from database - Entity Framework will map all identity columns to model properties with StoreGeneratedPattern.Identity. In your case, such property is crmtItem.Id as I understand. When you insert crmItem - Entity Framework will ignore value you set for identity property (if you set any), because it knows this value is provided by database, so it knows if it tries to provide such value in insert statement - database will return an error.
Entity Framework has no knowledge of IDENTITY_INSERT, so it will always behave according to StoreGeneratedPattern metadata of target model property. If it's Identity or Computed - it will not provide value for it in insert, whatever you do. If it's set to None - then it will provide a value (no matter what).
So for your case you need to set this attribute to None in EDMX designer for target property (CRMTItem.Id). Of course after doing that - you will have to always provide this value while inserting.
Another part of the problem, with IDENTITY_INSERT being not respected, you already solved but still worth some explanation. This setting is session-scoped, so when you just execute it in SSMS and then try to insert from your application - it has no effect: SSMS and your application are in different sessions.
When you just do:
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[CRMTItem] ON");
This still executes in separate session, not in the same your SaveChanges will execute. So to execute both IDENTITY_INSERT and SaveChanges in the same session - you need to wrap them in transaction, as you already figured out.

How to achieve partial update in Entity Framework 5/6?

I am working on Entity framework with database first approach and I came across below issue.
I have a Customer table with columns col1, col2, col3 ,....,col8. I have created an entity for this table and this table has around 100 records already. Out of above 8 columns, col4 is marked as Non-null.
Class Customer
{
member col1;
member col2;
member col3;
member col4;
.
.
member col8;
}
class Main
{
//main logic to read data from database using EF
Customer obj = object of Customerwith values assigned to col1,col2 and col3 members
obj.col2=some changed value.
DBContext.SaveChanges(); //<- throws an error stating it is expecting value of col4.
}
In my application, I am trying to read the one of the record using the stored procedure using EF and stored procedure only returns col1,col2 and col3.
I am trying to save the modified value of col2 and trying to save back to database using DBContext. But it thows an error stating value of required field col4 is not provided.
FYI: I have gone through couple of forums and question and option to go with disabled verfication on SaveChanges is not feasible for me.
Is there any other way through which I can achieve partial update?
I guess EntityFramework.Utilities satisfies your conditions.
This code:
using (var db = new YourDbContext())
{
db.AttachAndModify(new BlogPost { ID = postId }).Set(x => x.Reads, 10);
db.SaveChanges();
}
will generate single SQL command:
exec sp_executesql N'UPDATE [dbo].[BlogPosts]
SET [Reads] = #0
WHERE ([ID] = #1)
',N'#0 int,#1 int',#0=10,#1=1
disabled verfication on SaveChanges is not feasible for me
Sure it is. You even have to disable validation on Save. But then you can't mark the whole entity as modified, which I think you did. You must mark individual properties as modified:
var mySmallCustomer = someService.GetCustomer(); // from sproc
mySmallCustomer.col2 = "updated";
var myLargeCustomer = new Customer();
context.Customers.Attach(myLargeCustomer);
Entry(myLargeCustomer).CurrentValues.SetValues(mySmallCustomer);
// Here it comes:
Entry(myLargeCustomer).Property(c => c.col2).IsModified = true;
context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
So you see it's enough to get the "small" customer. From this object you create a stub entity (myLargeCustomer) that is used for updating the one property.

EF Attach won't update database fields to null

I'm facing a problem that when I set a field any value than empty, EF will update the database.
But, when I clear the field (input[text] empty), the matching field in DB won't update to NULL.
Here are the codes:
C#
var entidade = new Plantas { CodPlanta = vm.CodPlanta };
db.Plantas.Attach(entidade);
entidade.CodEstado = vm.CodEstado;
entidade.NomePlanta = vm.NomePlanta;
entidade.CEP = vm.CEP;
entidade.Telefone = vm.Telefone;
entidade.Fax = vm.Fax;
db.SaveChanges();
SQL generated by EF
exec sp_executesql N'update [dbo].[Plantas]
set [CodEstado] = #0, [NomePlanta] = #1, [CEP] = #3
where ([CodPlanta] = #4)
',N'#0 int,#1 varchar(200),#3 char(8),#4 int',
#0=26,
#1='ApiaĆ­',
#2='Integrada',
#3='18320000',
#4=373
go
See that when vm.Telefone and vm.Fax, these fields won't show in UPDATE query. But if a put some value, they will.
Even if the fields have some value before.
If a put a breakpoint at db.Savechanges(), it will show entidade.Telefone = null. But it won't go to the generated query.
How can I force Entity Framework to set fields to null?
The reason is that EF will only save modified fields. So if the field was null before you attach the object, and you set it to null again, EF will not update the database.
The solution is pretty simple: set the field to something other than null before you attach the object, than modify it to null before you save it.
One caveat: if you have optimistic concurrency control on the field, you may have to read the record from the database instead of attaching an empty object. Alternatively, use one column for optimistic concurrency control, such as a version number or SQL Server timestamp column.
For example, say you have this problem with the CEP column. Then do:
var entidade = new Plantas { CodPlanta = vm.CodPlanta };
entidade.CEP = "fake old value";
db.Plantas.Attach(entidade);
entidade.CEP = vm.CEP;
db.SaveChanges();
I found that setting attached instance of entity to "Modified" works:
db.Entry(entidade) = EntityState.Modified;
db.SaveChanges();

StackOverflowException when call DataContext.SubmitChanges()

I am using Linq-To-SQL to perform inserts into a table that has the following definition (omitting some superfluous fields):
ID int not null
SourceName varchar(100)
Version int not null
TransactionID int not null
Xml nvarchar(max)
ID, SourceName, Version and TransactionID form the primary key for the table. There are no foreign keys or constraints.
I create a DataContext for my database and then create a new record. When I call SubmitChanges on my DataContext a StackOverflowException is thrown.
using (var ctx = new MyDataContext(connectionString))
{
var row = new MyTable
{
ID = 1
, SourceName = "foo"
, Version = 1
, TransactionID = 0 //this is the weird part - see below
, Xml = "some xml string"
}
ctx.MyTable.InsertOnSubmit(row);
ctx.SubmitChanges();
}
However, after lots of value substituting and trial and error the StackOverflowException does not get thrown if I change TransactionID to 1 (I initially assumed the Xml field was somehow overflowing).
I was using 0 just for the scenarios where for a transaction id could not be identified some reason.
I obviously googled this but the only related issue I found was caused by a foreign key relationship.
Anyone have any idea why this is happening? I have a work around but am curious what could be the cause.
I am using .Net 3.5 and SQL Server 2005.

Categories