This is part of a series on using generics in C# to make code more resuable. Other articles in this series:
- Generic Entity Base Class (this article)
- A Truly Generic Repository, Part 1
- A Truly Generic Repository, Part 2
- Generic Admin Controller, Part 1 (coming soon)
- Generic Admin Controller, Part 2 (coming soon)
Generics in C# are hugely powerful, allowing amazing levels of code reuse. In future articles, I'll get into some of the powerful things you can achieve with things like generic repositories or even entire controllers. However, to achieve any of that requires that all your entities are on a level playing field. Let's get right to the code, since that's the fun part, anyways.
Code
Interfaces
public interface IModifiableEntity
{
string Name { get; set; }
}
public interface IEntity : IModifiableEntity
{
object Id { get; set; }
DateTime CreatedDate { get; set; }
DateTime? ModifiedDate { get; set; }
string CreatedBy { get; set; }
string ModifiedBy { get; set; }
byte[] Version { get; set; }
}
public interface IEntity<T> : IEntity
{
new T Id { get; set; }
}
IModifiableEntity
is a template for creating view models. I long despised the need to duplicate entity properties in view models that represent them, and especially the maintenance nightmare that usually entails: having to remember to mirror changes to the entity to its view models. This interace is a concession. If I have a view model for an entity, I can make it implement IModifiableEntity
and then at the very least, I will get compile time errors if the view model starts to diverge from the entity. Here, there's just one property, Name
, which admittedly is not ground-breaking. However, this can be applied more broadly, as you'll see later in this article. The other five properties on IEntity
, are nothing that should be modified via a view model, which is why they are not included.
IEntity
is fairly unremarkable, but suffice to say that, for my purposes, it holds standard properties I like to have on any database-peristed object: creation and modification dates, and who created or last modified it. Version
is for dealing with concurrency in Web Api scenarios. It's worth noting that Id
is typed as object
. That will be important later.
IEntity<T>
holds a single property, Id
, which is generically typed. This obviously is the primary key property and by making it generic, we can utilize anything here, such as int
, long
or Guid
, depending on the needs of the entity.
Implementation
public abstract class Entity<T> : IEntity<T>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public T Id { get; set; }
object IEntity.Id
{
get { return this.Id; }
}
public string Name { get; set; }
private DateTime? createdDate;
[DataType(DataType.DateTime)]
public DateTime CreatedDate
{
get { return createdDate ?? DateTime.UtcNow; }
set { createdDate = value; }
}
[DataType(DataType.DateTime)]
public DateTime? ModifiedDate { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
This is our implementation of IEntity<T>
(along with IEntity
and IModifiableEntity
, via inheritance). It's abstract because we don't actually want a table for this, itself. It's worth looking closely at the implemtation of Id
. Via data annotations, we define it as the key for any table that inherits from this class and use DatabaseGenerated
to inform Entity Framework that it should also be the "identity". In the case of types such as int
and long
, this will result in an autoincrementing primary key. For something like Guid
, it will use uniqueidentifier
to apply a unique GUID on insert. IEntity<T>.Id
is implemented implicitly, but then we explicitly implement IEntity.Id
. This may seem a little odd, but it's not without merit. With this in place, we can use IEntity
in places where we don't necessarily care about the type of Id
, but still need to access the property.
The rest of the implementation is fairly straight-forward. CreatedDate
is implemented with custom getter and setter so that if no value is set, it will default to the current date and time in UTC. This is so we don't have to worry about expressly setting a CreatedDate
.
Usage
All entities in your application, then, will now inherit from Entity<T>
:
public class Foo : Entity<int>
{
// Other properties specific to `Foo`
}
In further articles in this series, we'll see how the IEntity<T>
and IEntity
interfaces endear themselves.
Bonus
There's two other sets of interfaces/abstract classes I utilize. I'll provide these as an example, but you can freely throw them away, modify them to your purposes, or create something entirely different.
Interfaces
public enum PublishStatus
{
[Display(Name = "Draft")]
Draft,
[Display(Name = "Pending Review")]
PendingReview,
[Display(Name = "Published")]
Published
}
public interface IModifiablePublicationEntity : IModifiableEntity
{
PublishStatus Status { get; set; }
DateTime? PublishDate { get; set; }
DateTime? ExpireDate { get; set; }
}
public interface IPublicationEntity : IModifiablePublicationEntity, IEntity
{
}
public interface IPublicationEntity<T> : IPublicationEntity, IEntity<T>
{
}
public interface IModifiablePageEntity : IModifiablePublicationEntity
{
string Title { get; set; }
string Slug { get; set; }
string Description { get; set; }
string Abstract { get; set; }
string Content { get; set; }
}
public interface IPageEntity : IModifiablePageEntity, IPublicationEntity
{
}
public interface IPageEntity<T> : IPageEntity, IPublicationEntity<T>
{
}
Here we have "publication" and "page" interfaces. You'll notice they follow the same pattern as the entity interfaces above. Namely, there's "modifiable" versions of each, for the purposes of view models. Again, these are properties that should be allowed to be changed via a view model, which in the two cases here, just happens to be every property. Still, by separating it out this way, we can create an interface inheritance chain that can be used for the divergent purposes of an entity class versus a view model, without unduly restricting either.
Implementations
public abstract class PublicationEntity<T> : Entity<T>, IPublicationEntity<T>
{
private PublishStatus? status;
public PublishStatus Status
{
get { return status ?? PublishStatus.Draft; }
set { status = value; }
}
private DateTime? publishDate;
[DataType(DataType.DateTime)]
public DateTime? PublishDate
{
get
{
if (!publishDate.HasValue && Status == PublishStatus.Published)
{
publishDate = DateTime.UtcNow;
}
return publishDate;
}
set { publishDate = value; }
}
[DataType(DataType.DateTime)]
public DateTime? ExpireDate { get; set; }
}
public abstract class PageEntity<T> : PublicationEntity<T>, IPageEntity<T>
{
public string Title { get; set; }
public string Subtitle { get; set; }
[Index]
[StringLength(80)]
[Required]
public string Slug { get; set; }
[DataType(DataType.MultilineText)]
[StringLength(155)]
public string Description { get; set; }
[DataType(DataType.Html)]
public string Abstract { get; set; }
[DataType(DataType.Html)]
public string Content { get; set; }
}
Fairly straight-forward implementations, each. PublicationEntity<T>
uses custom getter and setter on Status
to default to PublishStatus.Draft
and on PublicationDate
to set the default the date and time to now, in UTC, but only if the status is set to PublishStatus.Published
. Otherwise, it remains null, unless explicitly set.
Notice that PublicationEntity<T>
inherits from Entity<T>
, and likewise, PageEntity<T>
inherits from PublicationEntity<T>
. This is important, as C# supports only single-inheritance. As I stated previously, all your entities need to inherit from Entity<T>
, so therefore, any further base classes you want to use, must also inherit from Entity<T>
. Since a "page" is a thing which is very likely to be "published", it benefits from both abstract classes it in its inheritance hierarchy.
Conclusion
The power of creating these interfaces and abstract classes cannot be overstated. Not only do they allow you to minimize a great bit of otherwise repeated code at the start, but they also serve to classify your entities into meaningful groupings that can help you further reduce code down the line. For example, if I have some bit of code that acts on a "publication" entity, by utilizing the interface, instead of the actual entity class, I can now apply that code broadly to any other entity that also has "publication" properties.
In the next article, we'll look at how to use some of this magic to our benefit by creating a truly generic repository.
Addendum: ASP.NET Identity
ASP.NET Identity requires that your "user" class (ApplicationUser
by default) inherit from IdentityUser
. As discussed above, C# only supports single inheritance. Does that mean, then, that our generic base class can't be used with Identity? Well, yes and no. No, you cannot inherit from both IdentityUser
and Entity<T>
, but via the interface, you can still utilize your "user" entity like any other entity inheriting from Entity<T>
.
public class ApplicationUser : IdentityUser, IEntity<string>
{
// Other properties
object IEntity.Id
{
get { return this.Id; }
}
public string Name { get; set; }
private DateTime? createdDate;
[DataType(DataType.DateTime)]
public DateTime CreatedDate
{
get { return createdDate ?? DateTime.UtcNow; }
set { createdDate = value; }
}
[DataType(DataType.DateTime)]
public DateTime? ModifiedDate { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
Basically, we just add in the implementation from Entity<T>
directly into the ApplicationUser
class to satisfy the IEntity<T>
interface. By default, Identity types the Id
on IdentityUser
as string
, so likewise, we use IEntity<string>
. If you use the generic version of IdentityUser
to customize the primary key type, then you just need to use that same type in place of T
in IEntity<T>
. We don't implicitly implement IEntity<T>.Id
here, because that's covered by IdentityUser
, but we still must explicitly implement IEntity.Id
.
This isn't ideal, admittedly, since you've now got some duplicated code, but unfortunately, there's no way around that. In this way, though, as long as you always use the interface rather than the abstract base class, you can now pop in ApplicationUser
any where you could with the rest of your entities that actually inherit from Entity<T>
.