4

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
michal.kohut
  • 129
  • 1
  • 10
  • When you say `!!!THIS WILL NOT WORK` what exactly do you mean? What happens – Andras Zoltan Jun 10 '11 at 09:57
  • could you post the signature (don't need the body) of your `Clear()` method to help @Daniel Hilgarth and I clear something up :) – Andras Zoltan Jun 10 '11 at 10:39
  • I wonder how `pi.PropertyType.IsGenericTypeDefinition` does not fail with you. See [this](http://stackoverflow.com/questions/31771628/getting-a-dbcontext-dbsets-using-reflection?noredirect=1#comment51474495_31771628) – Veverke Aug 02 '15 at 15:15

4 Answers4

4

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.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • @Daniel Hilgarth - nice; but will this work if `Clear` is an extension method? Running a quick redux of the code I get `Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Tests.A' does not contain a definition for 'Clear'`. It certainly would work for an instance method. You also couldn't invoke the extension explicitly as a static either, because presumably it's `Clear` and needs a generic argument. Actually, perhaps we need the method declaration from the OP – Andras Zoltan Jun 10 '11 at 10:33
  • @Andras: Ah... I missed that. Corrected. – Daniel Hilgarth Jun 10 '11 at 10:34
  • @Daniel Hilgarth - yup; I then edited my comment (that never works does it!); this won't work if the OP's method is `Clear` - which I'm guessing it must be if it works on any `DbSet` (in the absence of a base class!) – Andras Zoltan Jun 10 '11 at 10:37
  • @Daniel - I don't think you did actually - it's compiling and running here; now *that* is cool; I never thought dynamic dispatch extended to cracking out the correct generic parameters for a method... +100 – Andras Zoltan Jun 10 '11 at 10:39
  • @Andras: Yes, this works. The runtime determines, which method to call. That is most likely the same mechanism that makes it possible to pass a `dynamic` to an overloaded method where both overloads have only one parameter and still have the runtime choose the correct overload. – Daniel Hilgarth Jun 10 '11 at 10:45
  • @Daniel Hilgarth - I tried writing mechanisms like that for some of my own projects before C#4 came along and it's a nightmare! I'm already using dynamic in quite a few places (despite risking the wrath of Eric Lippert!); but this certainly adds an extra tool to the box - really, genuinely, awesome. – Andras Zoltan Jun 10 '11 at 10:50
  • @Andras: Yeah, `dynamic` is pretty awesome. I don't like to use `dynamic` out of laziness as I don't like to use `object` out of laziness. But in case where you have to do reflection anyway, I think it is ok to use it. Whether reflection is needed in this scenario in the first place is a whole different question. – Daniel Hilgarth Jun 10 '11 at 10:53
  • @Daniel: yeah - this one of those situations where you have to balance the need to steer the OP in the direction of solving the immediate problem as opposed to potentially bringing their world down by criticising their approach. – Andras Zoltan Jun 10 '11 at 10:56
  • @Daniel @Andras: thank you guys for really express answers. I usually do not prefer using `dynamic`, but in this case I think it is acceptable. – michal.kohut Jun 10 '11 at 12:48
1

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.

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • 1
    One optimization would be to use `dynamic`. You might want to add a sample of that. – Daniel Hilgarth Jun 10 '11 at 10:09
  • @Daniel Hilgarth - ah yes; I still think in DIY dynamic binding sometimes :) – Andras Zoltan Jun 10 '11 at 10:11
  • @Daniel Hilgarth - actually, fancy putting up another solution for how you see `dynamic` working? I've had a thought about the `dynamic` case, but can't easily see a nice way to convert from `extnMethod(this A)` to `dynamic` that doesn't end up requiring reflection in the extension method! I'm missing something I think – Andras Zoltan Jun 10 '11 at 10:18
  • Have a look at my answer. This should do it. – Daniel Hilgarth Jun 10 '11 at 10:27
  • I was also thinking about the creating the non-generic version `public static void Clear(this DbSet set)`, but then the line `.ForEach(pi = ((DbSet)pi.GetValue(currentContext, null)).Clear());` gives me Invalid Cast Exception (cannot cast DbSet<> to DbSet). However this line is working: `DbSet testCastEmp = (DbSet) currentContext.Employees`. Am I missing something ?? – michal.kohut Jun 10 '11 at 12:58
0

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.

The Evil Greebo
  • 7,013
  • 3
  • 28
  • 55
0

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);
                      }
                      );
burlhock
  • 54
  • 2