Search code examples
c#entity-framework-corestatict4poco

Elegantly call leaf's static constructor from base abstract class in C#


I'm writing a POCO generator library using EF Core 3 for .NET Standard 2.0. This library will be used to generate the .cs files via T4 templates in .NET Framework and .NET Core projects.

I've created a wrapper class that stores static functions for all your typical LINQ queries like .Where, .FirstOrDefault, etc.

Here's an example:

public abstract class QueryableTable<T> where T : QueryableTable<T>, new()
{
    public static Func<DatabaseContext, bool, IQueryable<T>> QueryInitializer;

    public static List<T> Where(Expression<Func<T, bool>> predicate, bool eagerLoad = false)
    {
        using (var context = new DatabaseContext())
        {
            return QueryInitializer(context, eagerLoad).Where(predicate).ToList();
        }
    }
}

Now in my model, I use a static constructor to initialize the QueryInitializer within QueryableTable as shown here:

[Table("comment")]
public class Comment : QueryableTable<Comment>
{
    [Key]
    public int Id { get; set; }
 
    public int PersonId { get; set; }

    // Navigation property
    public Person Person { get; set; }

    static Comment()
    {
        QueryableTable<Comment>.QueryInitializer = (context, eagerLoad) => 
        {
            return eagerLoad ? 
                context.Set<Comment>().Include(t => t.Person) as IQueryable<Comment> :
                context.Set<Comment>();
        };
    }
}

This works if I call the static Comment constructor prior to accessing the abstract class via Comment.Where(t => t.Id == 1).

However, if I immediately call it without calling the static Comment constructor first, I get a null reference exception on QueryInitializer, as expected.

This all makes sense, but I'm first trying to figure out an elegant way to call the static constructor prior to the static method, and second I'm trying to convince myself that while this design will be useful in practice due to the simplicity of querying via static Class.Where() methods, it doesn't feel right making these methods static.


Solution

  • I resolved this by making a wrapper around QueryInitializer that creates a new instance of T and immediately drops it for the garbage disposal to come clean up. But, it does call Comment's static constructor which does what I wanted. It's not the cleanest in design, but it works well and is entirely self contained.

    protected static IQueryable<T> GetQueryStart(DatabaseContext context, bool eagerLoading)
    {
        if (QueryInitializer == null)
        {
            QueryInitializer = (c, e) => { return c.Set<T>(); }; // Default implementation
            new T(); // Trigger static constructor to set custom definition
        {
        return QueryInitializer(context, eagerLoading);
    }