EF 6 OriginalValues lost when using Attach method - entity-framework

I have the following Update generic method for my entities:
public void Update < T > (T entity) where T: class {
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == System.Data.Entity.EntityState.Detached) {
DbContext.Set < T > ().Attach(entity);
}
dbEntityEntry.State = System.Data.Entity.EntityState.Modified;
}
After SaveChanges() the data is successfully updated in the DB.
Now I nee to implement and Audit Log before SaveChanges() but I noticed that CurrentValues are equal to OriginalValues:
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName))){
//here I add a new Audit Log entity
}
Any clue on how to solve this? Or is there a better way to do it in Entity Framework 6?

If you are using a disconnected entity, you can set originals values without affect entity instance values, adapt this method at you needs
public static void LoadOriginalValues(this WorkflowsContext db, DbEntityEntry entity)
{
var props = entity.GetDatabaseValues();
foreach (var p in props.PropertyNames)
{
if (entity.Property(p).IsModified)
{
entity.Property(p).OriginalValue = props[p];
}
}
}

The original values are recovered from the entity itself. If the entity is being tracked by a context, this information is available.
In your case, you're using a disconected entity, so there is no change tracking, and the entity doesn't have the original values.
SO, in this case, if you need the original values there is no other option than getting them from the DB, and compare them, one by one.
If you want to get an entity that behaves as if it had been tracked by the context you can use a context to read the entity from the DB, and use something like ValueInjecter to automatically set the property values from the disconected entity into the tracked entity.

Related

Issues with Automatically setting created and modified date on each record in EF Core

