EF Code-First doesn't update Sets immediately - c#

I'm developing an ASP.NET MVC 3 website with Entity Framework Code-First. What's happening is that in the Index action, I get some TransactionJournal objects from the database, call a method called CreateTransactions(), that, basically, create some transactions and add them to the ICollection in the TransactionJournal. Then, I save the context, and then query for all Transactions, and then they are not there! But if I just refresh the page, I will get the transactions generated at the first time, that didn't appear, but not the ones from this time. There's a lot of code, so, I'll try to mock the parts that are important here so that you can see.
A piece of the TransactionJournal:
public class TransactionJournal {
public long TransactionJournalID { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
// ...
}
A piece of the Index action:
IDbSet<TransactionJournal> journal_dbset = Context.Set<TransactionJournal>();
ICollection<TransactionJournal> journals = journal_dbset.Where( ... ).ToList();
foreach(TransactionJournal j in journals)
j.CreateTransactions(); // Create some transactions and add it to the Transactions collection
Context.Commit(); // This actually calls the SaveChanges() method, only
IDbSet<Transaction> trans_dbset = Context.Set<Transaction>();
ICollection<Transaction> trans = trans_dbset.Where( ... ).ToList(); // The transactions just created are NOT here! But those created in previews Index action calls are!
It's like if the SaveChanges() don't immediately updates the Sets in the Context object. What should I do to get these just created transactions?

In CreateTransactions did you make a call to flag the object in the context to be updated?
context.Entry(j).State = EntityState.Modified;

Related

Why won't EF Core update this item on my database?

I'm having trouble updating an item on my database. I've tried several different ways, but nothing seems to work. Here is my latest attempt:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
When SaveChanges executes, I get the following error message: "The instance of entity type ItemStatus cannot be tracked because another instance with the key value ['ItemStatusId: 4'] is already being tracked.
Here are the relevant properties of my Item model:
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemId { get; set; }
public ItemStatus InitialStatus { get; set; }
public ItemStatus FinalStatus {get; set; }
}
And here are the relevant properties from the ItemStatus class:
public class ItemStatus
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemStatusId { get; set; }
public string ItemStatusName{ get; set; }
]
Can anyone tell me what I'm doing wrong? I have checked to make sure I don't have an undisposed context somewhere. Also, when I look at the change tracker, I can see that it is tracking an entry from each row on the ItemStatus table. This doesn't seem right. Shouldn't it only track the ItemStatus that has been assigned to localItem rather than all the related entities?
I am calling the method from here:
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
The various properties of ItemToDisplay are bound to dropdown lists in the UI. I have verified that these properties are being assigned correctly before being sent to the InsertOrUpdateFindingAsync method.
I have also tried a this for the InsertOrUpdate method:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
Item itemFromDb = context.Items.Where(i => i.ItemId == localItem.ItemId).FirstOrDefault();
itemsFromDb = localItem;
context.Items.Update(localItem);
context.SaveChanges();
}
}
You apparently save a localItem that refers to two ItemStatus objects both having ItemStatusId = 4. The cause of the error is that these ItemStatus objects are two instances instead of one. The Update command tries to attach both of these instances to the context.
There are two ways to fix this:
Make sure that if both statuses in Item are identical they both refer to the same ItemStatus object. Depending on where localItem comes from, this may require JSON serializer settings or a modification in the code that constructs the localItem object.
(Preferred) Add the primitive foreign properties InitialStatusId and FinalStatusId to Item and only set these properties, not the references. Then the update is a simple update of scalar properties.
Because
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
this variable is itemtodisplay, you have to read from once and you keep this data in this variable, so your context to database, tracked this record when you read.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
Then here when you sent this variable to there, there is already tracked variable, you tried to track it one more time.
There are 2 solutions:
Read this record with .AsNoTracking so it will not be tracked
When your update method you need to read one more time, like searching with id and keep it in another variable, and assign the parameters which you want to update to sent variable to new read one. then save it so it will not tracked, because there is a new read, actually you read this with tracking parameter because you don't need to add .AsNoTracking here, but it will create a problem because you are trying to change something on same tracked context.
I will add basic code based on my 2. desc.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
var data = context.Items.Where(x => x.Id == localItem.Id).FirstOrDefault();
data.Name = localItem.Name;
context.Items.Update(data);
context.SaveChanges();
}
}
I hope I described it well.
But note: using update method is not the best, because in that example I only want to update name property but it will create query that updates all columns. You should do it with out update, else there is 2 option here, if write update, it says hey EF mark all need to update, so it creates a query that to update all columsn. But this data is already tracked, if you change name like example EF Core knows it so you should only write there, SaveChanges(); or second method is if you want to update only one record, but your record is read with .AsNoTracking, you should access the context, get entries and change the field as modified
public void ContextAttach(TEntity entity)
{
_dataContext.Attach(entity);
_dataContext.Entry(entity).Property(Name).IsModified = true;
}
https://blog.oneunicorn.com/2020/01/17/dontcallupdate/

