I have the following class:
public class AuthContext : DbContext
{
public DbSet<Models.Permission> Permissions { get; set; }
public DbSet<Models.Application> Applications { get; set; }
public DbSet<Models.Employee> Employees { get; set; }
// ...
}
I created the extension method Clear()
for type DbSet<T>
. Using reflection I am able to inspect the instance of AuthContext
and read all its properties of type DbSet<T>
as PropertyInfo[
]. How can I cast the PropertyInfo
to DbSet<T>
in order to call the extension method on it ?
var currentContext = new AuthContext();
...
var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public | BindingFlags.Instance);
dbSets.Where(pi =>
pi.PropertyType.IsGenericTypeDefinition &&
pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).ToList()
.ForEach(pi = ((DbSet<T>)pi.GetValue(currentContext, null)).Clear()); // !!!THIS WILL NOT WORK
Please see Andras Zoltan's answer for an explanation of what you are doing wrong.
However, if you use .NET 4.0, you don't need to use reflection to call the method, you can simply use the new dynamic
keyword:
var currentContext = new AuthContext();
var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public |
BindingFlags.Instance);
dbSets.Where(pi => pi.PropertyType.IsGenericType &&
pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.ToList()
.ForEach(pi => ExtensionClass.Clear((dynamic)pi.GetValue(currentContext,
null)));
I changed the cast from DbSet<T>
to dynamic
and changed the way the method is called.
Because Clear
is an extension method, it can't be called directly on the dynamic
type, because dynamic
doesn't know about extension methods. But as extension methods are not much more than static methods, you can always change a call to an extension method to a normal call to the static method.
Everything you have to do is to change ExtensionClass
to the real class name in which Clear
is defined.
Your cast is wrong.
You can't cast to (DbSet<T>)
because that's not a concrete type unless T
is defined inside a generic method or generic type.
You have a couple of possibilities.
If DbSet has a base class (e.g. DbSet_BaseClass
in my code below) from which you can still implement your Clear()
method - then change it's signature from:
public static void Clear<T>(this DbSet<T>)
to:
public static void Clear(this DbSet_BaseClass)
Then you can change your cast in the .ForEach
to ((DbSet_BaseClass)pi.GetValue...
If you can't do that, you could reflect-invoke the Clear
extension method by building a specific generic version of it for the T
of the DbSet<T>
:
MethodInfo myClearMethod = typeof(container_type).GetMethod(
"Clear", BindingFlags.Public | BindingFlags.Static);
Then, given a property info and context instance:
Type propType = pi.PropertyType;
Type typeofT = propType.GetGenericArguments[0];
MethodInfo toInvoke = myClearMethod.MakeGenericMethod(typeofT);
//now invoke it
toInvoke.Invoke(null, new[] { pi.GetValue(currentContext, null) });
There are lots of optimisations you can put on top of this, caching delegates etc etc, but this will work.
Update
Or see @Daniel Hilgarth's answer for a cool way to dynamically dispatch the call to the extension method without having to do any of the above (dynamic dispatch effectively does something like the above, but for you with all the caching on top). If it were me - I'd be using that.
You can't cast the types because they've got no relationship to each other. You're getting a PropertyInfo which tells you about the type, but is not the type itself.
I think you're going to want to use Type.GetMethod to locate the "Clear" method, as a MethodInfo, and then you'll be able to call MethodInfo.Invoke.
You have to do reflection on the DbSet to invoke the Clear Method
Try this :
var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public | BindingFlags.Instance);
dbSets.Where(pi =>
pi.PropertyType.IsGenericType &&
pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).ToList()
.ForEach(pi =>
{
typeof(DbSet<>)
.MakeGenericType(pi.PropertyType.GetGenericArguments()[0])
.GetMethod("Clear")
.Invoke(pi.GetValue(currentContext, null), null);
}
);