Most of the queries created using DevForce will be instances of some query subclass that implements IQueryable<T>. However, there will be cases where we will not be able to determine "T" until runtime. A "completely dynamic" query is one where we do not know the type "T" of the query at compile time. Because these queries cannot implement IQueryable<T> they will instead implement the IQueryable interface.
The static EntityQuery.Create is a non-generic method that creates a new query for a specified type. Here is a function that employs EntityQuery.Create to produce a query that retrieves every instance of a type:
C# | public EntityQuery GetAll(Type entityType, EntityManager manager) { return EntityQuery.Create(entityType, manager); } |
VB | Public Function GetAll(ByVal entityType As Type, ByVal manager as EntityManager) As EntityQuery Return EntityQuery.Create(entityType, manager) End Function |
It's a contrived example but you can imagine something like it supporting an application feature that lets users pick the type of entity to retrieve from a list. We use it to retrieve Products and compare this approach with the strongly typed LINQ statement that does the same thing ... when you know that you are always getting Products:
C# | var queryType = typeof(Product); // the type picked by the user EntityQuery query1 = GetAll(queryType, anEntityManager); EntityQuery<Product> query2 = anEntityManager.Products; |
VB | Dim queryType = GetType(Product) ' the type picked by the user Dim query1 As EntityQuery = GetAll(queryType, anEntityManager) Dim query2 As EntityQuery(Of Product) = anEntityManager.Products |
These two queries are identical from the standpoint of how they get executed and what they return. The critical difference is that the compiler can't know the type of the query returned in the first case and therefore must return an EntityQuery (which implements IQueryable and ITypedQuery) instead of EntityQuery<T> (which implements IQueryable<T>).
This has three ramifications:
The following example illustrates these points:
C# | // IQueryable implementation var query1 = EntityQuery.Create(typeof(Product), anEntityManager); // can't call query1.ToList() because query1 is not an IQueryable<T> IEnumerable results = query1.Execute(); // Have to cast to work with Products IEnumerable<Product> products1 = results.Cast<Product>(); // IQueryable<T> implementation var query2 = anEntityManager.Products; IEnumerable<Product> products2 = query2.ToList(); |
VB | ' IQueryable implementation Dim query1 = EntityQuery.Create(GetType(Product), anEntityManager) ' can't call query1.ToList() because query1 is not an IQueryable<T> Dim results As IEnumerable = query1.Execute() ' Have to cast to work with Products Dim products1 As IEnumerable(Of Product) = results.Cast(Of Product)() ' IQueryable<T> implementation Dim query2 = anEntityManager.Products Dim products2 As IEnumerable(Of Product) = query2.ToList() |
LINQ queries, as defined in standard .NET depend on the IQueryable<T> and IEnumerable<T> interfaces. Because a "completely dynamic" query only implements the IQueryable interface there is no way to implement the "standard" LINQ operators. However, DevForce does provide a substitute.
DevForce implements a separate set of extension methods that have the same names as the standard LINQ operators but operate on instances of objects that only implement IQueryable instead of IQueryable<T>. ( See IdeaBlade.Linq.QueryableExtensions). That covers many of the familiar LINQ operators and immediate execution methods.
Some extension methods require further refinment to operate specifically on instances of DevForce EntityQuery (as opposed to EntityQuery<T>. These methods may be found in IdeaBlade.EntityModel.EntityQueryExtensions along side the other EntityQuery extension methods. They are distinguishable by their first parameter which is of type ITypedEntityQuery.
The EntityQueryExtensions include the following methods that return an ITypedEntityQuery.
EntityQueryExtensions.Where
EntityQueryExtensions.OrderBySelector
EntityQueryExtensions.Select
EntityQueryExtensions.SelectMany
EntityQueryExtensions.GroupBy
EntityQueryExtensions.Take
EntityQueryExtensions.Skip
EntityQueryExtensions.Cast
EntityQueryExtensions.OfType
as well as the following immediate execution methods that take an ITypedEntityQuery and return a scalar result.
EntityQueryExtensions.All
EntityQueryExtensions.Any
EntityQueryExtensions.Contains
EntityQueryExtensions.Count
An overload of EntityQueryExtensions.AsScalarAsync also extends ITypedEntityQuery so you can call the asynchronous scalar functions {First..., Count, etc.).
These overloads differ from the standard LINQ overloads in that none of them involve parameters that are Expression<Func<T, ...>. This is again a ramification of the fact that the generic type "T" is not available at compile time for these methods. These methods instead take either a PredicateDescription, SortSelector, or ProjectionSelector in place of the strongly typed Expression<...>.