简体   繁体   中英

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 DbSet s 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 DatabaseTable s 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?

You won't be able to cast to "unknown generic" and then pass it as a parameter to the function. But you can invoke a generic function with a matching parameter through reflextion. It won't look pretty though.

// first, we construct the generic type of the parameter that goes to AnyAsync.
// myType is the type of the entity for the given DbSet.
var parameterType = typeof(IQueryable<>).MakeGenericType(myType);
// second, we extract the needed method from the EF extensions.
// make sure to get the correct overload, we need the one without the predicate.
var methodInfo = typeof(EntityFrameworkQueryableExtensions)
                    .GetMethod(nameof(EntityFrameworkQueryableExtensions.AnyAsync),
                               1,
                               new[] { parameterType, typeof(System.Threading.CancellationToken) })
                    .MakeGenericMethod(myType);
// invoke the method with the untyped parameters
var result = await (Task<bool>)methodInfo
                       .Invoke(null, 
                               new object[] { dbSet, default(System.Threading.CancellationToken) });

Try casting to the non-generic IEnumerable . You can then use foreach to see if it's empty.

var dbSet = propertyInfo.GetValue(dbContext) as IEnumerable;

bool isEmpty = true;
foreach (var item in dbSet) { isEmpty = false; break; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM