TimeStamp with EF Core 2.1 and Sql Server? - entity-framework

I want to have basically on most of my tables a "Last Updated" column so that I can quickly check when it was last updated.
Right now I been just putting a datetime and every time I do an action in my C# code I will save the new DateTime to update that field. Of course this can lead to me forgetting to do this.
I want something more automatic. I don't need this really for auditing purposes so it does not need to be too advanced.
I tried
builder.Property(x => x.RowVersion).IsRowVersion();
what should make a timestamp but when I look at in the database I see 0x00000000000007D1 when I thought it would look more like a datetime.
edit
public class CompanyConfig : IEntityTypeConfiguration<Company>
{
public void Configure(EntityTypeBuilder<Company> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.Property<DateTime>("LastUpdated");
}
}

Assuming you have defined a shadow property LastModified for each entity you want that functionality as in your example
builder.Property<DateTime>("LastUpdated");
and the question is how to update it automatically.
The technique described in the David's answer is outdated. It's still possible to override SaveChanges (and SaveChangesAsync), but there is IMHO a better approach, shown in Populate Created and LastModified automagically in EF Core. It's interface based approach, but can easily be adjusted for shadow property (or any property - the methods used work for both shadow and explicit properties).
Quick recap: Starting with v2.1, EF Core provides State change events:
New Tracked And StateChanged events on ChangeTracker can be used to write logic that reacts to entities entering the DbContext or changing their state.
Here is how you can utilize them. Add the following to your derived context class:
void SubscribeStateChangeEvents()
{
ChangeTracker.Tracked += OnEntityTracked;
ChangeTracker.StateChanged += OnEntityStateChanged;
}
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
ProcessLastModified(e.Entry);
}
void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{
if (!e.FromQuery)
ProcessLastModified(e.Entry);
}
void ProcessLastModified(EntityEntry entry)
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
var property = entry.Metadata.FindProperty("LastModified");
if (property != null && property.ClrType == typeof(DateTime))
entry.CurrentValues[property] = DateTime.UtcNow;
}
}
and add
SubscribeStateChangeEvents();
to your derived context constructor(s).
And that's all. Once the context is subscribed to these events, it will be notified anytime the entity is initially tracked or its state changes. You are interested only with Modified and Added state not triggered by query materialization (if you don't need Added, then just exclude it from the if condition). Then you check if the entity contains DateTime LastModified property using the EF Core metadata services and if yes, simply update it with the current date/time.

The pattern in EF Core for this is to make the "LastUpdated" a Shadow Property, and update it during SaveChanges().
See, eg: Implementing Common Audit Fields with EF Core’s Shadow Property

By looking at the DbContext class documentation, you can override SaveChanges method and set the LastUpdated values in there, before continuing with base.SaveChanges() at the end.
One last piece - inside SaveChanges method, you can find the modified/newly added entities using ChangeTracker property.

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.

Confusing articles and documentation about the differences (if any) between System.Data.EntityState.Add & DbSet.Add

I am working on a C# ASP.NET MVC 5 web application with EF 5. Mapping of my database tables using EF generates a DbContext class and an .edmx file. Today, I was reading a great article about creating generic DAL classes, but I stopped on the following sentence:
Note that using the Entry method to change the state of an entity will
only affect the actual entity that you pass in to the method. It won’t
cascade through a graph and set the state of all related objects,
unlike the DbSet.Add method.
That contradicts what is mentioned in these questions:
http://forums.asp.net/p/2015170/5803192.aspx
http://forums.asp.net/p/2060606/5943259.aspx
Difference between DbSet.Add(entity) and entity.State = EntityState.Added
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
In all the above questions’ answers, all users mentioned that using System.Data.EntityState.Added is exactly the same as using DbSet.Add. But the article I mentioned first states that using System.Data.EntityState.Added will not cascade through the graph.
Based on my test, I conclude that using System.Data.EntityState.Added will cascade through the graph same as in the DBset.Add case. Is the article wrong, or is it my test and the Q&A?
Those methods are the same which you can verify by regular testing, or, if you want to be completely sure - by some exploration of EF 6 code.
DbSet.Add method (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/DbSet.cs)
public virtual TEntity Add(TEntity entity)
{
Check.NotNull<TEntity>(entity, "entity");
this.GetInternalSetWithCheck("Add").Add((object) entity);
return entity;
}
This calls InternalSet<T>.Add(object) method.
DbEntityEntry<T>.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/DbEntityEntry.cs)
public EntityState State
{
get { return _internalEntityEntry.State; }
set { _internalEntityEntry.State = value; }
}
Where _internalEntityEntry is of InternalEntityEntry type.
InternalEntityEntry.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Internal/EntityEntries/InternalEntityEntry.cs)
public virtual EntityState State
{
get { return IsDetached ? EntityState.Detached : _stateEntry.State; }
set
{
if (!IsDetached)
{
if (_stateEntry.State == EntityState.Modified
&& value == EntityState.Unchanged)
{
// Special case modified to unchanged to be "reject changes" even
// ChangeState will do "accept changes". This keeps the behavior consistent with
// setting modified to false at the property level (once that is supported).
CurrentValues.SetValues(OriginalValues);
}
_stateEntry.ChangeState(value);
}
else
{
switch (value)
{
case EntityState.Added:
_internalContext.Set(_entityType).InternalSet.Add(_entity);
break;
case EntityState.Unchanged:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
break;
case EntityState.Modified:
case EntityState.Deleted:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
_stateEntry = _internalContext.GetStateEntry(_entity);
Debug.Assert(_stateEntry != null, "_stateEntry should not be null after Attach.");
_stateEntry.ChangeState(value);
break;
}
}
}
}
You see that if entity is detached (your case) and state is Added - the same InternalSet<T>.Add(object) is called.
As for verification by testing:
using (var ctx = new TestDBEntities()) {
// just some entity, details does not matter
var code = new Code();
// another entity
var error = new Error();
// Code has a collection of Errors
code.Errors.Add(error);
var codeEntry = ctx.Entry(code);
// modify code entry and mark as added
codeEntry.State = EntityState.Added;
// note we did not do anything with Error
var errorEntry = ctx.Entry(error);
// but it is marked as Added too, because when marking Code as Added -
// navigation properties were also explored and attached, just like when
// you do DbSet.Add
Debug.Assert(errorEntry.State == EntityState.Added);
}
I don't know the writer of that blog. I do know the writers of the book DbContext though (albeit not in person). They know EF inside-out. So when on page 80 they write
Calling DbSet.Add and setting the State to Added both achieve exactly the same thing.
I know what I'm up to. They do exactly the same thing, which is:
If the entity is not tracked by the context, it will start being tracked by the context in
the Added state. Both DbSet.Add and setting the State to Added are graph operations—
meaning that any other entities that are not being tracked by the context and are reachable
from the root entity will also be marked as Added.
I also know by experience that it works that way. But to remove any doubt, in EF's source code, both DbSet.Add and DbEntityEntry.State (when set to Added) arrive at the same point in ObjectContext that does the actual work:
public virtual void AddObject(string entitySetName, object entity)
It's a feature that continues to delude developers that start working with EF, as is evident from the large number of questions at StackOverflow asking something along the lines of "how come my entities are duplicated?". Julie Lerman wrote an entire blog explaining why this may happen.
This continued delusion made the EF team decide to change this behavior in EF7.
Maybe the writer of the blog you refer to was one of those deluded developers.

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)

EF 6 OriginalValues lost when using Attach method

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.

EF 4.1 - How to add a default on insertion for datetime column

Using EF 4.1 how could I add a default value to the underlying table? In this particular case how could I set a datetime column to the equivalent of getdate every time I insert a new record to the database, without having to set it in code.
Thanks in advance
The solution proposed by #elkdanger is way to go but just if you use code-first approach you don't have to create partial class - you can place initialization directly to your entity.
Don't use database approach! It will not work because it would demand marking property as database generated (to be correctly repopulated after insert). Once you mark property database generated you can never change its value in the application.
The last option is overriding SaveChanges in your derived DbContext and setting the property manually. Something like:
public override int SaveChanges()
{
var entities = ChangeTracker.Entries<YourEntityType>()
.Where(e => e.State == EntityState.Added)
.Select(e => e.Entity);
var currentDate = DateTime.Now;
foreach(var entity in entities)
{
entity.Date = currentDate;
}
return base.SaveChanges();
}
This approach can be better if there can be significant difference between creating an instance of the entity and saving the instanance.
You could create a partial class for your entity, and inside the constructor set the date column to DateTime.Now. This way, every time you create an instance of your class, that field will be set to the current date "automatically".
You could (and perhaps should) do it in the table itself using a trigger or a default value.
Entity Framework itself has not a mechanism for it. You have to do it manually in the db or the code.
You can also modify your T4 template (.tt file) to add a partial method that you call from within the generated constructor. Then, you can create your own partial class and implement the partial method and set your default value.
A snippet from the T4 template where the constructor is created, followed by the partial method. Note the last three lines:
public <#=code.Escape(entity)#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = =code.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var navigationProperty in collectionNavigationProperties)
{
#>
this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=code.Escape(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();
<#
}
#>
SetDefaultValues();
}
partial void SetDefaultValues();
That will result in a generated entity having something like:
public Foo()
{
// Properties set based on defaults in edmx
SetDefaultValues();
}
partial void SetDefaultValues();
Then, in your partial class, you can simply add something like:
partial void SetDefaultValues()
{
this.SomeDate = DateTime.Today;
}
Use [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
from System.ComponentModel.DataAnnotations.Schema;
if you have the default values configured on the database.