EF Core: Built in Created and Edited timestamps [duplicate] - entity-framework

This question already has answers here:
How to add the same column to all entities in EF Core?
(2 answers)
Closed 2 years ago.
The title is quite self-explanatory. Is there a built-in mechanism to support Created and Edited timestamps on records in a code-first database in Entity Framework Core?
Something like :created_at and :updated_at in Ruby on Rails migrations. I could not find any documentation concerning this. If no out-of-the-box mechanism is present, is there a best practice how to implement those columns?

The non EF Core solutions don't apply to EF Core without some amount of rework. I wanted to allow certain entities to have CreatedAt and LastModified timestamps that updated in a reasonably automated way. This is what I ended up doing.
Defined an interface - ITimeStampedModel that my models could inherit from that required them to have the timestamps I wanted:
public interface ITimeStampedModel
{
DateTime CreatedAt {get; set;}
DateTime LastModified {get; set;}
}
Override SaveChanges on my DbContext to look for new or modified models, and update their timestamps appropriately:
public override int SaveChanges()
{
var newEntities = this.ChangeTracker.Entries()
.Where(
x => x.State == EntityState.Added &&
x.Entity != null &&
x.Entity as ITimeStampedModel != null
)
.Select(x => x.Entity as ITimeStampedModel);
var modifiedEntities = this.ChangeTracker.Entries()
.Where(
x => x.State == EntityState.Modified &&
x.Entity != null &&
x.Entity as ITimeStampedModel != null
)
.Select(x => x.Entity as ITimeStampedModel);
foreach (var newEntity in newEntities)
{
newEntity.CreatedAt = DateTime.UtcNow;
newEntity.LastModified = DateTime.UtcNow;
}
foreach (var modifiedEntity in modifiedEntities)
{
modifiedEntity.LastModified = DateTime.UtcNow;
}
return base.SaveChanges();
}
Anyone have a better solution?

Related

Perform action when an entity is updated in Entity Framework

Is there a way to run code when an entity is updated? For example I have an entity that is updated many places in my code. I want to be able to update a DateTimeUpdated field any time that entity is updated without changing every function that updates that entity.
This is typically done by overriding the SaveChanges method in the DbContext.
Start by introducing a base class or a common interface for your editable entities that you want to track the DateTimeUpdated for:
public abstract class EditableEntityBase
{
public DateTime DateTimeUpdated { get; internal set; }
}
Your entities that you want to track this for should extend this class or implement a contract interface that will expose the property.
Then in your DbContext, override the SaveChanges method and insert:
var updatedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.OfType<EditableEntityBase>();
foreach (var entity in updatedEntities)
{
entity.DateTimeUpdated = DateTime.Now; // or DateTime.UtcNow
}
return base.SaveChanges();
You can also include x.State == EntityState.Added for new records, though generally I'd rely on a Default at the DB to capture it on insert.

Unit testing with MOQ & EFCore using .Find with composite primary key

I am trying to figure out a way to be able to moq the EF Core 'Find' functionality using a composite primary key. A work colleague has pointed in one direction which does appear to work but I am wondering if there is a better solution?
This is what I have so far.
I have a factory class for creating a mock DB set. I am creating the DB set from a list objects that have this structure;
public int FK { get; set; }
public string SourceId { get; }
public string EntityName { get; set; }
public int? EntityKey { get; set; }
public string IdLong { get; set; }
The primary key consists FK, EntityName & SourceId.
This is how I am currently creating my DBSet
var assocMock = EFCoreFactory.CreateMockDBSet(new List<AData> { obj1, obj2});
// Map the associated data for the EF .Find to work
assocMock.Setup(m => m.Find(It.Is<object[]>(mm =>
mm[0] as int? == obj1.FK &&
mm[1].ToString() == obj1.EntityName &&
mm[2].ToString() == obj1.SourceId
))).Returns(obj1);
assocMock.Setup(m => m.Find(It.Is<object[]>(mm =>
mm[0] as int? == obj2.FK &&
mm[1].ToString() == obj2.EntityName &&
mm[2].ToString() == obj2.SourceId
))).Returns(obj2);
EFCoreFactory.CreateMockSet<AData>(context, assocMock.Object);
Can anybody suggest any better or more generic way to do this?
UPDATE based on the link provided by Shafiq below this is my latest attempt which I think is a bit more generic and wont require me to map each individual object. Can anybody see any flaw in this approach?
assocMock.Setup(m => m.Find(It.IsAny<object[]>()))
.Returns<object[]>(ad => assocDataList.FirstOrDefault(d => d.FK == (int)ad[0] &&
d.EntityName == ad[1].ToString() &&
d.SourceId == ad[2].ToString()))
This is actually the answer that I have gone with and works for me quite well, many thanks all.
assocMock.Setup(m => m.Find(It.IsAny<object[]>()))
.Returns<object[]>(ad => assocDataList.FirstOrDefault(d => d.FK == (int)ad[0] &&
d.EntityName == ad[1].ToString() &&
d.SourceId == ad[2].ToString()))

