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.