C
C#2mo ago
Rillian

Help with EF Core polymorphic associations

Hi everyone, I'm currently at a IT job in a medical organisation. Through working with various software packages at scale I noticed two things. One was that the ability to add small amounts of plain text to various bits of data is very useful in unusual situations and quickly leads to the emergence of new workflows. And the second was that audit logs are invaluable. As I'm slowly getting to grips with EF Core (mainly used with ASP.NET) I've been trying to use polymorphic associations as a "silver bullet" for those kinds of things. The idea is that I have many different tables (users, tasks, requests, etc.) that can all have associated audit logs and comment sections. The tables containing the audit log events and comments would have two additional fields containing the type and primary key of the item they're associated with. E.g. one comment may be associated with ("request", 34) or a user locks themselves out and an entry is saved in the audit log referencing ("user", 21). As far as I understand my entities should include Discriminator fields but I haven't been able to find the information required to make it work. All relevant information I find seems to be based around an inheritance situation e.g. User and Contact inherit from Person instead of my use case with essentially replicates a regular many to one relationship. I'd also like it to support multiple inheritance so, for example, Page could inherit from AuditLog and Comments. Any advice on how to approach this would be much appreciated Thanks in advance
4 Replies
Yawnder
Yawnder2mo ago
@Rillian What you could do is have base event class, then specific classes specific for each type of entities.
public abstract class LoggedEvent {
public required int Id { get; set; }
public required DateTime EventDateTime { get; set; }
public required string Comment { get; set; }
}

public class UserLoggedEvent : LoggedEvent
{
public required int UserId { get; set; }
}

public class TaskLoggedEvent : LoggedEvent
{
public required int TaslId { get; set; }
}
public abstract class LoggedEvent {
public required int Id { get; set; }
public required DateTime EventDateTime { get; set; }
public required string Comment { get; set; }
}

public class UserLoggedEvent : LoggedEvent
{
public required int UserId { get; set; }
}

public class TaskLoggedEvent : LoggedEvent
{
public required int TaslId { get; set; }
}
When you register them, in your DataContext, you do:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LoggedEvent>()
.HasDiscriminator<string>("SubjectType")
.HasValue<UserLoggedEvent >("User")
.HasValue<TaskLoggedEvent >("Task");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LoggedEvent>()
.HasDiscriminator<string>("SubjectType")
.HasValue<UserLoggedEvent >("User")
.HasValue<TaskLoggedEvent >("Task");
}
Even though UserId is required, it will be marked as nullable, but EFC will still enforce the relationship. You can add the navigation property on the Subject types too if you want. You'd do that using the concrete classes. You can do the same for the logs and the comments. I wouldn't use the same tables though since they're significantly different: - the event logs should be optimized for write, with no modification and only pruning of old data - the comments could have a life cycle, being modifiable, etc., etc.
unhAPI
unhAPI2mo ago
Page could inherit from AuditLog and Comments
multiple inheritance in c# is possible only through interfaces also, Page is an AuditLog? Page is a Comment? or Page has an AuditLog and Page has a Comment
PixxelKick
PixxelKick2mo ago
Do you actually have any need at all to display these audit logs in the application itself? If not, I'd just slap an enum on the column to discriminate but I wouldn't put any traversal properties on it. It'd be a write only table as far as EF core is concerned. Reading audit logs I usually leave to an admin with DB access unless the client explicitly requests the audit logs are exposed to the FE on some kind of admin page. If that is the case, then how I design the audit table us heavily influenced by the business requirements of said FE page and that's a "there's 100 ways to skin a cat" problem.
Rillian
Rillian2mo ago
@boiled goose That's my issue with inheriting from abstract classes. @PixxelKick I do need to make these logs viewable in the application I'd be keen to use the same approach with other bits of data like comments. I currently have this working with joining tables. I have interfaces containing collections and then I link it all together in the context class with a HasMany relationship. I'm just wondering if I can simplify things and replace all those extra tables with a polymorphic association on the comment/auditlogentry tables
Want results from more Discord servers?
Add your server