What is the best way to implement filtering in ASP.NET Core Web API?

I have a JobsController that has a GetJobs method as follows:
[HttpGet]
... Task<IActionResult> GetJobs([FromQuery] Pagination urlQuery)
... return await _dbContext.Jobs
.AsNoTracking
.Where(x => x.BankId == user.BankId)
.Skip(urlQuery.Limit *(urlQuery.Offset -1))
.Take(urlQuery.Limit)
.ToListAsync()
I have successfully implemented paging functionality: .../jobs?limit=25&offset=1
I tried creating a filter class
...
public int BankId {get; set;}
public bool Archived {get; set;}
...
public bool HaveFilter => !string.IsNullOrEmpty(BankId.ToString()) ||
!string.IsNullOrEmpty(Archived.ToString())
But then it gets messy when trying to use this alongside Pagination.
What would be the best way to implement server-side filtering so that I can use something like .../jobs?limit=25&offset=1&bankId=3&archived=true ?
You don't have to chain all the filters in the same statement. Since they all return IQueryable, and not actually performing the database call until ToListAsync is called, you can easily use If statements to chain what you need.
var dataToReturn = _dbContext.Jobs
.AsNoTracking;
if(BankId > 0)
dataToReturn = dataToReturn
.Where(x => x.BankId == user.BankId);
if(Archived)
dataToReturn = dataToReturn
.Where(x => x.Archived);
return dataToReturn
.Skip(urlQuery.Limit *(urlQuery.Offset -1))
.Take(urlQuery.Limit);
.ToListAsync();

Adding CreatedDate to an entity using Entity Framework 5 Code First

