Up Build queries dynamically

Create a completely dynamic query

Last modified on October 27, 2011 15:30

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.


EntityQuery.Create

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:

  • Completely dynamic queries typically derive from EntityQuery which implements IQueryable and ITypedQuery.
  • These queries must be executed either with the IEntityQuery.Execute extension method or the EntityManager.Execute method; you can't use ToList().
  • Such queries return IEnumerable results instead of IEnumerable<T>. 

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 methods where no IQueryable<T> exists


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 ITypedEntityQuery interface describes an EntityQuery that is actually an EntityQuery<T> but the type "T" will not be known until runtime and is therefore not available at compile time.  This is not something that a developer will usually ever have to concern himself/herself with. 

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<...>.

Tags:
Created by DevForce on March 20, 2011 15:09

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2015 IdeaBlade