In this post Colin Meek and Diego Vega delve into some enhancements we are planning for LINQ to Entities, anyway over to them...
Entity Framework v1 customers preferring to write their queries using LINQ often hit a limitation on the range of functions and query patterns supported in LINQ to Entities. For some of those customers, having to resort to Entity SQL, or even to Entity SQL builder methods, feels awkward and reduces the appeal of Entity Framework.
There are two things we want to do in order to address this in future versions:
Expand the range of patterns and standard BCL methods we recognize in LINQ expressions.
Provide an extensibility mechanism that people can use to map arbitrary CLR methods to appropriate server and EDM functions.
This blog post expands on the second approach:
It is actually possible for us to improve our LINQ implementation so that all functions defined in the EDM and in the store, and even user defined functions, can be mapped to CLR methods with homologous signatures.
There are multiple dimensions to the problem space we want to address:
Functions can be defined in either the conceptual or the storage space
Functions can be defined in either the manifest, or just declared in the model
Functions can be mapped to either static CLR methods or to instance methods on the ObjectContext
This feature specifically targets composable functions
The basis of the extensibility mechanism is a new method-level attribute that carry function mapping information. Here is the basic signature of the attribute’s constructor:
The namespaceName parameter indicates the namespace for the function in metadata (i.e. “EDM” or “SQLSERVER”, or other store provider namespace). The functionName parameter takes the name of the function itself.
The following example could be product code or customer code applying the attribute on an extension method (it could be a regular static function) in order to map it to the standard deviation SQL Server function:
Notice that while this method can’t be called directly it can be used in a query like this:
The following example shows how the canonical DiffYear function is mapped:
The following example shows how a user defined function defined in SQL Server can be mapped:
We can establish that by convention the name of the CLR function defines the value of the functionName parameter. That makes the functionName parameter in the EdmFunctionAttribute optional.
To avoid having to always specify the namespaceName for each function, we define a new class-level attribute named EdmFunctionNamespaceAttribute that would define the namespace mapping globally for a given class:
Using EdmFunctionNamespaceAttribute and the convention based constructor:
When a method with the EdmFunction attribute is detected within a LINQ query expression, its treatment is identical to that of a function within an Entity-SQL query. Overload resolution is performed with respect to the EDM types (not CLR types) of the function arguments. Ambiguous overloads, missing functions or lack of overloads result in an exception. In addition, the return type of the method must be validated. If the CLR return type does not have an implicit cast to the appropriate EDM type, the translation will fail.
Instance methods on the ObjectContext will be supported as well. This allows the method to bootstrap itself and trigger direct evaluation, as in the following example (definition of the method and sample query):
Without the ObjectContext, the function cannot reach the store! To support this style of bootstrapping, the context needs to expose the LINQ query provider. For this reason, we now expose a “QueryProvider” property on the ObjectContext. This provider includes the necessary surface to construct or execute a query given a LINQ expression.
If such a method is encountered inline in another query, then we must validate that the instance argument (MethodCallExpression.Object) is the correct context, but the instance is otherwise ignored:
A function proxy can sometimes bootstrap itself without an explicit context, e.g. when an input argument is itself an IQueryable:
Particularly for functions taking collections, we will need to provide overloads for nullable and non-nullable elements. We don’t want to require awkward constructions like:
We created a simple internal tool that generates the classes that represent all the EDM canonical function and the SQL Server store functions. The tool will take the function definitions from Metadata and generate the appropriate function stubs/implementations.
The tool will be outside the product and will be run on demand. We expect to make a version of this tool available for provider writers together with the provider samples.
The methods will be in the following classes:
Note: The equivalent class in LINQ to SQL is System.Data.Linq.SqlClient.SqlMethods.
The method names will correspond to the name of the EDM/SQL function they represent. The argument names will correspond to the argument names of the EDM/SQL functions as retrieved by the metadata.
The recommendation for provider writers will be to include a similar static class in a namespace of the following form:
System.Data.Objects.[Standard provider namespace].[Standard provider prefix]Functions
For each non-aggregate function we create an overload with all inputs type as nullable of the CLR equivalent of their EDM primitive type, and the return type nullable of the CLR equivalent of their EDM primitive type.
The implementation of the functions (what gets executed if the function is invoked outside an expression tree) will be to throw a NotSupportedException.
For each aggregate function we will provide two overloads, one with IEnumerable<Nullable<T>> and another one with IEnumerable<T>, where T is the CLR equivalent of the EDM primitive type of the input. The implementations of these will check whether the input is IQueryable in which case it will implement the self-bootstrapping.
The Entity Framework team would love to hear your comments.
Alex James Program Manager, Entity Framework Team
This post is part of the transparent design exercise in the Entity Framework Team. To understand how it works and how your feedback will be used please look at this post.