EF4 Part 4: Calculated Properties and Model-Defined Functions

It is fairly common to require ‘calculated properties’ on a business object, but have found this seemingly simple requirement to be quite interesting in practice with EF4.

Scenario

As our example, we want to get a Contact’s full name which I consider to be a calculated property as it’s based on concatenating a couple of standard properties.  So if we manually define a business object, we might have something like the following that achieves this:

Manually Defined C# BO
public class Contact
{       
    public string FullName
    {
        get { return this.FirstName.Trim() + " " + this.LastName.Trim(); }
    }
}

So when you have finished defining your EDM, and discovered there is no obvious way to define something like this via the designer, you maybe tempted to either modify the T4 generated code or use a partial class to extend the generated class:

Using Partial to Extend Entity
public partial class Contact
{       
    public string FullName
    {
        get { return this.FirstName.Trim() + " " + this.LastName.Trim(); }
    }
}

This all looks great, and compiles fine…but you will run into an NotSupportedException when you try to use this in LINQ to Entities:

LINQ to Entities
var students = from student in context.Contacts
               select new
               {                                
                   FullName = student.FullName                                                          
               };

foreach (var s in students)
{
    Console.WriteLine("Student: {0}", s.FullName);
}

image The reason for this is that the code we have in this property cannot be translated into a store expression.

(A more detailed explanation can be found on Moses blog)

Model-Defined Functions

This is where Model-Defined Functions (MDF) come into play: we need to define a function in the model.  These allow us to be database agnostic, but we are more limited with what we can achieve in their definition.  However, they can certainly be applied to our current example.  First we need to open our model as XML rather than the designer, and declare our function inside the conceptual model schema:

Model-Defined Function
    <Function Name="GetContactDisplayName" ReturnType="Edm.String">
      <Parameter Name="contact" Type="Model1.Contact"></Parameter>
      <DefiningExpression>
        Trim(contact.FirstName) + " " + Trim(contact.LastName)
      </DefiningExpression>
    </Function>       
  </Schema>
</edmx:ConceptualModels>

Now we need to define a common language runtime (CLR) method that maps to the function we have defined in the conceptual model (see here):

Mapping Function
[EdmFunction("Model1", "GetContactDisplayName")]
public static string GetContactDisplayName(Contact contact)
{
    throw new NotSupportedException("Direct calls are not supported");
}

If called direct, then you clearly get an exception thrown.  But if called from LINQ to Entities, it gets mapped to the MDF.  So we can use as shown below:

Calling Function
var students =
    from student in context.Contacts
    select new
    {
        FullName = MyFunctions.GetContactDisplayName(student)                                                          
    };

However, the approach of having functions you call and pass the object does not seem that elegant, especially when considering the current example.  An obvious improvement to address this is to use extension methods:

Function via Extension Method
[EdmFunction("Model1", "GetContactDisplayName")]
public static string GetFullName(this Contact contact)
{
    throw new NotSupportedException("Direct calls are not supported");            
}

As you can see you can also name the CLR method totally differently to the MDF, which is great as you can define multiple distinctly named functions in the model, and then be more general when you scope these via extension methods.

Calling Extension Method
var students =
    from student in context.Contacts
    select new
    {
        FullName = student.GetFullName()
    };

However, what would be really nice (but is not currently possible), would be to define an attribute on a property directly in the entity class itself, and have the this passed as the parameter to the MDF:

Attribute on Property
[EdmFunction("Model1", "GetContactDisplayName")]
public string FullName
{
    get
    {
        throw new NotSupportedException("Direct calls are not supported");
    }               
}

Summary

I look forward the next version, and have my fingers crossed that we can better define MDF mappings in the entities themselves as suggested above.  The main consideration when you first start to use the entities is remember there is a clear divide between standard CLR methods/properties and those that map to the model: the model mapped ones can be used in LINQ to Entities, but not direct; and vice versa.

Some people suggest going IQueryable to IEnumerable and using LINQ for Objects, etc. but then you loose some of the great benefits that IQueryable provides us with such as projection.  Take a look at Andy’s post on issues with using projection in ASP .NET, and how Felix tackles this in his series of posts.

What you can do that is somewhat context dependant is combine both worlds; rather than returning the NotSupportedException, you could define the following:

MDF and CLR
[EdmFunction("SoniaModel", "GetContactDisplayName")]
public static string GetDisplayName(this Contact contact)
{            
    return contact.FirstName.Trim() + " " + contact.LastName.Trim();
}

Obviously we are duplicating the ‘function’ in XML and C# code…but now you can use this extension method directly as well as from LINQ-Entities.

Print | posted on Wednesday, 25 August 2010 8:26 PM

Feedback

No comments posted yet.
Title  
Name
Email (never displayed)
Url
Comments   
Please add 7 and 5 and type the answer here: