views:

43

answers:

1

Following on from the excellent answer to my previous question:

http://stackoverflow.com/questions/3220155/linq-entity-framework-generic-filter-method

I am now trying to understand how I can apply something like recursion to my solution.

To recap, instead of multiple similar declarations of this method:

protected IQueryable<Database.Product> GetActiveProducts( ObjectSet<Database.Product> products ) {

    var allowedStates = new string[] { "Active" , "Pending" };

    return (
        from product in products
        where allowedStates.Contains( product.State )
            && product.Hidden == "No"
        select product
    );

}

I now have a single implementation that accepts an interface type (IHideable) to operate on:

protected IQueryable<TEntity> GetActiveEntities<TEntity>( ObjectSet<TEntity> entities ) where TEntity : class , Database.IHideable {

    var allowedStates = new string[] { "Active" , "Pending" };

    return (
        from entity in entities
        where allowedStates.Contains( entity.State )
            && entity.Hidden == "No"
        select entity
    );

}

This works well and the solution is clean and understandable ( big thanks to http://stackoverflow.com/users/263693/stephen-cleary ).

What I am trying to do now is to apply a similar (or the same?) method to any EntityCollections associated to an EntityObject that also happen to implement IHideable.

I currently make use of GetActiveEntities() like this:

var products = GetActiveEntities( Entities.Products );

return (

    from product in products

    let latestOrder = product.Orders.FirstOrDefault( 
        candidateOrder => (
            candidateOrder.Date == product.Orders.Max( maxOrder => maxOrder.Date )
        )       
    )

    select new Product() {

        Id = product.Id ,
        Name = product.Name,
        LatestOrder = new Order() {

            Id = latestOrder.Id ,
            Amount = latestOrder.Amount,
            Date = latestOrder.Date

        }

    }

);

In this example I would like to have the Orders EntityCollection also filter by GetActiveEntities(), so that the latest order returned can never be a "hidden" one.

Is it possible to have all EntityCollections implementing IHideable to be filtered - maybe by applying some reflection/recursion inside GetActiveEntities() and calling itself? I say recursion because the best solution would go multiple levels deep walking through the entity graph.

This stuff is stretching my brain!

UPDATE #1 (moved my comments up to here)

Thanks Steve.

Making the method accept IQuerable as suggested gives this error:

'System.Data.Objects.DataClasses.EntityCollection<Database.Order>' does not contain a definition for 'GetActiveEntities' and no extension method 'GetActiveEntities' accepting a first argument of type 'System.Data.Objects.DataClasses.EntityCollection<Database.Order>' could be found (are you missing a using directive or an assembly reference?)

I presume this is because EntityCollection does not implement IQueryable.

I was able to get further by creating a second extension method that explicitly accepted EntityCollection and returned IEnumerable. That compiled but at runtime gave this error:

LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable`1[Database.Order] GetActiveEntities[Order](System.Data.Objects.DataClasses.EntityCollection`1[Database.Order])' method, and this method cannot be translated into a store expression.

I also tried calling AsQueryable() on the EntityCollection and returning IQueryable but the same error came back.

+1  A: 

Edited to use ObjectSet instead of EntityCollection

I recommend using my solution, but as an extension method, which can be applied to any query:

public static IQueryable<TEntity> WhereActive<TEntity>(
    this IQueryable<TEntity> entities)
    where TEntity : class , Database.IHideable
{
    var allowedStates = new string[] { "Active", "Pending" };

    return (
        from entity in entities
        where allowedStates.Contains(entity.State)
            && entity.Hidden == "No"
        select entity
    );
}  

This can then be used as such:

var products = Entities.Products.WhereActive();

return (

    from product in products

    let latestOrder = (
        from order in Entities.Orders.WhereActive()
        where order.ProductId == product.Id
        orderby candidateOrder.Date descending
        select order).FirstOrDefault()

    select new Product()
    {
        Id = product.Id,
        Name = product.Name,
        LatestOrder = new Order()
        {
            Id = latestOrder.Id,
            Amount = latestOrder.Amount,
            Date = latestOrder.Date
        }
    }
);
Stephen Cleary
Thanks for the reply Stephen. I actually refactored the method into an extension method earlier today but didn't want to bog down this example so used a method call. Good to know that was the right move - much of the C# language functionality is new to me.
Jamie
Thanks Steve, I have moved my comments to the question area for easier reading...
Jamie
You are correct: `EntityCollection` does not implement `IQueryable`. I've updated my answer to use `Entities.Orders` rather than `product.Orders`, which should work. (I also changed the `latestOrder` calculation to use `orderby`).
Stephen Cleary
Thanks Steve, you're a star. I enjoyed reading your profile.
Jamie