I wanted to throw an update out here. I've been doing a lot of different things as of late, some development related, some not so much. But needless to say, life has been busy. I have been working on a bunch of personal projects, and I wanted to throw some items out here related to that.
Firstly, I wanted to make sure I got an update out here that I will be speaking at the Central PA .NET User Group in Harrisburg PA on 3/21. Topic has yet to be announced, but more to come on that later.
Secondly, I wanted to throw some of the smaller pieces of code that I use to make my life easier out here. Let's face it, we all have written code that was specifically tailored to make our lives easier, and we all use those building blocks like the legos designed to get us moving faster with our applications. For many of these, I'm trying to package them up and will be releasing them publicly as nuget packages. I figure if they've helped me, maybe they can do the same for you.
So in that direction, the first one I wanted to tackle isn't exactly anything spectacular, but it does make life a little simpler. Many of us leverage Entity Framework, and honestly it is one of the most powerful, and equally misunderstood technologies out there for Data Access. But one of the things that always bugged me wasn't so much a problem with entity framework, but a problem with all data access.
For this post, it's a little thing. Specifically, what I call Audit Fields. Almost all of us use these in our databases. Fields like the following:
EffectiveDate
: Specifically to track when a record becomes active and visible within an application EndDate
: Specifically a date and time the record becomes inactive or invisible within the application DateAdded
: The date the record was created AddedBy
: The person who created the record DateModified
: The date the record was last modified ModifiedBy
: The person who last modified the record
These fields are the kinds of things most of us developers do, and if you are like me, you always forget about these fields and end up troubleshooting null
values and issues related to forgetting about them, and wasting a lot of time.
Another common issue is that of the primary key. I'll be honest with you, I do a lot of mobile development work, and if you are looking at mobile. Guid
s really are the only primary key you should be using. Synchronizing an integer is a painful process. But if you use Guid
s, the key is set at the application level, which is another thing to remember. Or another thing to forget.
Not to mention, that these fields really are a data requirement, so it always would "grind my gears" that I was handling them at a business layer level. So I modified my logic to use the following solution to resolve the issue.
Firstly, I implemented a new class in my applications called "BaseModel
", which is shown below:
public class BaseDataModel
{
[Key]
public Guid PrimaryKey { get; set; }
[Required]
[DisplayName("Effective Date")]
public DateTime EffectiveDate { get; set; }
[DisplayName("End Date")]
public DateTime? EndDate { get; set; }
[Required]
[DisplayName("Date Added")]
public DateTime DateAdded { get; set; }
[Required]
[DisplayName("Added By")]
public string AddedBy { get; set; }
[DisplayName("Date Modified")]
public DateTime? DateModified { get; set; }
[DisplayName("Modified By")]
public string ModifiedBy { get; set; }
}
And I would add the following interface also to your code:
public interface IEntity
{
Guid PrimaryKey { get; set; }
DateTime EffectiveDate { get; set; }
DateTime? EndDate { get; set; }
DateTime DateAdded { get; set; }
string AddedBy { get; set; }
DateTime? DateModified { get; set; }
string ModifiedBy { get; set; }
}
This provides a nice reusable template for all my models moving forward. For each model from then forward, they would look like the following. Below is a sample of a common model I use for application configuration.
public class ConfigValue : BaseModel, IEntity
{
[Required]
public string ConfigKey { get; set; }
[Required]
public string ConfigValue { get; set; }
}
This will ensure that all your models maintain the same structure and your database will follow suit.
Finally, I recommend the following modification to your entity framework. You can override the SaveChanges
method to add custom logic to handle these audit fields and remove any extra manual effort on your part for these basic data operations.
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var auditable = ChangeTracker.Entries<IEntity>().ToList();
if (!auditable.Any()) return base.SaveChanges();
foreach (var record in auditable)
{
var userIdentity =
switch (record.State)
{
case System.Data.Entity.EntityState.Added:
if (record.Entity.Key == Guid.Empty)
{
record.Entity.Key = Guid.NewGuid();
}
record.Entity.DateAdded = DateTime.Now;
record.Entity.AddedBy = userIdentity.UserName;
if (record.Entity.EffectiveDate == DateTime.MinValue)
{
record.Entity.EffectiveDate = DateTime.Now;
}
break;
case System.Data.Entity.EntityState.Modified:
if (String.IsNullOrEmpty(record.Entity.AddedBy))
{
record.Entity.DateAdded = DateTime.Now;
record.Entity.AddedBy = userIdentity.UserName;
}
record.Entity.DateModified = DateTime.Now;
record.Entity.ModifiedBy = userIdentity.UserName;
break;
}
}
return base.SaveChanges();
}
The above code then makes it possible that every time "SaveChanges
" is called on your data context. It will automatically scan the records coming in to see if any of them implement the IEntity
interface, and if so handle the population of these fields long before it ever gets saved.
The biggest benefits of this approach being:
- Increases consistency of the data model
- Keeps data related operations out of the business layer
- Protects yourself from forgetting to perform these operations
- Ensures the audit fields are implemented in a consistent fashion
That's all for now, more to come but I wanted to share something that's helped me, and I hope it helps you too.