In the following code, I receive an error in _context.SaveChanges(); when adding a new record to FeedbackComments inside the first foreach loop. The error is New transaction is not allowed because there are other threads running in the session.. Any ideas why this is happening?
BTW, I keep receiving the same error when SaveChanges is called only once after the outer loop.
List<FeedbackComment> feedbackComments = comments.Select(c => new FeedbackComment
{
Id = c.Id,
CommentText = c.Content,
SubmissionId = submissionId,
UserDisplayName = c.Author.DisplayName,
DateCreated = c.CreatedTime.GetValueOrDefault(),
FeedbackReplies = c.Replies.Select(r => new FeedbackReply
{
Id = r.Id,
UserDisplayName = r.Author.DisplayName,
ReplyText = r.Content,
DateCreated = r.CreatedTime.GetValueOrDefault(),
FeedbackCommentId = c.Id
}).ToList()
}).ToList();
_context.SaveChanges();
foreach (FeedbackComment c in feedbackComments)
{
if (!_context.FeedbackComments.Any(fc => fc.Id == c.Id))
{
ApplicationUser commentOwner = _context.ApplicationUsers.FirstOrDefault(au => au.GoogleDisplayName == c.UserDisplayName);
if(commentOwner != null)
{
c.UserId = commentOwner.Id;
_context.FeedbackComments.Add(c);
newComments = true;
_context.SaveChanges();
}
}
foreach (FeedbackReply r in c.FeedbackReplies)
{
if (!_context.FeedbackReplies.Any(fr => fr.Id == r.Id))
{
ApplicationUser replyOwner = _context.ApplicationUsers.FirstOrDefault(au => au.GoogleDisplayName == c.UserDisplayName);
if (replyOwner != null)
{
r.UserId = replyOwner.Id;
_context.FeedbackReplies.Add(r);
newComments = true;
_context.SaveChanges();
}
}
}
}
When you are trying to save a change using a transaction, you should wait until any other transactions are completed. On the other hand, waiting for the previous transaction to be completed, makes severe performance issue.
You should put _context.SaveChanges(); outside the foreach loop like this:
foreach (FeedbackReply r in c.FeedbackReplies)
{
if (!_context.FeedbackReplies.Any(fr => fr.Id == r.Id))
{
ApplicationUser replyOwner = _context.ApplicationUsers.FirstOrDefault(au => au.GoogleDisplayName == c.UserDisplayName);
if (replyOwner != null)
{
r.UserId = replyOwner.Id;
_context.FeedbackReplies.Add(r);
newComments = true;
}
}
}
_context.SaveChanges();
In the above code, all changes are applied to the database in one transaction.
Why are you calling SaveChanges after the initial data fetch? If you leave that one out and call SaveChanges only once, at the end, it should work, as Hadi said.
Related
I am using the code below to insert records into a SQL Server table. The code works perfectly, but the part I need to add is a check to make sure that the ID passed does not exist before inserting it. If records with the passed ID exists, those records need to be deleted, and then inserted again.
Would it be possible to ask for help to figure out the part that would check the ID before the insert?
I will continue to try to figure it out on my own, but I hope someone could offer some help if possible.
Here is my code:
public ActionResult InsertData(List<Meeting> meeting)
{
bool status = false;
if (ModelState.IsValid)
{
using (MeetingEntities db = new MeetingEntities())
{
foreach (var i in meeting)
{
db.Meeting.Add(i);
}
db.SaveChanges();
status = true;
}
}
return new JsonResult { Data = new { status = status } };
}
Thank you,
Erasmo
Check it against our meeting list before adding to the context object like
using (MeetingEntities db = new MeetingEntities())
{
foreach (var i in meeting)
{
if(!meeting.Any(x => x.ID == i.ID)) {
db.Meeting.Add(i);
}
}
db.SaveChanges();
status = true;
}
You said * I need to check and if exists delete those records with that MeetingId* .. then you can do something like below
var meetingIds = meeting.Select(x => x.ID).ToList();
db.Meeting.Where(x => meetingIds.Contains(x.ID))
.ToList().ForEach(db.Meeting.DeleteObject);
db.SaveChanges();
Well you can combine of this operations
using (MeetingEntities db = new MeetingEntities())
{
//insert operation
foreach (var i in meeting)
{
if(!meeting.Any(x => x.ID == i.ID)) {
db.Meeting.Add(i);
}
}
//Delete Operation
var meetingIds = meeting.Select(x => x.ID).ToList();
db.Meeting.Where(x => meetingIds.Contains(x.ID))
.ToList().ForEach(db.Meeting.DeleteObject);
// Save the changes
db.SaveChanges();
status = true;
}
maybe try to check if is already present if not insert it.. like:
public ActionResult InsertData(List<Meeting> meeting)
{
bool status = false;
if (ModelState.IsValid)
{
using (MeetingEntities db = new MeetingEntities())
{
foreach (var i in meeting)
{
if(db.Meeting.FirstOrDefault(xx=>xx. ID == i. ID) == null)
{
db.Meeting.Add(i);
}
}
db.SaveChanges();
status = true;
}
}
return new JsonResult { Data = new { status = status } };
}
What is the best way to update multiple records in a list to speed up processing?
Currently, I'm updating about 15000 products, each with 3 different price sets and it takes the whole day to complete.
I need to update the prices all at once in code side, then commit those changes to the database in 1 go, instead of fetching each inventory item, updating its values, then attaching it to the context. Every single fetch is causing the delays.
Code
public void UpdatePricesFromInventoryList(IList<Domain.Tables.Inventory> invList)
{
var db = new UniStockContext();
foreach (var inventory in invList)
{
Domain.Tables.Inventory _inventory = db.Inventories
.Where(x => x.InventoryID == inventory.InventoryID)
.FirstOrDefault();
if (inventory.Cost.HasValue)
_inventory.Cost = inventory.Cost.Value;
else
_inventory.Cost = 0;
foreach (var inventoryPrices in inventory.AccInventoryPrices)
{
foreach (var _inventoryPrices in _inventory.AccInventoryPrices)
{
if (_inventoryPrices.AccInventoryPriceID == inventoryPrices.AccInventoryPriceID)
{
_inventoryPrices.ApplyDiscount = inventoryPrices.ApplyDiscount;
_inventoryPrices.ApplyMarkup = inventoryPrices.ApplyMarkup;
if (inventoryPrices.Price.HasValue)
_inventoryPrices.Price = inventoryPrices.Price.Value;
else
_inventoryPrices.Price = _inventory.Cost;
if (inventoryPrices.OldPrice.HasValue)
{
_inventoryPrices.OldPrice = inventoryPrices.OldPrice;
}
}
}
}
db.Inventories.Attach(_inventory);
db.Entry(_inventory).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
db.Dispose();
}
I've also tried working my code according to this SOQ Entity Framework update/insert multiple entities
and it gave me and error. Here are the details:
Code:
public void UpdatePricesFromInventoryListBulk(IList<Domain.Tables.Inventory> invList)
{
var accounts = new List<Domain.Tables.Inventory>();
var db = new UniStockContext();
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var inventory in invList)
{
accounts.Add(inventory);
if (accounts.Count % 1000 == 0)
{
db.Set<Domain.Tables.Inventory>().AddRange(accounts);
accounts = new List<Domain.Tables.Inventory>();
db.ChangeTracker.DetectChanges();
db.SaveChanges();
db.Dispose();
db = new UniStockContext();
}
}
db.Set<Domain.Tables.Inventory>().AddRange(accounts);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
db.Dispose();
}
Error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
I would suggest changing the following:
Domain.Tables.Inventory _inventory = db.Inventories
.Where(x => x.InventoryID == inventory.InventoryID)
.FirstOrDefault();
To
Domain.Tables.Inventory _inventory = db.Inventories
.Single(x => x.InventoryID == inventory.InventoryID);
I'd still add the db.Configuration.AutoDetectChangesEnabled = false; after getting the context, and also use AsNoTracking:
Turn off EF change tracking for any instance of the context
that is because you are hit the database context at every loop to increase the performance you should get all the Inventories by one hit ,this is your problem try the below code and you will notice the performance :
public void UpdatePricesFromInventoryList(IList<Domain.Tables.Inventory> invList)
{
var db = new UniStockContext();
invIdsArray = invList.select(x => x.InventoryID).ToArray();
IList<Domain.Tables.Inventory> invListFromDbByOneHit = db.Inventories.Where(x => invIdsArray.Contains(x.InventoryID)).Tolist();
foreach (var inventory in invListFromDbByOneHit)
{
//Domain.Tables.Inventory _inventory = db.Inventories
//.Where(x => x.InventoryID == inventory.InventoryID)
//.FirstOrDefault();
if (inventory.Cost.HasValue)
_inventory.Cost = inventory.Cost.Value;
else
_inventory.Cost = 0;
foreach (var inventoryPrices in inventory.AccInventoryPrices)
{
foreach (var _inventoryPrices in _inventory.AccInventoryPrices)
{
if (_inventoryPrices.AccInventoryPriceID == inventoryPrices.AccInventoryPriceID)
{
_inventoryPrices.ApplyDiscount = inventoryPrices.ApplyDiscount;
_inventoryPrices.ApplyMarkup = inventoryPrices.ApplyMarkup;
if (inventoryPrices.Price.HasValue)
_inventoryPrices.Price = inventoryPrices.Price.Value;
else
_inventoryPrices.Price = _inventory.Cost;
if (inventoryPrices.OldPrice.HasValue)
{
_inventoryPrices.OldPrice = inventoryPrices.OldPrice;
}
}
}
}
db.Inventories.Attach(_inventory);
db.Entry(_inventory).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
db.Dispose();
}
I have method in controller
It receive data from post request and write to table
Here is code
[ResponseType(typeof(TimeTable))]
public IHttpActionResult PostTimeTable(TimeTable timeTable)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (ModelState.IsValid)
{
DateTime dt = DateTime.Today;
TimeTable c = (from x in db.TimeTables
where x.Company == timeTable.Company && x.INN == timeTable.INN
select x).First();
c.StartPause = timeTable.StartPause;
c.StartDay = timeTable.StartDay;
c.EndPause = timeTable.EndPause;
c.EndDay = timeTable.EndDay;
db.SaveChanges();
}
db.TimeTables.Add(timeTable);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = timeTable.Id }, timeTable);
}
But it works well when record with INN and Company already in db.
But if it not in database I need to create new entry.
How I need to modify this method?
You can use a flag (exisingCompanyFlag) for edit mode or add new mode like this
bool existingCompanyFlag = true;
TimeTable c = (from x in db.TimeTables
where x.Company == timeTable.Company && x.INN == timeTable.INN
select x).FirstOrDefult();
if (c == null)
{
existingCompanyFlag = false;
c = new TimeTable();
}
c.StartPause = timeTable.StartPause;
c.StartDay = timeTable.StartDay;
c.EndPause = timeTable.EndPause;
c.EndDay = timeTable.EndDay;
if (!existingCompanyFlag)
db.TimeTables.Add(c);
You need a separate branch in your code for the insert case.
if (ModelState.IsValid) {
if (addingNewRow) {
TimeTable tt = new TimeTable {
// Populate properties (except identity columns)
};
db.TimeTables.Add(tt);
} else {
// update
}
db.SaveChanges();
}
To link to other entities use one of:
Assign instances:
x.Company = theCompany;
or, assign the instance id
x.CompanyId = companyId;
(#1 is easier if you already have the other entity loaded or are creating it – EF will sort out the ids – while #2 saves loading the whole other entity.)
I have an Asp.Net MVC 5 using Entity Framework 6. I am using Unity.MVC5 Version 1.2.3.0
The issue I am having is that I would get the following error on certain scenarios when saving to the database
Additional information: The operation failed: The relationship could
not be changed because one or more of the foreign-key properties is
non-nullable. When a change is made to a relationship, the related
foreign-key property is set to a null value. If the foreign-key does
not support null values, a new relationship must be defined, the
foreign-key property must be assigned another non-null value, or the
unrelated object must be deleted.
After troubleshooting the issue I believe it has to do with how I have Unity.MVC5 configured. Here is my Unity.Config.cs
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterTypes(AllClasses.FromLoadedAssemblies(), WithMappings.FromMatchingInterface, WithName.Default);
container.RegisterType<IUnitOfWork, UnitOfWork>(new InjectionConstructor(new MasterContext()));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
}
So my controller would have something like this
private IService _Service;
MyController(IService service)
{
_Service = service;
}
However it looks like the data is not refreshing, Although when I use a SQL Profiler , it shows as it is making a call but the data is not refreshed as I do a breakpoint it still has old data. If I do away with the Unity.MVC injecting the classes, then the data gets refreshed and savechanges works fine.
I am overwriting the EF Context SaveChanges , here is the code
public override int SaveChanges()
{
var autoDetectChanges = Configuration.AutoDetectChangesEnabled;
try
{
Configuration.AutoDetectChangesEnabled = false;
ChangeTracker.DetectChanges();
var errors = GetValidationErrors().ToList();
if (errors.Any())
{
throw new DbEntityValidationException("Validation errors found during save.", errors);
}
//For modified column
var changedInfo = ChangeTracker.Entries().Where(t => t.State == EntityState.Modified)
.Select(t => new
{
Original = t.OriginalValues.PropertyNames.ToDictionary(pn => pn, pn => t.OriginalValues[pn]),
Current = t.CurrentValues.PropertyNames.ToDictionary(pn => pn, pn => t.CurrentValues[pn]),
objectContext = ((IObjectContextAdapter)this).ObjectContext,
ent = t,
});
foreach (var item in changedInfo)
{
if (GetTableInformation.GetTableName(item.objectContext, item.ent) != "HistoryLogs")
{
var result = GetDifference.GetChangedValues(item.Original, item.Current, item.objectContext, item.ent);
HistoryLog history = new HistoryLog();
history.Description = result[0];
history.TableFields = result[1];
history.UserId = userId;
history.TableAction = "Modified";
history.PrimaryKeyValue = Convert.ToInt32(result[2]);
history.TableName = result[3];
if (history.TableName == "MainRates")
{
MainRate rate = MainRates.SingleOrDefault(r => r.RateId == history.PrimaryKeyValue);
history.InstitutionId = rate.InstitutionId;
}
else if (history.TableName == "ProgramRates")
{
ProgramRate rate = ProgramRates.SingleOrDefault(r => r.RateId == history.PrimaryKeyValue);
history.InstitutionId = rate.InstitutionId;
}
else
{
int institutiondId;
if (int.TryParse(result[4], out institutiondId))
{
history.InstitutionId = institutiondId;
}
else
{
history.InstitutionId = null;
}
}
//InstitutionName and OPEID are being updated by trigger(executer after each insert operations)
//Check if there is any modified column or not
if (!string.IsNullOrEmpty(history.TableFields))
HistoryLogs.Add(history);
}
}
//For Deleted columns
var deletedInfo = ChangeTracker.Entries().Where(t => t.State == EntityState.Deleted)
.Select(t => new
{
Original = t.OriginalValues.PropertyNames.ToDictionary(pn => pn, pn => t.OriginalValues[pn]),
objectContext = ((IObjectContextAdapter)this).ObjectContext,
ent = t,
});
foreach (var item in deletedInfo)
{
if (GetTableInformation.GetTableName(item.objectContext, item.ent) != "HistoryLogs")
{
var result = GetDifference.GetDeletedValues(item.Original, item.objectContext, item.ent);
HistoryLog history = new HistoryLog();
history.Description = result[0];
history.TableFields = result[1];
history.UserId = userId;
history.TableAction = "Deleted";
history.PrimaryKeyValue = Convert.ToInt32(result[2]);
history.TableName = result[3];
if (history.TableName == "MainRates")
{
int locationRateId = (int)item.Original["LocationRateId"];
history.InstitutionId = LocationRates.SingleOrDefault(l => l.Id == locationRateId).InstitutionId;
}
else if (history.TableName == "ProgramRates")
{
ProgramRate rate = ProgramRates.SingleOrDefault(r => r.RateId == history.PrimaryKeyValue);
history.InstitutionId = rate.InstitutionId;
}
else
{
history.InstitutionId = result[4] == null ? null : (int?)int.Parse(result[4]);
}
//InstitutionName and OPEID are being updated by trigger(executer after each insert operations)
history.InstitutionName = "";
history.OpeidNumber = "";
//Check if there is any modified column or not
if (!string.IsNullOrEmpty(history.TableFields))
HistoryLogs.Add(history);
}
}
// For data that is added
string[] applicableTables = new string[] { "EligiblePrograms", "Fees", "LocationRates", "MainRates", "ProgramRates" };
var addedInfo = ChangeTracker.Entries().Where(t => t.State == EntityState.Added)
.Select(t => new
{
Current = t.CurrentValues.PropertyNames.ToDictionary(pn => pn, pn => t.CurrentValues[pn]),
ObjectContext = ((IObjectContextAdapter)this).ObjectContext,
Entity = t,
}).ToList();
//Placing this here adds the primary keys to the new values before saving their history.
Configuration.ValidateOnSaveEnabled = false;
int rVal = base.SaveChanges();
foreach (var item in addedInfo)
{
string tableName = GetTableInformation.GetTableName(item.ObjectContext, item.Entity);
if (applicableTables.Contains(tableName))
{
var result = GetDifference.GetDeletedValues(item.Current, item.ObjectContext, item.Entity);
HistoryLog history = new HistoryLog();
history.Description = result[0];
history.TableFields = result[1];
history.UserId = userId;
history.TableAction = "Added";
history.PrimaryKeyValue = Convert.ToInt32(result[2]);
history.TableName = result[3];
if (history.TableName == "MainRates")
{
history.InstitutionId = ((MainRate)item.Entity.Entity).InstitutionId;
}
else if (history.TableName == "ProgramRates")
{
history.InstitutionId = ((ProgramRate)item.Entity.Entity).InstitutionId;
}
else
{
history.InstitutionId = result[4] == null ? null : (int?)int.Parse(result[4]);
}
history.InstitutionName = "";
history.OpeidNumber = "";
//Check if there is any modified column or not
if (!string.IsNullOrEmpty(history.TableFields))
HistoryLogs.Add(history);
}
}
rVal += base.SaveChanges();
return rVal;
}
finally
{
Configuration.AutoDetectChangesEnabled = autoDetectChanges;
}
}
Then my Service class will do something like this:
Header header = _uow.MyRepository.GetByHeaderId(model.Id, model.HeaderId);
header.WebAddresses = string.Join(",", model.WebAddresses.ToArray());
header.Date = DateTime.Parse(model.Date);
header.IsField1 = model.Field1;
header.Field2 = model.Field2;
header.Field3 = model.Field3;
_uow.SaveChanges();
I try to get the value of entity that stored in DbSet before it was changed by code and before it was saved. However, when I try to get it with LINQ Single statement I get the changed value. I'm using EF7.
Here's the code:
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
ent.FirstName = "New name";
Entity entityBeforeChange = dbSet.Single(x => x.Id == id); //here I want to get entity with old values, if that's important I just need to read it without modifying this instance
Context.SaveChanges();
Hope I was clear enough and can get some help
You can grab the original values of any entity from the ChangeTracker.
var original = Context.ChangeTracker.Entries<Entity>().Single(x => x.Id == id);
var firstName = original.Property<string>("FirstName").OriginalValue;
Here is a code I use from my audit library.
EF7
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
context.ChangeTracker.DetectChanges();
// Find your entry or get all changed entries
var changes = context.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified);
foreach (var objectStateEntry in changes)
{
AuditEntityModified(audit, objectStateEntry, auditState);
}
}
public static void AuditEntityModified(Audit audit, AuditEntry entry, EntityEntry objectStateEntry)
{
foreach (var propertyEntry in objectStateEntry.Metadata.GetProperties())
{
var property = objectStateEntry.Property(propertyEntry.Name);
if (entry.Parent.CurrentOrDefaultConfiguration.IsAudited(entry.ObjectStateEntry, propertyEntry.Name))
{
entry.Properties.Add(new AuditEntryProperty(entry, propertyEntry.Name, property.OriginalValue, property.CurrentValue));
}
}
}
EF6
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
var entry = ((IObjectContextAdapter)ctx).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
var currentValues = entry.CurrentValues;
var originalValues = entry.OriginalValues;
AuditEntityModified(originalValues, currentValues);
}
public static void AuditEntityModified(DbDataRecord orginalRecord, DbUpdatableDataRecord currentRecord, string prefix = "")
{
for (var i = 0; i < orginalRecord.FieldCount; i++)
{
var name = orginalRecord.GetName(i);
var originalValue = orginalRecord.GetValue(i);
var currentValue = currentRecord.GetValue(i);
var valueRecord = originalValue as DbDataRecord;
if (valueRecord != null)
{
// Complex Type
AuditEntityModified(valueRecord, currentValue as DbUpdatableDataRecord, string.Concat(prefix, name, "."));
}
else
{
if (!Equals(currentValue, originalValue))
{
// Add modified values
}
}
}
}
Edit:
The complete source code can be found in my GitHub repository: https://github.com/zzzprojects/EntityFramework-Plus
Library Website: http://entityframework-plus.net/
You can Detach an entity from the context. Keep in mind that you'll have to pull it from the context before you update the other, attached entity.
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
Entity entityBeforeChange = dbSet.Single(x => x.Id == id);
Context.Entry(entityBeforeChange).State = EntityState.Detached; // severs the connection to the Context
ent.FirstName = "New name";
Context.SaveChanges();
You could use a new DbContext since the loaded entity is cached in the one you already have.
Entity oldUnchanged;
using (var ctx = new YourDbContext())
{
oldUnchanged = ctx.Set<Entity>().Single(x => x.Id == id);
}