Using ASP.NET Core 2.2 with EF Core, I have followed various guides in trying to implement the automatic creation of date/time values when creating either a new record or editing/updating an existing one.
The current result is when i initially create a new record, the CreatedDate & UpdatedDate column will be populated with the current date/time.
However first time I edit this same record, the UpdatedDate column is then given a new date/time value (as expected) BUT for some reason, EF Core is wiping out the value of the original CreatedDate which results in SQL assigning a default value.
Required result I need as follows:
Step 1: New row created, both CreatedDate & UpdatedDate column is given a date/time value (this already works)
Step 2: When editing and saving an existing row, I want EF Core to update the UpdatedDate column with the updated date/time only, BUT leave the other CreatedDate column unmodified with the original creation date.
I'm using EF Core code first, and do no want to go down the fluent API route.
One of the guides i was partially following is https://www.entityframeworktutorial.net/faq/set-created-and-modified-date-in-efcore.aspx but neither this or other solutions I've tried is giving the result I am after.
Baseclass:
public class BaseEntity
{
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
DbContext Class:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var entries = ChangeTracker.Entries().Where(E => E.State == EntityState.Added || E.State == EntityState.Modified).ToList();
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Modified)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
else if (entityEntry.State == EntityState.Added)
{
entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now;
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
UPDATE FOLLOWING ADVICE FROM STEVE IN COMMENTS BELOW
I spent a bit more time debugging today, turns out the methods I posted above are appear to be functioning as expected i.e. when editing an existing row and saving it, only the entityEntry.State == EntityState.Modified IF statement is being called. So what I'm finding is that after saving the entity, the CreatedDate column is being overwitten with a Null value, I can see this by watching the SQL explorer after a refresh. I believe the issue is along the lines of what Steve mentions below "If it is #null then this might also explain the behavior in that it is not being loaded with the entity for whatever reason."
But i'm a little lost in tracing where this CreatedDate value is being dropped somewhere through edit/save process.
Image below shows the result at the point just before the entity is saved following an update. In the debugger I'm not quite sure where to find the entry of the CreatedDate to see what value is held at this step, but it appears to be missing from the debugger list so wandering whether somehow it doesn't know about the existence of this field when saving.
Below is the method I have in my form 'Edit' Razor page model class:
public class EditModel : PageModel
{
private readonly MyProject.Data.ApplicationDbContext _context;
public EditModel(MyProject.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public RuleParameters RuleParameters { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
RuleParameters = await _context.RuleParameters
.Include(r => r.SystemMapping).FirstOrDefaultAsync(m => m.ID == id);
if (RuleParameters == null)
{
return NotFound();
}
ViewData["SystemMappingID"] = new SelectList(_context.SystemMapping, "ID", "MappingName");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(RuleParameters).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!RuleParametersExists(RuleParameters.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool RuleParametersExists(int id)
{
return _context.RuleParameters.Any(e => e.ID == id);
}
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class. But I also didn't think this was necessary? otherwise I'd struggle to see what value there is in the this process of using an inherited BaseEntity class i.e. not having to manually add the CreatedDate & UpdatedDate attribute to every model class where I want to use it...
It may be easier to just give your BaseEntity a constructor:
public BaseEntity()
{
UpdatedDate = DateTime.Now;
CreatedDate = CreatedDate ?? UpdatedDate;
}
Then you can have your DbContext override SaveChangesAsync like:
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken token = default)
{
foreach (var entity in ChangeTracker
.Entries()
.Where(x => x.Entity is BaseEntity && x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<BaseEntity>())
{
entity.UpdatedDate = DateTime.Now;
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, token);
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class.
That's true.Your post data does not contains the original CreatedDate,so when save to database, it is null and could not know what the exact value unless you assign it before saving.It is necessary.
You could just add below code in your razor form.
<input type="hidden" asp-for="CreatedDate" />
Update:
To overcome it in server-side,you could assign data manually:
public async Task<IActionResult> OnPostAsync()
{
RuleParameters originalData = await _context.RuleParameters.FirstOrDefaultAsync(m => m.ID == RuleParameters.ID);
RuleParameters.CreatedDate = originalData.CreatedDate;
_context.Attach(RuleParameters).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
I don't suspect EF is doing this, but rather your database, or you're inadvertently inserting records instead of updating them.
A simple test: Put break-points in your SaveChangesAsnc method within both the Modified and Added handlers and then run a unit test that loads an entity, edits it, and saves. Which breakpoint is hit? If the behavior seems to be normal with a simple unit test, repeat again with your code.
If the Modified breakpoint is hit, and only the Modified handler is hit then check the state of the CreatedDate value in the entity modified. Does it still reflect the original CreatedDate? If yes, then it would appear that something in your schema will be overwriting it on save. If no then you have a bug in your code that has caused it to update. If it is #null then this might also explain the behaviour in that it is not being loaded with the entity for whatever reason. Check that the property has not been configured as something like a Computed property.
If the Added breakpoint is hit at all, then this would point at a scenario where you're dealing with a detached entity, such as an entity that was read from a different DB Context and being associated to another entity in the current DB Context and saved as a byproduct. When a DbContext encounters an entity that was loaded and disassociated with a different DbContext, it will treat that entity as a completely new entity and insert a new record. The biggest single culprit for this is invariably MVC code where people pass entities to/from views. Entity references are loaded in one request, serialized to the view, and then passed back on another request. Devs assume they are receiving an entity that they can just associate to a new entity and save, but the Context of this request doesn't know about that entity, and that "entity" isn't actually an entity, it is now a POCO shell of data that the serializer created. It's no different to you newing up a new class and populating fields. EF won't know the difference. The result of this is you will trip the Added condition for your entity, and after completion you will have a duplicate record. (with different PK if EF is configured to treat PKs as Identity)
So an example is an Order screen: When presenting a screen to create a new order I may have loaded the Customer and passed that to the view to display customer information and will want to associate to the new order:
var customer = context.Customers.Single(x => x.CustomerId == 15);
var newOrder = new Order { Customer = customer };
return View(newOrder);
This looks innocent enough. When we go to save the new order after setting their details:
public ActionResult Save(Order newOrder)
{
context.Orders.Add(newOrder);
newOrder.Customer.Orders.Add(newOrder);
context.SaveChanges();
// ...
}
newOrder had a reference to Customer #14, so all looks good. We're even associating the new order to the customer's order collection. We might even want to have updated fields on the customer record to reflect a change to the Modified date. However, newOrder in this case, and all associated data including .Customer are plain 'ol C# objects at this point. We've added the new order to the Context, but as far as the context is concerned, the Customer referenced is also a new record. It will ignore the Customer ID if that is set as an Identity column and it will save a brand new Customer record (ID #15 for example) with all of the same details as Customer ID 14 and associate that to the new order. It can be subtle and easy to miss until you start querying Customers and spotting duplicate looking rows.
If you are passing entities to/from views, I'd be very wary of this gotcha. Attaching and setting modified state is one option, but that involves trusting that the data has not been tampered with. As a general rule, calls to update entities should never pass entities & attach them, but rather re-load those entities, validate row version, validate the data coming in, and only copy across fields you expect should ever be modified before saving the entity associated to the DbContext.
Hopefully that gives you a few ideas on things to check to get to the bottom of the issue.

When does the entity get detached in Entity Framework?

I have seen in many posts that if the tracking is not enabled, the entity is detached.
What I would like to know is: how can there be few objects which are not tracked and a few which are tracked?
Can someone share the code snippet which shows that this entity is not tracked by a context.
According to MSDN:
Detached: the entity is not being tracked by the context
https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
According to following post which I read:
http://blog.maskalik.com/entity-framework/2013/12/23/entity-framework-updating-database-from-detached-objects/
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
_context.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
}
Detached objects, or objects that are created outside of Entity Framework (EF), don’t have automatic tracking enabled.
And creating a POCO class in code-first approach is one such example of a detached entity.
Is this the only scenario?
There are more scenarios for the Detached Object.
1.You dont want to track an entity.
var entity= context.MyEntities.AsNoTracking().Where(...).FirsOrDefault();
In this query entities retrieved are not tracked hence any changes on the entities will not be recorded to database.
Consider this.
entity.Name = "1";
context.SaveChanges();
As this entities are not tracked the changes will not be saved
unless you attach this.
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
_context.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
}
2.Consider your working on disconnected architecture (API,Web). Consider an employee API which have PUT endpoint.
This would attach the employee to the context and update the entity as context is not aware of this entity.
Advantage : No need to fetch the employee entity from the database.
Disadvantage : Someother user changes the entity between the transaction might be losed (you can still update property that are only changed)
public void UpdateEmployee(Employee entity)
{
var entry = _context.Entry<Employee>(entity);
if (entry.State == EntityState.Detached)
{
_context.Attach(entity);
entry.State = EntityState.Modified;
}
Context.SaveChanges()
}
Second Version
public void UpdateEmployee(Employee entity)
{
var dbItem = context.EmployeeEnities.FirstOrDefault(g=>g.Id==entity.Id);
//Context is already have track of this entity, you can just update properties you have changed.
dbItem.Name = entity.Name;
Context.SaveChanges()
}

Update object using only DbSet

I'm trying to apply the unit of work pattern as described in this blog, but have bumped into the following problem: If I inject the associated DbSet into the repo only, e.g.
public ArticleRepository(DbSet<Article> articles)
{
this.articles = articles;
}
then how do I update records or set their status to modified?
Before I used
public void Update(Article article)
{
this.context.Entry(article).State = EntityState.Modified;
}
but with the new approach I don't have access to DbContext anymore. Neither DbSet.Add nor DbSet.Attach will work here, so how can I update the object in the context?
System.Data.Entity.Migrations.IDbSetExtensions contains the IDbSet extension AddOrUpdate<TEntity>. This will update the entity.
Some people like the advantage of not knowing whether they are adding a new entity or changing an existing one.
However, if you really want an error if you are updating an item that is not added yet, take a look at the Source Code of IDbSetExtensions.AddOrUpdate
Here you can see that the function first checks if the item exists and depending on the result adds or updates it as follows:
var existing = set.SingleOrDefault
(Expression.Lambda<Func <TEntity, bool>> (matchExpression, new[]
{parameter}));
if (existing != null)
{ // entity exists: update it
foreach (var keyProperty in keyProperties)
{
keyProperty.Single().SetValue
(entity, keyProperty.Single().GetValue (existing, null), null);
}
internalSet.InternalContext.Owner.Entry(existing)
.CurrentValues.SetValues (entity);
}
else
{ // not existing entity: Add it
internalSet.Add(entity);
}
If you don't want the AddOrUpdate, but really only an update, consider Creating your own Extension method for IDbSet. See Extension Methods (C# Programming Guide)

How can I prevent EF from inserting an object that already exists in the db when adding one that contains this first one?

It's quite self-explainatory.
I have a class that contains another
Let's call them Subject and Classroom
public class Subject
{
public Classroom Class {get; set;}
}
I'm using stateless facades, wich means my DbContext is disposed right after recovering the objects and is created to store the new ones.
Shouldn't it know that Classroom isn't a new object since it's ID is already in the DB?
Using the debugger I can track to the point right before I call the SaveChanges method and Classroom.id is the one I have on the database.
What's the problem? EF adds a new Classroom with the exact properties as the previous one, but with a new PK.
What am I doing wrong here?
This is the code used for the general CRUD operations (They are in my DbContext) Both update and delete work just fine:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
public void Insert(DbSet MySet, object Obj)
{
MySet.Add(Obj);
this.SaveChanges();
}
public void Delete(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Deleted;
this.SaveChanges();
}
Without seeing you're actual code on how you're either updating or creating your Subject entity, it's hard to tell. However, you're probably not attaching the Classroom so EF is assuming that the entity is new, when it's really not.
using (Model m = new Model())
{
m.Subject.Add(subject);
m.Classrooms.Attach(subject.Class);
m.SaveChanges();
}
Even though the PK is the same, without attaching to the Context, EF has no way of figuring out what you're intention is. Attaching the entity explicitly tells your context what you want.

DbContext.Entry performance issue

Following Julia Lermas book 'DbContext' on a N-Tier solution of keeping track of changes, I provided each entity with a State property and a OriginalValues dictionary (through IObjectWithState). After the entity is constructed I copy the original values to this dictionary. See this sample (4-23) of the book:
public BreakAwayContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
entity.OriginalValues = BuildOriginalValues(this.Entry(entity).OriginalValues);
}
};
}
In the constructor of the BreakAwayContext (inherited from DbContext) the ObjectMaterialized event is caught. To retrieve the original values of the entity, the DbEntityEntry is retrieved from the context by the call to this.Entry(entity). This call is slowing the process down. 80% of the time of this event handler is spend on this call.
Is there a faster way to retrieve the original values or the entities DbEntityEntry?
Context.Entry() calls DetectChanges() that depends on number of objects in context and could be very slow. In your case you could replace with faster version ((IObjectContextAdapter) ctx).ObjectContext.ObjectStateManager.GetObjectStateEntry(obj);