What is the best way to manage Entity Framework contexts? - c#

I have something like this:
static Employee getEmployee(string ssn){
MyEntity context = null;
Employee employee;
try{
context = new MyEntity();
employee = context.Employee.Where(X => X.ssn.Equals(ssn));
}
catch (Exception exc){
LogLibrary.WriteLog(exc.Message);
}
finally{
if (context != null){
context.Database.Connection.Close();
}
}
return employee;
}
static Employee addEmployee(Employee emp){
MyEntity context = null;
Employee employee;
try{
context = new MyEntity();
context.Employee.Add(e);
}
catch (Exception exc){
LogLibrary.WriteLog(exc.Message);
}
finally{
if (context != null){
context.Database.Connection.Close();
}
}
}
And this is the code I want to implement:
Employee myNewEmployee = DBClass.getEmployee("12345");
myNewEmployee.name = "John";
DBClass.AddEmployee(myNewEmployee);
But I obviously receive the following exception: An entity object cannot be referenced by multiple instances of IEntityChangeTracker
So I've been recommended to do the following:
Employee myNewEmployee = DBClass.getEmployee("12345");
Employee myNewEmployee2 = new Employee();
// manually copy all the fields from myNewEmployee to myNewEmployee2
myNewEmployee2.name = "John";
DBClass.AddEmployee(myNewEmployee2);
But I think it might be unefficient due to the fact that I am wasting clock cycles to have an identical copy of the same object. Should we use a single static context for the whole application? (It's an ASPx project with master page). Where can I read more about "how to use contexts"? Thank you so much.

Since the code is working with completely separate DbContext instances for each call, it is technically possible to update the instance to copy and save it as a new employee after ensuring all unique values and keys are updated. However, your existing code is leaving the entity tracking references orphaned by disposing the DbContext. Code like that getEmployees should either be loading entities in a detached state with AsNoTracking() or detaching the instance from the DbContext before the context is disposed. The method can also be simplified to remove the finally block to handle disposal by using a using block:
static Employee getEmployee(string ssn)
{
using(var context = new MyEntity())
{
try
{
return context.Employee.AsNoTracking().Single(X => X.ssn.Equals(ssn));
}
catch (Exception exc)
{
LogLibrary.WriteLog(exc.Message);
}
}
return null;
}
Alternatively you can detach the entity before returning it:
try
{
var employee = context.Employee.Single(X => X.ssn.Equals(ssn));
context.Entry(employee).State = EntityState.Detached;
return employee;
}
using blocks take care of disposing instances within a scope. One missed finally block and you have an undisposed DbContext instance.
When that initial entity is not tracked by the now disposed DbContext, you can have code like:
var employee = getEmployee("12345");
employee.SSN = "54321";
employee.Name = "John";
using(var context = new MyEntity())
{
context.Employees.Add(employee);
context.SaveChanges();
}
Another alternative is to clone the entity.There are a few ways to do this including manually copying the values across or leveraging Automapper to copy the values across. This can be configured to ignore copying keys and unique values. At the very basic:
var config = new MapperConfiguration(cfg => cfg.CreateMap<Employee, Employee>());
var mapper = config.CreateMapper();
var sourceEmployee = getEmployee("12345");
var newEmployee = mapper.Map<Employee>(sourceEmployee);
newEmployee.SSN = "54321";
newEmployee.Name = "John";
using(var context = new MyEntity())
{
context.Employees.Add(newEmployee);
context.SaveChanges();
}
This code would need to ensure that the primary key value and any unique constraints are updated. If the employee table has an EmployeeId PK and that key is set up as an Identity then that should be covered automatically. Otherwise if the PK is something like the SSN you will need to ensure that is a new and unique value before saving. To do that you should first check the database to ensure the new SSN is unique:
using(var context = new MyEntity())
{
if (!context.Employees.Any(x => x.SSN == newEmployee.SSN))
{
context.Employees.Add(newEmployee);
context.SaveChanges();
}
else
{
// handle that the new SSN is already in the database.
}
}
Regarding just using a single static DbContext: No, that is not a good idea with EF. DbContexts by default track every instance they load unless explicitly told not to. This means the longer they are alive, the more instances they track, consuming memory and causing performance drops as EF will continually check across it's known tracked instances to see if it should return that rather than a new instance pulled from the database. It still runs the queries in most cases, so dealing with tracked instances does not save performance like you might think comparing the behaviour to caching. Normally you would want multiple calls though to be associated with a single DbContext instance so having the DbContext too finely scoped makes it less flexible. For example if you wanted to update a Position in a company and associate it with an employee, having a getEmployee() method that scoped it's own DBContext can actually have unintended consequences such as this example:
using (var context = new MyEntity())
{
var position = context.Positions.Single(x => x.PositionId == positionId);
var employee = getEmployee(ssn);
position.Employee = employee;
context.SaveChanges();
}
What this can end up resulting with is an error about a duplicate constraint on attempting to insert a new Employee, or it will insert a completely new clone of the Employee record. (If the Employee is configured with an Identity for it's PK) The reason for this is that the Position is being managed by one instance of the DbContext while the getEmployee() was using a completely separate DbContext instance. The position doesn't know that "employee" is an existing record and treats it like a brand new one. The proper way to ensure these instances are associated together is to ensure they are both associated with the same DbContext instance:
using (var context = new MyEntity())
{
var position = context.Positions.Single(x => x.PositionId == positionId);
var employee = context.Employees.Single(x => x.SSN == ssn);
position.Employee = employee;
context.SaveChanges();
}
Or else ensuring that both this code and getEmployee are injected with the same DbContext instance rather than scoping it within the methods. (I.e. dependency injection) Working with detached instances like your code is structured is possible but it can get quite messy so be cautious.

Related

The instance of the entity type cannot be tracked because another instance with the keyvalue is being tracked

I am basically trying to implement CRUD using EntityFrameWork core and .Net core 3.1. I have an issue with my update operation where I am not able update the context with the modified value.
I am using postman to initiate the request.
As you can see in the code below, I am trying to check if that customer exist and if it does pass the modified object to the context.
Function code
[FunctionName("EditCustomer")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous,"post", Route = "update-customer")] HttpRequest req)
{
var customer = JsonConvert.DeserializeObject<CustomerViewModel>(new StreamReader(req.Body).ReadToEnd());
await _repo.UpdateCustomer(customer);
return new OkResult();
}
Repository method
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
_context.Customers.Update(_mapper.Map<Customers>(customerViewModel));
await _context.SaveChangesAsync();
}
}
}
Mapping
public class CustomerManagerProfile : Profile
{
public CustomerManagerProfile()
{
CreateMap<CustomerDetails, CustomerDetailsViewModel>().ReverseMap();
CreateMap<CustomerOrders, CustomerOrdersViewModel>().ReverseMap();
CreateMap<CustomerOrderDetails, OrderDetailsViewModel>().ReverseMap();
CreateMap<Customers, CustomerViewModel>().ReverseMap();
}
}
Solution
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Entry<Customers>(customer).State = EntityState.Detached;
_context.Entry<Customers>(customerModel).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
}
}
Entity Framework tracks your entities for you. For simplicity's sake, think of it like keeping a dictionary (for every table) where the dictionary key is equal to your entity's PK.
The issue is that you can't add two items of the same key in a dictionary, and the same logic applies to EF's change tracker.
Let's look at your repository:
var customer = _context
.Customers
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
The fetched customer is retrieved from the database and the change tracker puts it in his dictionary.
var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();
I split your code in two steps for the sake of my explanation.
It's important to realize that EF can only save changes to tracked objects. So when you call Update, EF executes the following check:
Is this the same (reference-equal) object as one I have I my change tracker?
If yes, then it's already in my change tracker.
If not, then add this object to my change tracker.
In your case, the mappedCustomer is a different object than customer, and therefore EF tries to add mappedCustomer to the change tracker. Since customer is already in there, and customer and mappedCustomer have the same PK value, this creates a conflict.
The exception you see is the outcome of that conflict.
Since you don't need to actually track your original customer object (since EF doesn't do anything with it after fetching it), the shortest solution is to tell EF to not track customer:
var customer = _context
.Customers
.AsNoTracking()
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
Since customer is now not put into the change tracker, mappedCustomer won't cause a conflict anymore.
However, you don't actually need to fetch this customer at all. You're only interested in knowing whether it exists. So instead of letting EF fetch the entire customer object, we can do this:
bool customerExists = _context
.Customers
.Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));
This also solves the issue since you never fetch the original customer, so it never gets tracked. It also saves you a bit of bandwidth in the process. It's admittedly negligible by itself, but if you repeat this improvement across your codebase, it may become more significent.
The most simple adjustment that you could make would be to avoid tracking your Customers on retrieval like this:
var customer = _context
.Customers
.AsNoTracking() // This method tells EF not to track results of the query.
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
It's not entirely clear from the code, but my guess is your mapper returns a new instance of Customer with the same ID, which confuses EF. If you would instead modify that same instance, your call to .Update() should work as well:
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
_context.Customers.Update(customer);
await _context.SaveChangesAsync();
As a matter of fact, if you track your Customer you don't even need to explicitly call .Update() method, the purpose of tracking is to be aware of what changes were made to the entities and should be saved to the database. Therefore this will also work:
// Customer is being tracked by default.
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
await _context.SaveChangesAsync();
EDIT:
The solution you yourself provide begins by tracking the results of your query (the Customer) instance, then stops tracking it (a.k.a. gets detached) before writing to database and instead starts tracking the instance that represents the updated Customer and also marks it as modified. Obviously that works as well, but is just a less efficient and elegant way of doing so.
As a matter of fact if you use this bizarre approach, I don't see the reason for fetching your Customer at all. Surely you could just:
if (!(await _context.Customers.AnyAsync(c => c.CustomerId == customerViewModel.CustomerId)))
{
throw new Exception("customer not found");
}
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update(customerModel);
await _context.SaveChangesAsync();
You use AutoMapper wrong way. It is not created to map from View model or DTO to Entity classes. It makes many problems and you are facing with only one of them now.
If you have more complex bussiness logic in you app (not just udpate all fields), it will be horrible to manage, test and debug what actually is happening in your code. You should write you own logic with some bussiness validation in case when you want to make some other update than CRUD.
If I were you I would create UpdateFields method in Customer class which would update them and finally call SaveChanges. It depends on whether you use anemic entity (anti)pattern or not. If you do not want your entity class to have any method you can create just method which manually map you VM do entity with some domain validation