I am trying to add a CreatedDate property to entities in my Model and am using EF5 Code First. I want this date to not be changed once set, I want it to be a UTC date. I do NOT want to use a constructor, as I have many entities in my model that I want to inherit from an abstract class containing the CreatedDate property, and I can't enforce a constructor with an interface.
I have tried different data annotations and I have attempted to write a database initializer that would pick up a specific entity type and write an alter constraint with a getdate() default value for the correct table_name and column_name, but I have not been able to write that code correctly.
Please do not refer me to the AuditDbContext - Entity Framework Auditing Context or the EntityFramework.Extended tools, as they do not do what I need here.
UPDATE
My CreatedDate is null on SaveChanges() because I am passing a ViewModel to my view, which correctly has no audit property called CreatedDate in it. And even if I passed the model to my view, I am not editing or storing the CreatedDate in the view.
I read here that I could add the [DatabaseGenerated(DatabaseGeneratedOption.Identity)] and this would tell EF to store the CreatedDate correctly after Insert and Update, but not allow it to be changed by my application: but I just get a Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF error by adding this attribute.
I am about to switch to EF Model First because this simple database requirement is ridiculous to implement in Code First.
Here is how I did it:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate{ get; set; }
in my migration's Up() method:
AddColumn("Agents", "CreatedDate", n => n.DateTime(nullable: false, defaultValueSql: "GETUTCDATE()"));
Override the SaveChanges-Method in your context:
public override int SaveChanges()
{
DateTime saveTime = DateTime.UtcNow;
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == (EntityState) System.Data.EntityState.Added))
{
if (entry.Property("CreatedDate").CurrentValue == null)
entry.Property("CreatedDate").CurrentValue = saveTime;
}
return base.SaveChanges();
}
Updated because of comments: only freshly added Entities will have their Date set.
Similar to Stephans's Answer but with Reflection and also ignores all user (external) updates Created/Updated times. Show Gist
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(x => x.Entity.GetType().GetProperty("CreatedTime") != null))
{
if (entry.State == EntityState.Added)
{
entry.Property("CreatedTime").CurrentValue = DateTime.Now;
}
else if (entry.State == EntityState.Modified)
{
// Ignore the CreatedTime updates on Modified entities.
entry.Property("CreatedTime").IsModified = false;
}
// Always set UpdatedTime. Assuming all entities having CreatedTime property
// Also have UpdatedTime
// entry.Property("UpdatedTime").CurrentValue = DateTime.Now;
// I moved this part to another foreach loop
}
foreach (var entry in ChangeTracker.Entries().Where(
e =>
e.Entity.GetType().GetProperty("UpdatedTime") != null &&
e.State == EntityState.Modified ||
e.State == EntityState.Added))
{
entry.Property("UpdatedTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
Ok so the primary issue here was that CreatedDate was being Updated every time I called SaveChanges and since I wasn't passing CreatedDate to my views it was being updated to NULL or MinDate by Entity Framework.
The solution was simple, knowing that I only need to set the CreatedDate when EntityState.Added, I just set my entity.CreatedDate.IsModified = false before doing any work in my SaveChanges override, that way I ignored changes from Updates and if it was an Add the CreatedDate would be set a few lines later.
Code First doesn't currently provide a mechanism for providing column default values.
You will need to manually modify or create base class to automatic update CreatedDate
public abstract class MyBaseClass
{
public MyBaseClass()
{
CreatedDate = DateTime.Now;
}
public Datetime CreatedDate { get; set; }
}
For EF Core you can find the MS recommended solution here:
Default Values.
Use Fluent API in your DBContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
}
Accounts account;
account.Acct_JoinDate = DateTime.Now.ToUniversalTime();
data.Accounts.Add(account);
data.SaveChanges();
Why not give the timestamp upon model creation? Similar to these accounts here.

Sorting Collection of One-To-Many relationship - Entity Framework Codefirst

I have a codefirst entity model which has two entities, City and Locality. City has one to many relation with Locality. Here is how they are related.
public class City {
public virtual List<Locality> Localities { get; set; }
}
public class Locality {
public virtual City City { get; set; }
}
Now I have some code which finds a particular City and loads Localities for that city too. I was looking to sort the loaded Localities by "Name" using LINQ IQueryable something like this
city = context.Cities.AsNoTracking().Include(c => c.Localities.OrderBy(l => l.Name))
.Where(c => c.Id == id).FirstOrDefault();
When I use the above code it throws "ArgumentException". So I tried the code shown below.
city = context.Cities.AsNoTracking().Include(c => c.Localities)
.Where(c => c.Id == id).FirstOrDefault();
if (city != null) {
city.Localities.OrderBy(l => l.Name);
}
The above code does not throw any exceptions but it doesn't sort the the Localities by Name. I get Localities sorted by Id instead.
Any ideas how to sort the "many side" of One-to-Many relationship
LINQ queries do not modify the source collection. You need to create a new sorted City.Localities instance:
if (city != null) {
city.Localities = city.Localities.OrderBy(l => l.Name).ToList();
}
This sorts in memory after the parent and the child collection has been loaded.
And your code first snippet doesn't work because you cannot specify sort or filter criteria in an eager loading statement.
Here is another answer with some more remarks to the same problem: Entity Framework, MVC 3, OrderBy in LINQ To Entities