NHibernate by code - Save entity with unsaved(transient) child entity

I have the following entity:
public class Item
{
public virtual long ID { get; set; }
public virtual Version Version { get; set;}
More properties...
}
In the entity mapping I have:
ManyToOne(p => p.Version, m =>
{
m.Column("VERSION_ID");
}
The entity Version is also mapped by code and it's ID is an auto generated sequence.
When I save an Item, I create a new Version, assign it to the Version property and save it. I want to save the Version entity only after the Item is successfully saved. Now it throws a TransientObjectExceptionwhen I do this. Is it possible to solve this?
You cannot save an entity that references a transient object through a mapped property (Item->Version) unless when mapping the property you specify Cascade.Persist or Cascade.All.
Another thing is that since you should be running that code in a transaction the order of inserts should not matter. In case an exception is thrown (or anything else bad happens) after you save Version but before you save the Item, the transaction should be rolled back and nobody is going to see the new version.
The snippet below shows how you can begin/commit a transaction with nHibernate. Notice that the transaction will be rolled back if it does not get commited before it is disposed.
using(var session = sessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
// Do your thing here...
transaction.Commit();
}

Entity Framework - Non Key Relationships

Problem
I have a situation whereby I need to use Entity Framework 6, Code First, with a legacy database structure which cannot be changed. The database has a very generic table which stores text based data alongside some non key data which can be used to relate the record back to another table.
To illustrate:
Assume the Notes table has a model as follows:
[Table("Notes")]
public class Notes
{
[Key]
public int RecordId { get; set; }
[Required]
public string RelatedTableName { get; set; }
[Required]
public int RelatedTableRecordId { get; set; }
[Required]
public string NotesText { get; set; }
}
I then have another model which could look like so:
[Table("Drivers")]
public class Drivers
{
[Key]
public int RecordId { get; set; }
[Required]
public string DriverName { get; set; }
public ICollection<Notes> DriverNotes { get; private set; }
}
There is no foreign key which links the tables. The Drivers table is linked to the Notes table by way of the RelatedTableName and RelatedTableRecordId fields.
I do not have a problem reading data from the database and hydrating the models using entity framework.
The problem I have is that I want to be able to save a new Driver and its newly created Notes in one transaction and have the RelatedTableRecordId field set to the primary key of the Driver.
If a foreign key existed entity framework would know to back fill the property but in this case it doesn't know about the relationship.
Key Points
Database Structure must not change.
Must use Entity Framework 6 Code First
Must be able to use an Execution Strategy.
Require a relationship between non key fields.
Need to be able to persist all data in a single transaction.
What I've Tried
I had a similar issue with Audit type data and solved it by doing something similar to the following (note that this is very pseudo here):
public override int SaveChanges()
{
int changes = 0;
//Disable the current execution strategy as the default ones do not support user instantiated transactions.
this.ContextConfiguration.SuspendExecutionStrategy();
try
{
//Wrap a whole transaction inside an execution strategy so that auditing can be combined with regular saving of changes.
this.ExecutionStrategy.Execute(
() =>
{
using (var transaction = this.Database.BeginTransaction())
{
//Reset the change count so that it doesn't increase each time the transaction fails.
changes = 0;
//Remove any audit records created by previous failed transactions.
this.AuditTableChanges.Local.Clear();
//Evaluate the change tracker to identify entities which will potentially require an audit trail.
var insertedEntities = this.ChangeTracker.Entries().Where(entryEntity => entryEntity.State == EntityState.Added).ToList();
//Save all changes to get identities.
changes = base.SaveChanges();
//Create the audit trail for inserted entities. This step must occur after the initial call to SaveChanges() so that the identities are set.
foreach (DbEntityEntry entryEntity in insertedEntities)
{
//For each inserted record, get the audit record entries and add them
foreach (AuditTableChange auditTableChange in GetAuditRecords(entryEntity, AuditTableChangeType.Insert).Result)
this.AuditTableChanges.Add(auditTableChange);
}
//Save the audit trail for inserted entities.
changes += base.SaveChanges();
//Commit all changes to the database
transaction.Commit();
}
});
}
finally
{
//Re-enable the execution strategy so that other calls can benefit from the retry policy.
this.ContextConfiguration.UnSuspendExecutionStrategy();
}
return changes;
}
This worked fine for the Audit data as the implementation was hidden away in the framework. I do not want my development team to have to do all of the above each time they persist records.
In its simplistic form this is as much as I'd want people to be doing:
public void CreateDriver()
{
using (MyContext context = new MyContext())
{
Drivers driver = new Drivers();
driver.DriverName = "Joe Bloggs";
Notes driverNote = new Notes();
driverNote.RelatedTableName = "Drivers";
driverNote.NotesText = "Some very long text";
driver.DriverNotes.Add(driverNote);
context.Drivers.Add(driver);
context.SaveChanges();
}
}
In a way I want a foreign key which exists in code but not in the database so that entity framework knows to fill in the RelatedTableRecordId field. I've read some articles on hacking the EDMX but this project is purely Code First only.
There are older questions on stack overflow which are similar but relate to older versions of entity framework and don't help much or have as much detail as the above.
I'm hoping that someone may have experienced a similar problem and has an answer which may involve perhaps some custom mapping/metadata or some overrides to entity framework logic.
Any help would be appreciated.
Thanks,
Greg

Unable to update modified entities

I've got an aggregate for a specific type of entity which is stored in a collection inside the aggregate. Now I'd like to add a new entry of that type and update the aggregate afterwards, however Entity Framework never updates anything!
Model
public class MyAggregate {
protected ICollection<MyEntity> AggregateStorage { get; set; }
public void AddEntity(MyEntity entity) {
// some validation
AggregateStorage.Add(entity);
}
}
API Controller
[UnitOfWork, HttpPost]
public void UpdateMyEntity(int aggregateId, MyEntityDto dto) {
var aggregate = _aggregateRepository.Find(aggregateId);
aggregate.AddEntity(...// some mapping of the dto).
_aggregateRepository.Update(aggregate);
}
EF Update
EntitySet.Attach(aggregate);
Context.Entry(aggregate).State = EntityState.Modified;
(Please note that there's an unit of work interceptor on the API action who fires DbContext.SaveChanges() after successful execution of the method.)
Funny thing is, the update never get's executed by EF. I've added a log interceptor to the DbContext to see what's going on sql-wise and while everything else works fine, an update statement never occurs.
According to this answer in detached scenario (either aggregate is not loaded by EF or it is loaded by different context instance) you must attach the aggregate to context instance and tell it exactly what did you changed, set state for every entity and independent association in object graph.
You must either use eager loading and load all data together at the beginning and
instead of changing the state of aggregate, change the state of entities:
foreach(var entity in aggregate.AggregateStorage)
{
if(entity.Id == 0)
Context.Entry(entity).State = EntityState.Added;
}

What's the fundamental concept of EF that I'm missing?

I'm sure I am misunderstanding something fundamental about how EF5 works.
In a [previous question] I asked about how to pass values between actions in an ASP.NET MVC application and it was suggested I could use TempData as a mechanism to pass around data (in my case I've gone for the POCOs that represent my data model in EF).
My controllers in MVC are not aware of any persistence mechanism within EF. They make use of a service layer which I've called "Managers" to perform common tasks on my POCOs and read/persist them to the underlying datastore.
I'm writing a workflow to allow an "employee" of my site to cancel a "LeaveRequest". In terms of controllers and actions, there's an HttpGet action "CancelLeaveRequest" which takes the ID of the LeaveRequest in question, retrieves the LeaveRequest through the service layer, and displays some details, a warning and a confirm button. Before the controller returns the relevant View, it commits the LeaveRequest entity into TempData ready to be picked up in the next step...
The confirm button causes an HttpPost to "LeaveRequest" which then uses the LeaveRequest from TempData and a call down to the service layer to make changes to the LeaveRequest and save them back to the database with EF.
Each instance of a manager class in my code has it's own EF DBContext. The controllers in MVC instantiate a manager and dispose of it within the page lifecycle. Thus, the LeaveRequest is retrieved using one instance of a DBContext, and changes are made and submitted via another instance.
My understanding is that the entity becomes "detached" when the first DBContext falls out of scope. So, when I try to commit changes against the second DBContext, I have to attach the entity to the context using DBContext.LeaveRequests.Attach()? There is an added complication that I need to use an "Employee" entity to note which employee cancelled the leave request.
My code in the service layer for cancelling the leave request reads as follows.
public void CancelLeaveRequest(int employeeId, LeaveRequest request)
{
_DBContext.LeaveRequests.Attach(request);
request.State = LeaveRequestApprovalState.Cancelled;
request.ResponseDate = DateTime.Now;
using (var em = new EmployeesManager())
{
var employee = em.GetEmployeeById(employeeId);
request.Responder = employee;
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
}
_CommitDatabaseChanges();
}
You can see that I retrieve an Employee entity from the EmployeesManager and assign this employee as the responder to the leave request.
In my test case, the "responder" to the Leave Request is the same employee as the "requestor", another property on Leave Request. The relationships are many-to-one between leave requests and a requesting employee, and many-to-one between leave requests and a responding employee.
When my code runs in it's present state, I get the following error:
AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
I suspect this is because EF thinks it knows about the employee in question already. The line that fails is:
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
However, if I remove this line and don't try to be clever by telling EF not to change my employee object, the leave request gets cancelled as expected but some very strange things happen to my Employees.
Firstly, the employee who made/responded to the request is duplicated. Then, any navigation properties (like "Manager", a many-to-one relationship between an Employee and other Employees) seem to get duplicated too. I can understand that the duplication of the Manager property on Employee is because I am loading the Manager object graph in as part of GetEmployeeById and I think I understand that the original Employee is being duplicated because, as far as the LeaveRequest DBContext is concerned, it has just appeared out of nowhere (I retrieved the Employee through a different DBContext). However, assuming those two points are correct, I'm at a loss as to how I can a) prevent the Employee and it's associated object graph being duplicate in the database and b) how I can ensure the modified LeaveRequest is persisted correctly (which it seems to stop doing with various combinations of attaching, changing state to modified etc... on the employee and leave request).
Please can someone highlight the error of my ways?
My LeaveRequest entity:
public class LeaveRequest
{
public LeaveRequest()
{
HalfDays = new List<LeaveRequestHalfDay>();
}
public int CalculatedHalfDaysConsumed { get; set; }
public Employee Employee { get; set; }
public virtual ICollection<LeaveRequestHalfDay> HalfDays { get; set; }
public int LeaveRequestId { get; set; }
public DateTime RequestDate { get; set; }
public int ResponderId { get; set; }
public virtual Employee Responder { get; set; }
public DateTime? ResponseDate { get; set; }
public LeaveRequestApprovalState State { get; set; }
public LeaveRequestType Type { get; set; }
public ICollection<LeaveRequest> ChildRequests { get; set; }
public LeaveRequest ParentRequest { get; set; }
}
The "Employee" field (of type Employee...) is the person who submitted the request. The "Responder" is potentially a different, but could be the same, employee.
You should change your navigation properties to this:
public int ResponderId {get;set;}
public virtual Employee Responder { get; set; }
This scalar property will be auto-mapped to the navigation property by EF. Next you can simply do the following (and you don't need the Unchanged state):
var employee = em.GetEmployeeById(employeeId);
request.ResponderId = employee.Id;
See also this article about relationships in EF.

Categories