Entity Framework Still Cache Although Use "Using"

I am trying to use Entity Framework for work with database, I use Extension Method and pass Entity Context Into Logic Codes, The Database is update successful, but when I call back, result, I still old Records, I guess that issue on Cache of Entities, But It not make clear, I could not find any thing wrong in my code. Please help:
Extension method:
public static bool UpdateTruck(this Truck Truck, Truck updateInfo, Entities entities)
{
var isSuccess = true;
try
{
// Find Enity Object
var ObjectModel = entities.Truck.Where(x => x.Code == Truck.Code && x.CodePlant == Truck.CodePlant).FirstOrDefault();
// Mapping Modified Properties
ObjectModel = Mapper.Map(updateInfo, ObjectModel);
// Create Database Entity Transaction
entities.Truck.AddOrUpdate(ObjectModel);
//Save Changes
entities.SaveChanges();
}
catch (Exception exception)
{
Debug.WriteLine("[Application Exception:] " + exception.Message);
isSuccess = false;
}
return isSuccess;
}
Here is where I call: if (truckInfo.FindTruck(entities).UpdateTruck(truckInfo,entities))
After that, i checked database, i value update success update, but when I call:
using (Entities entities = new Entities())
{
PageModel.Truck= Truck.FindTruck(entities);
....
It receive old record.
Entity Framework caches objects internally in a DbContext instance.
Look here
Your issue isn't caching. You are using the AddOrUpdate method, which is only designed for use when initialising a database with seed data.
Use:
entities.Truck.Attach(Truck); // if it already exists.
// since you are loading it again for some reason
var ObjectModel = entities.Truck.Where(x => x.Code == Truck.Code && x.CodePlant == Truck.CodePlant).FirstOrDefault();
// Mapping Modified Properties
ObjectModel = Mapper.Map(updateInfo, ObjectModel); // this looks dubious
// You've just loaded it from the context so it's tracked, so you could just save it
entities.SaveChanges();

The instance of entity type 'Item' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked

I am aware that such question has already been asked, but solution did not help me.
[Fact]
public async Task UpdateAsync()
{
string newTitle = "newTitle1";
int newBrandId = 3;
var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
item.BrandId = newBrandId;
item.Title = newTitle;
storeContext.Entry(item).State = EntityState.Detached;
await service.UpdateAsync(item); // exception inside
var updatedItem = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
Assert.Equal(newTitle, updatedItem.Title);
Assert.Equal(newBrandId, updatedItem.BrandId);
}
public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified; // exception when trying to change the state
await _dbContext.SaveChangesAsync();
}
Message: System.InvalidOperationException : The instance of entity type 'Item' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
interesting that exception is the same even if no item retreived from db, like so
//var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
var item = new Item()
{
Id = 1,
BrandId = newBrandId,
CategoryId = 1,
MeasurementUnitId = 1,
StoreId = 1,
Title = newTitle
};
Had the same problem with EF core 2.2. I never experianced this with other applications.
Ended up rewriting all my update functions somehow like this:
public bool Update(Entity entity)
{
try
{
var entry = _context.Entries.First(e=>e.Id == entity.Id);
_context.Entry(entry).CurrentValues.SetValues(entity);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
// handle correct exception
// log error
return false;
}
}
Alexandar's answer, which was to disable tracking completely, solved my issue, but I got worried since I didn't know what this would do to the rest of my application. So I went to the Microsoft docs and found this:
You should not disable change tracking if you want to manipulate entity instances and persist those changes to the database using SaveChanges().
This method sets the default behavior for all contexts created with these options, but you can override this behavior for a context instance using QueryTrackingBehavior or on individual queries using the AsNoTracking(IQueryable) and AsTracking(IQueryable) methods.
So the solution for me was to disable tracking only when needed. So I solved my issue by using this in the other part of my code that retrieved the same entry from the database:
var entry = await context
.SomeDbTable
.AsNoTracking() // this is what you're looking for
.Find(id);
Numerous issues I've been running into have one nasty root.
In a nutshell: I've learned the hard way why dbContext is scoped rather than singleton. Here is Store type, but the issue was the same.
Here is simplified test initialization code
public TestBase()
{
services = new ServiceCollection();
storeContext = StoreContextMock.ConfigureStoreContext(services, output);
serviceProvider = services.BuildServiceProvider();
}
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
storeContext .Stores.Add(new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" });
storeContext .Stores.Add(new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" });
storeContext .SaveChanges();
return storeContext ;
}
I reread error and finally noticed the main word
The instance of entity type 'Store' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
So there has to be some orphan tracked instance preventing me from working with store. I did not save any references to s1 or s2, so it must be storeContext storing references on inserted objects even after leaving scope of their declaration and initialization. That's why I was unable update variables normally and also why my 'queried' from db objects had all their navigation properties assigned (lazy loading has little to do with this). The following code resolved all my issues.
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
var s1 = new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" };
var s2 = new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" }
storeContext .Stores.Add(s1);
storeContext .Stores.Add(s2);
storeContext .Entry<Store>(s1).State = EntityState.Detached;
storeContext .Entry<Store>(s2).State = EntityState.Detached;
storeContext .SaveChanges();
return storeContext ;
}
That is one of many reasons why dbContext should be limited by a scope.
Thanks for the hint.
For me was this the solution:
public void Update(int id, T obj)
{
var entry = table.Find(id);
_context.Entry(entry).CurrentValues.SetValues(obj);
}
Based on the solution Bryan gave. I think I use newer version of EF/Automapping. This works for me.
I got some similar error when I wanted to update data, and I found out I could fix it by clearing the property context. Here is what a did. It's not the same problem but it's the same error, so I think it can be fixed the same way. Clearing the context seems to be a good solution because it's the reason of whats happening.
context.ChangeTracker.Clear();
context.Cliente.Update(cliente);
context.SaveChanges();
I had same problem while I was copying some records in database by Entity Framework and changing one column that was other's entity key.
Tracking mode change did not fix the issue.
The issue was fixed by properly setting primary key in EntityTypeConfiguration, to contain the changed value here described as x.EntityTwoKey.
builder.HasKey(x => new { x.EntityOneKey, x.EntityTwoKey });
In my case I hit this error when running SaveChanges twice inside of two IFs statements. I moved the SaveChanges outside of those two blocks of code. Just a side note in my service layer it is querying the data with AsNoTracking();
if (user.SendPaymentStatus)
{
user.SendPaymentStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetPaymentHTML(user.MasterNodeName, user.Payee, DbMasterNode.LastPaidUtc);
Framework.Email.SendEmail(email, "MasterNode Payment - " + user.MasterNodeName, msg);
}
if (user.SendNodeStatus)
{
user.SendNodeStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetStatusHTML(user.MasterNodeName, user.Payee, DbMasterNode.CurrentStatus, DbMasterNode.LastSeenUtc);
Framework.Email.SendEmail(email, "MasterNode Down - " + user.MasterNodeName, msg);
}
if (saveChanges)
{
user.SendPaymentStatus = false;
_userService.SaveChanges(user, false);
}
I was getting the same problem when was trying to update the value. then i found the proble i was using this.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")),ServiceLifetime.Singleton);
then i remove lifetime and it worked well for me.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")));
In my case above issue was resolved after I set primary key column Id as an Identity column.
We recently run into the same issue when adding multiple new items with identity column id set to 0. We are using OracleDataAccess client for EF core 3, we set the sequence number for the new entities when we do saveChanges(), but it errors out when we try to add() if there's already another item with id=0.
The fix we did is making sure the configuration for the identity column is correct:
1.) Set the key
builder.HasKey(t => t.Id);
2.) Set the database generate option correctly
[Column("ID"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int Id { get; set; }
or fluent equivalent:
builder.Property(t => t.Id)
.ValueGeneratedOnAdd();
We did not do second step correctly and was setting as DatabaseGeneratedOption.None, then EF core failed on add.

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!

Why doesn't entity framework concretize my entity's one to many relationship?

I am using a code-first approach with Entity Framework, and a repository pattern to get entities back from my database. In my data model, each OverallEvent has many EventInConcept children. I want my GetEvents method to return an IList of OverallEvents, and I want the children of the aforementioned relationship to be concretized such that they can be accessed outside my DbContext (which AssessmentSystemContext is). This is the code I currently have:
public IList<OverallEvent> GetEvents() {
using (var context = new AssessmentSystemContext()) {
return context.OverallEvents
.Select(evnt => new {
OverallEvent = evnt,
// evnt.EventsInConcept is a public virtual ICollection<EventInConcept>
ConcreteEventsInConcept = evnt.EventsInConcept
})
.AsEnumerable()
.Select(evntData => {
evntData.OverallEvent.EventsInConcept = evntData.ConcreteEventsInConcept.ToList();
// foreach (var eic in evntData.OverallEvent.EventsInConcept) {
// eic.Name = eic.Name;
// }
return evntData.OverallEvent;
})
.ToList();
}
}
It gives me back a list of OverallEvent entities, which is fine, but the trouble is that if I try to access the child relationship EventsInConcept, I get an error. For example:
EventRepository repoEvent = new EventRepository();
var gotEvents = repoEvent.GetEvents();
var firstEventInConcept = gotEvents[0].EventsInConcept.FirstOrDefault();
... gives me the error "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."
I understood from the answer to an earlier question that if I projected EventsInConcept into a wrapper object, then explicitly set it in a later .Select call (ie. evntData.OverallEvent.EventsInConcept = evntData.ConcreteEventsInConcept.ToList();), it would concretize this one:many relationship and I would be able to access EventsInConcept outside of the DbContext, but it isn't working here. Note that if I uncomment the foreach loop, it starts working, so to get it to work I have to explicitly set a property on every single entry of EventsInConcept. I don't really want to have to do this (I'm picking an arbitrary property, .Name, which feels wrong anyway). Is there a better way?
Disable lazy loading for this query. It is of no use in that situation and when you dispose the context after the entities have been retrieved:
public IList<OverallEvent> GetEvents() {
using (var context = new AssessmentSystemContext()) {
context.Configuration.LazyLoadingEnabled = false;
return ...
}
}
It might be possible that EF doesn't recognize that the collection has been loaded when you use a projection (instead of eager or explicit loading) and triggers lazy loading as soon as you access the collection.

Categories