Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a reusable predicate for EntitySet<T>, IQueryable<T> and IEnumerable<T>

In my LINQ to SQL setup I have various tables which are mapped to classes which basically support the same interface to support versioning, i.e.

public interface IValid
{
    int? validTo { get; }
    int validFrom { get; }
}

The LINQ to SQL classes derive from this interface like this:

public partial class representationRevision : IValid
{
}

Now I would like to define a DRY (Don't Repeat Yourself) way of filtering EntitySet<T>, IEnumerable<T> and IQueryable<T> so that the resulting lists are valid for a specific revision. I've tried doing this:

public static class ExtensionMethods
{

    public static IQueryable<T> ValidFor<T>(this IQueryable<T> v, int? revision)
        where T : IValid
    {
        return v.Where(cr => ((cr.validFrom <= revision) &&
            ((cr.validTo == null) || (cr.validTo > revision)))
            || ((revision == null) && (cr.validTo == null))
            );
    }
}

But this gives problems on EntitySet<T>. I added a special implementation for EntitySet which first calls AsQueryable(), but this throws an exception. Undeterred I tried making a predicate so I could use the Where(predicate) approach:

    public static Expression<Func<contentRevision, bool>> IsValidFor(int? revision)
    {
        return ((cr) => ((cr.validFrom <= revision) &&
            ((cr.validTo == null) || (cr.validTo > revision)))
            || ((revision == null) && (cr.validTo == null)));
    }

When used with .Where<contentRevision>(IsValidFor(revision)) it gives errors like:

Error 5 'System.Data.Linq.EntitySet' does not contain a definition for 'Where' and the best extension method overload
'System.Linq.Enumerable.Where (System.Collections.Generic.IEnumerable, System.Func)' has some invalid arguments

Note that this is even without using the IValid interface... I've been trying all kinds of variations on this theme (like adding the int parameter) but they all invariably seem to fail. Any pointers to get me in the right direction?

like image 341
Matthijs P Avatar asked Jan 27 '10 09:01

Matthijs P


People also ask

What is the difference between IEnumerable T and IQueryable T?

Both IEnumerable and IQueryable are forward collection. Querying data from a database, IEnumerable execute a select query on the server side, load data in-memory on a client-side and then filter data. Querying data from a database, IQueryable execute the select query on the server side with all filters.

Does IQueryable implement IEnumerable?

The IQueryable interface inherits the IEnumerable interface so that if it represents a query, the results of that query can be enumerated.

What is an IQueryable in C#?

IQueryable is suitable for querying data from out-memory (like remote database, service) collections. While querying data from a database, IQueryable executes a "select query" on server-side with all filters. IQueryable is beneficial for LINQ to SQL queries.


1 Answers

Not sure about EntitySet<T> so will focus on IQueryable<T> and IEnumerable<T>.

To allow the LINQ provider to evaluate the expression trees that IQueryable<T> uses for its expression arguments one needs to ensure the underlying interface is preserved. Otherwise do everything in term of IEnumerable<T> (i.e. if you are OK to pull the whole dataset into local memory and then process it there, rather than in the database, just use LINQ to Objects).

The other option is the AsQueryable extension method (part of Queryable class) which converts a IEnumerable<T> to a IQueryable<T>, and then you can share the rest of the code.

IQueryable<T> SomeSharedQuery(this IQueryable<T> source) {
    return source.(LINQ query operators...);
}
IQueryable<T> SomeSharedQuery(this IEnumerable<T> source) {
    return source.AsQueryable().SomeSharedQuery();
}

So you have the shared code, with an adapter method.

like image 175
Richard Avatar answered Oct 05 '22 13:10

Richard