mardi 4 mai 2021

How to cast down a property fetched with reflection to a its correct generic type at runtime

In a nutshell, what I am going to achieve is to validate specific DbSets in my (ef core) DbContext based on configuration. This means that I would like to specify in the configurations what tables I want to validate, and then I would apply a validation rule to the matching DbSet in my DbContext. Allow me to explain:

suppose you have the following values in the configuration as a JSON array:

"TablesToValidate" : [
    "Appointments",
    "Products"
]

And these values are mapped to the following:

public class AppSettings {
    public IEnumerable<DatabaseTable> TablesToValidate { get; set; }
}

where DatabaseTable is an Enum with all the Table names as Enum values:

[JsonConverter(typeof(StringEnumConverter))]
public enum DatabaseTable {
    Appointments,
    Products,
    Users
}

and here's a simplified version of the DbContext:

public DbContext(....)
{
    DbSet<Appointment> Appointments { get; set; }
    DbSet<Product> Products { get; set; }
    DbSet<User> Users { get; set; }
}

What should happen is that for each of the DatabaseTables fetched from the configuration, I need to get the DbSet matching its name from the DbContext and validate it not to be empty. Here's the code that I have so far and where I am stuck:

appSettings
    .TablesToValidate
    .ToList()
    .Foreach(table => {
        var propertyInfo = dbContext //an instance of the DbContext
                        .GetType()
                        .GetProperty(table.ToString());

        // let's say propertyInfo is not null

        var dbSet = propertyInfo.GetValue(dbContext) as DbSet<?>; //Consider this line
        
        if (dbSet is null || !dbSet.Any())
        {
            //db set not valid. Do Something
        }
    });

As you can see The operations I want to do on the fetched DbSet is checking if it is null (which will work if I cast it to an object) and dbSet.Any() which is the main problem.

Whatever route I take, I would still need the actual generic type of the DbSet to be able to call the Any() function on the dbSet variable. I cannot put a runtime type inside the generic definition <> since it requires a compile-time type. I also tried casting the dbSet variable to DbSet<BaseEntity> where BaseEntity is the parent of all Appointment, Product and User, but as I suspected it wouldn't work and it will return null since the cast will always fail.

Any ideas on how I may be able to solve this?





Aucun commentaire:

Enregistrer un commentaire