Data Concurrency Issue with Linq .ToList() and SqlQuery - c#

We save a list of records from one application by removing the list of items and saving the updated list (the list is always less than 10 items):
using (var context = GetContext())
{
EFConfiguration.SuspendExecutionStrategy = true;
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.ItemTable.RemoveRange(context.ItemTable.Where(d => d.ForeignKey == foreignKey));
foreach (var item in items)
{
SetTimeStampFields(item);
context.ItemTable.Add(item);
}
int i = await context.SaveChangesAsync();
dbContextTransaction.Commit();
EFConfiguration.SuspendExecutionStrategy = false;
}
catch (Exception)
{
dbContextTransaction.Rollback();
EFConfiguration.SuspendExecutionStrategy = false;
throw;
}
}
}
These changes show up in the database immediately. Then we pull the info from this table from another application:
var itemList = await context.ItemTable.ToListAsync();
The application seems to pickup the updated values when first deployed but afterwards they are stuck to the same values. I have tried adding AsNoTracking() before .ToListAsync() to try and circumvent any cache as this is readonly data but that did not work. The only solution that I have found that gets the data in real time is:
var itemList = await context.ItemTable.SqlQuery("Select * from TableName").ToListAsync();
The table in question holds records with a primary key, two foreign keys, and some timestamp data.
My question is why will the context not pickup the updated values from the table? This issue manifests in local and deployed environments. This is an ASP.NET Core application using Entity Framework 6. The database in question is a mature SQL Server 2012 database that is used across multiple applications without any issues such as this.

Related

ASP.NET C#: Entity updating is being blocked

