0

So I have a method that I need to get a collection of Repositories from my Bucket, loop through those repositories and find all the records in the repository that need to be expired and then expire them. I am having a problem figuring out how to execute a Invoke using a Func. Any thoughts? Am I going down the wrong path?

    public void DeactivateNonTransversableExpiredRecords()
    {
        databucket = new AdminBucket(PublishingFactory.AuthoringContext);

        IEnumerable<Tuple<dynamic, Type>> nonTransversableList = this.GetNonTransversableRepositories(databucket);

        foreach (Tuple<dynamic, Type> repository in nonTransversableList)
        {
            Type repoType = repository.Item1.GetType(); // RepositoryType
            var method = repoType.GetMethod("FindBy"); // Method
            var entityType = repository.Item2; // EntityType

            // Not working
            IQueryable recordsToExpire = method.Invoke(new Func<BaseEntity, bool>((x) => x.IsActive));

            foreach (var row in recordsToExpire)
            {
                ((BaseEntity) row).IsActive = false;
                repository.Item1.Edit((BaseEntity) row);
            }
        }
    }`

EDIT: Solution... the contribution from @Eduard was invaluable in solving this challenge. I will up vote his contribution however, it was not the actual solution that was implemented.

Through the contributed code I found that returning a IQueryable to a dynamic variable like I was doing caused problems when trying to save records back to the database. If you are going for a Read only set then @Eduard's solution will work elegantly.

I ended up creating a publication specific method in the Model's BaseRepository that calls the .FindBy() method in the same Repository. This publication specific method returns a IList<T> to the publication application. This allows the dynamic variable to work properly when enumerating the collection and executing an .Edit() without having to worry about what Types go to what repository. Using the default .FindBy() returned a IQueryable<T> which caused EF5 to puke saying 'a new transaction is not allowed because there are other threads running in the session'.

Here is a working sample

Model's BaseRepository Code

public IList<T> GetItemsToExpire(DateTime date)
{
    return this.GetActive(x => x.ExpirationDate <= date).ToList<T>();
}

public virtual IQueryable<T> GetActive(Expression<Func<T, bool>> predicate)
{
    return this.GetActive().Where(predicate);
}

public virtual new IQueryable<T> GetActive()
{
    return this.FindBy(entity => entity.IsActive)
}

Publication Service Code

public void DeactivateNonTransversableExpiredRecords()
{
    databucket = new AdminBucket(PublishingFactory.AuthoringContext);

    IEnumerable<dynamic> nonTransversableRepositories = this.GetNonTransversableRepositories(databucket);

    foreach (dynamic repository in nonTransversableRepositories)
    {
        dynamic activeRecordsReadyToExpire = repository.GetItemsToExpire(DateTime.Now.AddDays(-1));  
        foreach (var record in activeRecordsReadyToExpire)
        {
            ((BaseEntity)record).IsActive = false;
            repository.Edit(record, true);
        }
    }
}
Tim
  • 1,249
  • 5
  • 28
  • 54
  • What is the signature of the FindBy method? I would guess that your Func is causing problems, have you attempted wrapping the func in an object[] { } array? What exception / compile error are you getting? – Clint Apr 04 '13 at 20:42
  • Compiler error is 'Can't resolve Invoke(function...)' FindBy method signature is public IQueryable FindBy(Expression> predicate) – Tim Apr 04 '13 at 20:46
  • That's because you're calling it with precisely 1 argument. The Invoke method has 2 arguments: one object and an array of objects. The first parameter represents the "this" instance (that which in the regular, non-reflection way lies before the "." operator). The second parameter represents an array of objects which will be used as the method's parameters (those which in a regular way would reside where i place question marks ( ? , ? , ? .. ). You need to pass just one parameter. But that doesn't mean you get away with it so easy. You need to specify both .( ).rCheck my answer – Eduard Dumitru Apr 04 '13 at 21:00

3 Answers3

1

I'm gonna make an assumption based on my intuition and say that I see 2 problems in your code.

1st of all, your FindBy method is most surely an instance method, not a static method. So apart from the parameter you're willing to pass it (the Func) you also need to pass it the instance on which the "FindBy" method should be called on. Plus: You need to thoroughly respect the Invoke method's signature: one object for the instance, one array of objects for the parameters.

2ndly, you would probably be just fine with using the DLR and syntactically invoke the hoped for method.

Please note my minor modification:

        Type repoType = repository.Item1.GetType(); // RepositoryType
        var method = repoType.GetMethod("FindBy"); // Method
        var entityType = repository.Item2; // EntityType

        // Should work
        IQueryable recordsToExpire = method.Invoke(
             repository.Item1, 
             new object[] { (Expression<Func<BaseEntity, bool>>)((x) => x.IsActive) }
        ) as IQueryable;

If you'll have a look at the Invoke method of the MethodInfo class you'll notice that the 1st parameter is the "this" parameter. So what you were doing was trying to invoke "FindBy" on the Func < T, ReturnType > delegate type (which does not have a method called "FindBy")

A more aesthetical approach would be to just go with the flow, use the DLR, use the power of the "dynamic" type, something like so:

        //Type repoType = repository.Item1.GetType(); // RepositoryType
        //var method = repoType.GetMethod("FindBy"); // Method
        var entityType = repository.Item2; // EntityType

        // Should work
        dynamic someDynamicResult = repository.Item1.FindBy ((Expression<Func<BaseEntity, bool>>)((x) => x.IsActive));
        IQueryable whichAtRuntimeShouldActuallyBeAnIQueryable = someDynamicResult;

BIG EDIT

Should you need to dynamically create explicit "IsActive" lambdas, you could do it like so:

public class SomeClass
{
    private static MethodInfo methodOf_CreateLambdaGeneric = 
        typeof(SomeClass).GetMethod("CreateIsActiveLambdaGeneric");

    public static Expression<Func<T, bool>> CreateIsActiveLambdaGeneric<T>() where T : BaseEntity {
        return x => x.IsActive;
    }
    public static LambdaExpression CreateIsActiveLambda(Type type) {
        MethodInfo particularized = methodOf_CreateLambdaGeneric.MakeGenericMethod(type);
        object theLambda = particularized.Invoke(null, null);
        return theLambda as LambdaExpression;
    }
    }

and then use those helper methods like so:

        //Type repoType = repository.Item1.GetType(); // RepositoryType
        //var method = repoType.GetMethod("FindBy"); // Method
        var entityType = repository.Item2; // EntityType

        // Should work
        LambdaExpression compatibleIsActiveLambda = SomeClass.CreateIsActiveLambda(entityType);
        dynamic someDynamicResult = repository.Item1.FindBy (compatibleIsActiveLambda as dynamic);
        IQueryable whichAtRuntimeShouldActuallyBeAnIQueryable = someDynamicResult;
Eduard Dumitru
  • 3,242
  • 17
  • 31
  • Thanks Eduard, I like the second solution and am working to implement it. Both are throwing additional exceptions I am trying to work through. The second method throws a runtime error of...The best overloaded method match for 'GenericRepository.EntityFramework.EntityRepository .FindBy(System.Linq.Expressions.Expression>)' has some invalid arguments. Not exactly sure why. The signature I posted above came from the GenericRepository.EntityFramework class – Tim Apr 04 '13 at 21:16
  • In that case, if you'll kindly note the little discussion me and @Tim have been having in his answer's comments section aaand.. if it's true that MarkelRiskClassification directly or indirectly extends BaseEntity and if the name of BaseEntity was chosen correctly, any other entity type does the same then, because of .NET's covariance and contravariance, you could simply combine the "dynamic" part of my answer with what Tim said about using Expression and simply do what I just updated in my answer -- have a look – Eduard Dumitru Apr 04 '13 at 21:26
  • The results of your last update... design time error "Cannot access private constructor 'Expression' here" and x.IsActive throws a design time error which may be caused by the first. – Tim Apr 04 '13 at 21:32
  • Sorry bout that.. Check out the modification. Just "cast" instead of "instantiate".... So it's (Expression) ( blah ) instead of new Expression ( blah ) – Eduard Dumitru Apr 04 '13 at 21:35
  • dynamic someDynamicResult = repository.Item1.FindBy((Expression>)((x) => x.IsActive)); returned the same error for invalid args – Tim Apr 04 '13 at 21:38
  • Please check out the BIG EDIT I made.. I assumed that the entityType variable (the second member of the tuple) is what the actual runtime type of the entities (in other words IQueryable< THIS GUY RIGHT HERE > ) – Eduard Dumitru Apr 04 '13 at 22:00
  • That is a correct assumption. The repository signature for this specific one would be public class MarkelRiskClassficationRepository : UWBaseRepository, IMarkelRiskClassificationRepository . I returned both items in the Tuple in the event I needed them – Tim Apr 04 '13 at 22:21
  • No joy with that one either. I get the same 'Invalid Arguments' message when it attempts to execute 'dynamic someDynamicResult = repository.Item1.FindBy(compatibleIsActiveLambda);' – Tim Apr 05 '13 at 13:31
  • Just for fun I ran a direct query using IQueryable collection = databucket.MarkelClassificationRepository.FindBy(x => x.IsActive); and I returned a set of data. – Tim Apr 05 '13 at 13:39
  • I can confirm that your new methods work as I called the following with success IQueryable collection = databucket.MarkelClassificationRepository.FindBy((Expression>)compatibleIsActiveLambda); I can also successfully execute the repository.Item1 call using the cast to the type but the problem is the type in the cast can't be hard coded. – Tim Apr 05 '13 at 14:07
  • Well, in that case I'm really sorry I didn't check the site earlier cause all which was keeping you from reaching your goal (I trully hope this time :) ) was this "as" cast to "dynamic" (check the EDIT): dynamic someDynamicResult = repository.Item1.FindBy (compatibleIsActiveLambda as dynamic); – Eduard Dumitru Apr 05 '13 at 21:09
  • Hey @Eduard. I tried the casts and the safe cast earlier today with no luck. I did come up with a solution which I will document in the question. What I found was that Find() was returning IQueryable so when I tried to call 'repository.Item1.Edit(row)' EF5 blew up saying 'a new transaction is not allowed because there are other threads running in the session'. So I created a method in my modelrepopsitory specifically for publishing that calls the find() there, but returns a IList to the dynamic variable. Then I was successful in saving the changes. Thank for all you contributed. – Tim Apr 06 '13 at 04:28
0

FindBy is expecting an Expression<Func<TEntity, bool>> you're giving it a Func<TEntity, bool>

Have you tried dropping the Func< piece and just doing method.Invoke((x) => x.IsActive)?

Clint
  • 6,133
  • 2
  • 27
  • 48
  • Without a cast to BaseEntity x.IsActive causes a design time error – Tim Apr 04 '13 at 20:55
  • @Tim Ah, yes, I see your predicament, you might find some useful information here: http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct – Clint Apr 04 '13 at 20:57
  • I think the title of the question which of course, contains the "Expression" word does not mean (nor does anything else) that @Tim needs Expression. It's more like saying: Expression in his case is something like "externally defined rule for selecting stuff". I don't think he's writing custom LINQ providers or things like that... – Eduard Dumitru Apr 04 '13 at 21:08
  • That being said, I don't know if you've tested your code but the "MethodInfo" class does not have a method with just one parameter (you just echoed @Tim's compiler error in your answer). Furthemore, lambdas are not implicitly convertible to object. You always need to explicitly "cast" or "instantiate" them to a proper delegate type. – Eduard Dumitru Apr 04 '13 at 21:10
  • @EduardDumitru I hadn't tested the code no, it was a quick put together to just illustrate the chain of thought. – Clint Apr 04 '13 at 21:11
  • Without any hidden sarcasm (just trying to understand here)... How did you realize that the "FindBy" method was expecting an Expression< TDelegate > ? I'm honestly just trying to learn something together here. Is it a well known method in the Base Class Library of .NET or something ? It did not strike my mind and I'm still not getting it... The reason why I'm so interested is because you also suggested the http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct answer in your comment... – Eduard Dumitru Apr 04 '13 at 21:16
  • @EduardDumitru the compiler error: Compiler error is 'Can't resolve Invoke(function...)' FindBy method signature is public IQueryable FindBy(Expression> predicate) states that it's expecting an Expression. – Clint Apr 04 '13 at 21:17
  • OK! :) That's what I missed. Thanks ;) I guess in that case that @Tim is indeed doing some voodoo library (very powerful stuff... sending dynamically generated expressions which will in turn be translated into SQL and sent to the database accordingly or something like that). Thanks again :) – Eduard Dumitru Apr 04 '13 at 21:22
0

See solution in the Question for what worked in my situation. Also if you are not worrying about writing back to the database then @EduardDumitru solution will work great.

Tim
  • 1,249
  • 5
  • 28
  • 54