Experiencing an issue about updating mysql DB through EF. It's not the first time I'm dealing with it, so I had some ideas about why isn't my data getting changed. I tried changing an element in goods array; tried editing an object, recieved through LINQ-request (seen some examples of this method); made some attempts on marking element found in the database before editing (like EntityState and Attach()). Nothing of these made any difference, so I tried removing <asp:UpdatePanel> from Site.Master to see what happens (responsive for postback blocking to prevent page shaking on update), but nothing changed (while btnRedeemEdit.IsPostBack having its default value).
Code below is the function I use for updates.
protected void btnRedeemEdit_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(Request.QueryString["id"]))
{
var db = new GoodContext();
var goods = db.Goods.ToList();
Good theGood = goods.FirstOrDefault(x => x.Id == int.Parse(Request.QueryString["id"]));
//db.Goods.Attach(theGood);//No effect
//db.Entry(theGood).State = System.Data.Entity.EntityState.Modified; //No effect
if (theGood != default)
{
theGood.AmountSold = GetInput().AmountSold;
theGood.APF = GetInput().APF;
theGood.Barcode = GetInput().Barcode;
theGood.Description = GetInput().Description;
theGood.ImagesUrl = GetInput().ImagesUrl;//"https://i.pinimg.com/564x/2d/b7/d8/2db7d8c53b818ce838ad8bf6a4768c71.jpg";
theGood.Name = GetInput().Name;
theGood.OrderPrice = GetInput().OrderPrice;
theGood.Profit = GetInput().Profit;
theGood.RecievedOn = GetInput().RecievedOn;//DateTime.Parse(GetInput().RecievedOn).Date.ToString();
theGood.TotalAmount = GetInput().TotalAmount;
theGood.WeightKg = GetInput().WeightKg;
//SetGoodValues(goods[editIndex],GetInput());//Non-working
db.SaveChanges();
Response.Redirect("/AdminGoods");
}
else Response.Write($"<script>alert('Good on ID does not exist');</script>");
}
else Response.Write($"<script>alert('Unable to change: element selected does not exist');</script>");
}
Notice, that no alerts appear during execution, so object in database can be found.
Are there any more things, that can be responsible for blocking database updates?
A few things to update & check:
Firstly, DbContexts should always be disposed, so in your case wrap the DbContext inside a using statement:
using (var db = new GoodContext())
{
// ...
}
Next, there is no need to load all goods from the DbContext, just use Linq to retrieve the one you want to update:
using (var db = new GoodContext())
{
Good theGood = db.Goods.SingleOrDefault(x => x.Id == int.Parse(Request.QueryString["id"]));
if (theGood is null)
{
Response.Write($"<script>alert('Good on ID does not exist');</script>");
return;
}
}
The plausible suspect is what does "GetInput()" actually do, and have you confirmed that it actually has the changes you want? If GetInput is a method that returns an object containing your changes then it only needs to be called once rather than each time you set a property:
(Inside the using() {} scope...)
var input = GetInput();
theGood.AmountSold = input.AmountSold;
theGood.APF = input.APF;
theGood.Barcode = input.Barcode;
theGood.Description = input.Description;
// ...
db.SaveChanges();
If input has updated values but after calling SaveChanges you aren't seeing updated values in the database then there are two things to check.
1) Check that the database connection string at runtime matches the database that you are checking against. The easiest way to do that is to get the connection string from the DbContext instance's Database.
EF 6:
using (var db = new GoodContext())
{
var connectionString = db.Database.Connection.ConnectionString; // Breakpoint here and inspect.
EF Core: (5/6)
using (var db = new GoodContext())
{
var connectionString = db.Database.GetConnectionString();
Often at runtime the DbContext will be initialized with a connection string from a web.config / .exe.config file that you don't expect so you're checking one database expecting changes while the application is using a different database / server. (More common than you'd expect:)
2) Check that you aren't disabling tracking proxies. By default EF will enable change tracking which is how it knows if/when data has changed for SaveChanges to generate SQL statements. Sometimes developers will encounter performance issues and start looking for ways to speed up EF including disabling change tracking on the DbContext. (A fine option for read-only systems, but a pain for read-write)
EF6 & EF Core: (DbContext initialization)
Configuration.AutoDetectChangesEnabled = false; // If you have this set to false consider removing it.
If you must disable change tracking then you have to explicitly set the EntityState of the entity to Modified before calling SaveChanges():
db.Entry(theGood).State = EntityState.Modified;
db.SaveChanges();
Using change tracking is preferable to using EntityState because with change tracking EF will only generate an UPDATE statement if any values have changed, and only for the values that changed. With EntityState.Modified EF will always generate an UPDATE statement for all non-key fields regardless if any of them had actually changed or not.

Improve concurrency performance in ASP .NET Core 3.1 and Entity Framework Core?

At first I didn't know if my question is related to concurrency performance, but the scenario is I want to insert some events come from a PBX server and based on the type of each event, fill specific column value.
Events come too fast continuously and the duration between two events might be about 0.017s as below screenshot from inserted events.
The listener application is developed in ASP.NET 3.1 which has a class derived from .NET core Background Service class and DBContext is provided by Entity Framework Core 3.1.2.
In ExecuteAsync method event listeners have been provided. So in event listener for inserting events to database, I'm using .NET Core Service Provider scope to inject needed repository.
using (var scope = _service.CreateScope())
{
var callStatRepo = scope.ServiceProvider.GetRequiredService<ICallStatRepository>();
var lastState = await callStatRepo.GetLastStateOfDevice(e.Channel.Name);
var callTypeSb = new StringBuilder();
if (lastState != null && String.Equals(lastState, "ANSWER",
StringComparison.CurrentCultureIgnoreCase))
{
callTypeSb.Append(CallType.OUTCALL.Value);
}
else if (lastState != null && String.Equals(lastState, "InboundStarts",
StringComparison.CurrentCultureIgnoreCase))
{
callTypeSb.Append(CallType.INBOUND.Value);
}
await callStatRepo.InsertCallStat(new RawCallStatRegisterViewModel
{
HappenedAt = DateTime.Now,
Type = callTypeSb.ToString(),
Device = e.Channel.Name,
Number = e.Channel.Connected.Number,
State = e.Channel.State
});
callTypeSb.Clear();
}
public async Task InsertCallStat(RawCallStatRegisterViewModel model)
{
var rawCall = new DeviceState
{
HappenedAt = model.HappenedAt,
Type = model.Type,
Device = model.Device,
Number = model.Number,
State = model.State
};
_context.Entry(rawCall).State = EntityState.Added;
try
{
await _context.SaveChangesAsync();
}
catch (Exception e)
{
throw new Exception("Insert new call stat falis with this error : " + e.Message);
}
}
The problem is around filling 'Type' column based on last previous 'State'. The condition for filling 'Type' is checking if last previous state was 'ANSWER' or 'Ringing'. But sometimes it cannot be figured it out what was the last state and leaves the Type column empty.
I think this problem is may related to inserting records too fast and don't know what is the solution for this. May I need to change the database type to any other type of databases such as MongoDB? And could it be the right solution? Are No-SQL databases faster than the SQL Server in concurrency?

EF5 can not handle Concurrency when Updating selective fields

I am using EF5 and Data First approach to Update entities.
I am using approach suggested by other questions to conditionally update only modified properties in the Entities.
Oki so here's the scenario My controller call Service with POCO objects and gets POCO objects from Service, The Service layer talks with Data layer which internally uses EF5 to retrieve entity from DB and Update them in DB.
The View data is loaded by controller from DTO object retrieved from Service layer.
User makes changes to View and Posts back JSON data to controller which gets mapped to DTO object in controller (courtesy MVC).
The controller makes call to Service layer with the DTO object (POCO) object.
The Service maps the POCO object to EF entity object and calls the Data layer's(i.e Repository) Update method passing in the EF entity.
In the Repository I fetch the existing entity from DB and call ApplyCurrentvaluesValues method, then I check if any properties are modified .
If properties are modified then I apply my custom logic to other entities which are not related to current entity and also Update the "UpdatedAdminId" & "UpdationDate" of current entity.
Post this I call "SaveChanges" method on Centext.
Every thing above I mentioned is working fine , except if I insert a break point in "SaveChanges" call and update some field modified by User to different value then "DbUpdateConcurrencyException" is not thrown by EF5.
i.e. I can get conditional Update & fire my custom logic when properties of my interest are modified to work perfectly.
But I am not getting error in case of the concurrency i.e the EF is not raising "DbUpdateConcurrencyException" in case a record is updated in between me fetching the record from DB , updating the record and saving it.
In real scenario there is a offline cron running which checks for newly created campaign and creates portfolio for them and marks the IsPortfolioCreated property below as true, in the mean time user can edit the campaign and the flag can be set to false even though the cron has created the portfolios.
To replicate the concurrency scenario I put a break point on SaveChanges and then Update the IsPortfolioCreated feild from MS-Sql enterprise manager for the same entity, but the "DbUpdateConcurrencyException" is not thrown even though the Data in Store has been updated.
Here's my code for reference,
Public bool EditGeneralSettings(CampaignDefinition campaignDefinition)
{
var success = false;
//campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client
var updatedAdminId = campaignDefinition.UpdatedAdminId;
var updationDate = DateTime.UtcNow;
CmsContext context = null;
GlobalMasterContext globalMasterContext = null;
try
{
context = new CmsContext(SaveTimeout);
var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();
//Always use this fields from Server, no matter what comes from client
campaignDefinition.CreationDate = contextCampaign.CreationDate;
campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;
campaignDefinition.UpdationDate = contextCampaign.UpdationDate;
campaignDefinition.AdminId = contextCampaign.AdminId;
campaignDefinition.AutoDecision = contextCampaign.AutoDecision;
campaignDefinition.CampaignCode = contextCampaign.CampaignCode;
campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;
var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName;
// Will be used in the below if condition....
var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();
var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();
//This also not firing concurreny exception....
var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition);
((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign);
var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);
var modifiedProperties = entry.GetModifiedProperties();
//Even tried this , works fine but no Concurrency exception
//var entry = context.Entry(contextCampaign);
//entry.CurrentValues.SetValues(campaignDefinition);
//var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
// If any fields modified then only set Updation fields
if (modifiedProperties.Count() > 0)
{
campaignDefinition.UpdatedAdminId = updatedAdminId;
campaignDefinition.UpdationDate = updationDate;
//entry.CurrentValues.SetValues(campaignDefinition);
updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
//Also perform some custom logic in other entities... Then call save changes
context.SaveChanges();
//If campaign name changed call a SP in different DB..
if (campaignNameChanged)
{
globalMasterContext = new GlobalMasterContext(SaveTimeout);
globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId);
globalMasterContext.SaveChanges();
}
}
success = true;
}
catch (DbUpdateConcurrencyException ex)
{
//Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry
//In short Store Wins Strategy
//Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question...
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
finally
{
if (context != null) context.Dispose();
if (globalMasterContext != null) globalMasterContext.Dispose();
}
return success;
}
Entity framework it's not doing anything special about concurrency until you (as developer) configure it to check for concurrency problems.
You are trying to catch DbUpdateConcurrencyException, the documentation for this exception says: "Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected. ", you can read it here
In a database first approach, you have to set the property 'Concurrency Mode' for column on 'Fixed' (the default is None). Look at this screenshot:
The column Version is a SQL SERVER TIMESTAMP type, a special type that is automatically updated every time the row changes, read about it here.
With this configuration, you can try with this simple test if all is working as expected:
try
{
using (var outerContext = new testEntities())
{
var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
outerCust1.Description += "modified by outer context";
using (var innerContext = new testEntities())
{
var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
innerCust1.Description += "modified by inner context";
innerContext.SaveChanges();
}
outerContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ext)
{
Console.WriteLine(ext.Message);
}
In the example above the update from the inner context will be committed, the update from the outer context will thrown a DbUpdateConcurrencyException, because EF will try to update the entity using 2 columns as a filters: the Id AND the Version column.
Hope this helps!

Bulk insert with EF

I need to insert some objects (about 4 million) in the database using C# and EF (using .NET 3.5). My method that adds the objects is in a for:
private DBModelContainer AddToContext(DBModelContainer db, tblMyTable item, int count)
{
db.AddTottblMyTable (item);
if ((count % 10000== 0) || (count == this.toGenerate))
{
try
{
db.SaveChanges();
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}
}
return db;
}
How to detach the added objects (of type tblMyTable) from the context object? I don't need them for a later use and when more than 300000 objects are added, the execution time between db saving ( db.SaveChanges()) increases considerably.
Regards
Entity Framework may not be the best tool for this type of operation. You may be better off with plain ADO.Net, some stored procedures... But if you had to use it, here are a number of suggestions:
Keep the active Context Graph small by using a new context for each
Unit of Work
Turn off AutoDetechChangesEnabled - context.Configuration.AutoDetectChangesEnabled = false;
Batching, in your loop, Call SaveChanges periodically
EDIT
using(var db = new DBModelContainer())
{
db.tblMyTable.MergeOption = MergeOption.NoTracking;
// Narrow the scope of your db context
db.AddTottblMyTable (item);
db.SaveChanges();
}
Keeping a long running db context is not advisable, so consider refactoring your Add method to not keep attempting to reuse the same context.
See Rick Strahl's post on bulk inserts for more details
AFAK EF does not support directly the BulkInsert so it will be tedious to do such thing manually.
try to consider EntityFramework.BulkInsert
using (var ctx = GetContext())
{
using (var transactionScope = new TransactionScope())
{
// some stuff in dbcontext
ctx.BulkInsert(entities);
ctx.SaveChanges();
transactionScope.Complete();
}
}
You may try Unit Of Work and dont save context (SaveChanges) on every record insert but save it at end

Distributed transactions SQL Server <-> MySql. Exception: MySQL Connector/Net does not currently support distributed transactions

I have to sync some data from a SQL Server 2008 DB to a MySql DB.
I'm using C# and EF to manage data.
Flow is: read unsynced rows from SQL server, convert the entries (through the class EntitiesConverter) to MySql db entities, add rows to MySql, mark SQL rows as synced (and save changes on both contexts).
Code:
public static int SyncAttivitaToHiPlan(int start, int? end)
{
var options = new TransactionOptions();
options.IsolationLevel = IsolationLevel.Serializable;
using (var scope = new TransactionScope(TransactionScopeOption.Required, options))
{
//MySql ObjectContext
var hpContext = new hiplanEntities1();
//SQL Server ObjectContext
var otoContext = new OTOCOMMEntities1();
var otoProvider = OtoCommEntitiesProvider.GetInstance();
IEnumerable<Attivita> attivitaEntries;
if (end == null)
attivitaEntries = otoProvider.GetUnsyncedAttivita(start);
else
attivitaEntries = otoProvider.GetUnsyncedAttivita(start, (int)end);
var tvavalavs = new ConcurrentQueue<tvavalav>();
//ObjectContext is not thread-safe
//attivitaEntries.AsParallel().AsOrdered().ForAll((a) => tvavalavs.Enqueue(EntitiesConverter.BuildTvavalavFromAttivita(a)));
foreach (var a in attivitaEntries)
{
tvavalavs.Enqueue(EntitiesConverter.BuildTvavalavFromAttivita(a));
}
foreach (var t in tvavalavs)
{
hpContext.tvavalav.AddObject(t);
}
// attivitaEntries.AsParallel().ForAll((a) => a.Synced = true);
foreach (var a in attivitaEntries)
{
a.Synced = true;
}
hpContext.SaveChanges();
otoContext.SaveChanges();
}
return 0; //TODO: return the number of synced rows.
}
}
When I try it, I get a MySql Connector Exception: "MySQL Connector/Net does not currently support distributed transactions."
How can I solve this? Is there a way to do this in a single transaction ("virtual" singles transaction, as I am working with 2 differnt db) without getting that exception?
How can I solve this?
My car is broken, how can I solve this?
Sorry, the cnnector is not according to spec and does not Support distributed Transactions. EIther find a way to do that without them, or fix the connector, or use one that does Support them. Simply like that.
Separate Transactions are complex - a DTC is a VEY complexy beast. Yes, 2 Transactions work. Unless they fail - i.e. a machine or the Network goes down, then you have half open Transactions (one commited, the other rolled back).
YOu COULD do a merge mechanism - mark the class inthe source only as symced when this is done (after commiting mysql) AND
write the target part so it "updates" or sees that rows already were there, but that will slow thigns down.
Then you can work around it.

Categories