EF4: Model-Defined Functions Level 1 & 2

Model Defined Functions are a great addition to EF4. It allows you to add functions directly into your model rather than having to place the additional logic into business classes. This not only allows the functions to be “just there”, but you can use them in queries, something that you cannot do with properties that are defined in the classes.

Note that in the MSDN docs, these are called “Conceptual Model Functions” and also “User Defined Functions”.

The basics for MDFs are simple enough and have been covered in a few blog posts, but there are only hints at how to use them when you want to go beyond the basics.

But first the basics with a simple example that I have wanted to do since the first time I saw an EDM: concatenate First and Last Name properties.

Functions have been part of the conceptual schema since we were first introduced to the EDM. There is a new function element called DefiningExpression. Here, you can write some Entity SQL to perform a function and return the results. You don’t write an entire query expression however, just the function.

The complete query expression to return the full name could look like this.

SELECT c.contactId, Trim(c.FirstName) + “ “ + c.LastName
FROM MyEntities.Contacts AS c

If you wanted to enapsulate that full name forumla in a defining expression you need only

<DefiningExpression>
Trim(c.FirstName) + “ “ + c.LastName
</DefiningExpression>

The function also requires you to pass in the parameter the function will be preformed on, in this case, the Contact.

Here's what the function would look like. NOte that it takes a parameter.

      <Function Name="FullName" ReturnType="Edm.String">
         <Parameter Name="c" Type="TestModel.Contact">
         </Parameter>
         <DefiningExpression>
           Trim(c.FirstName) + " " + c.LastName
         </DefiningExpression>
       </Function>

As of Beta 1, this features is not supported in the designer, so you have to go into the XML to add the new function. You need to place the function in the CSDL so that it is a sibling of the Entity elements.

The function is readily available to Entity SQL queries. The contact variable is passed into the function as a parameter.

SELECT c.contactId, FullName(c)
FROM MyEntities.Contacts as c

But because LINQ to Entities works against classes, not the model itself, you need to make that function available in code. The function is not generated into any of the classes by default. You can change the T4 template of course.

The function is not added as a method of a particular entity. I have been creating a separate class called Functions. The method needs attributes to wire it up to the model and can only be used in queries, not called directly. I’ve copied the exception text that is demonstrated in the MSDN docs.

    public static class Functions
    {
        [EdmFunction("TestModel", "FullName")]
        public static string FullName(Person p)
        {
            throw new NotSupportedException
("This function can only be used in a query"); }
}

Now you can use the function in a LINQ query:

from p in context.People
select new {p.PersonID, FullName=Functions.FullName(p)}

Okay, that’s the easy one.

In both the Model Defined Functions blog post from Alex James and the documentation, there are hints about returning more than a scalar value from the function. You can return scalars, entities, types or collections. You can even define a type on the fly. I wanted to figure out how to do this. It took a lot of experimenting to figure out, but now that I did figure it out, it makes perfect sense.

Imagine that I wanted to create a new type that returned a number of calculated values, for example, Age and FullName.

If I did that in an EntitySQL query it would look like this:

SELECT c.contactID, Trim(c.FirstName) + “ “ + c.LastName, DiffYears(c.BirthDate,CurrentDateTime())
FROM MyEntities.Contacts AS c

If you look at the function above, you can see that I defined the ReturnType as an attribute of the function.

I could also define it as an element

       <Function Name="FullName">
         <ReturnType Type="Edm.String" />

For an in-line type, I can then define a RowType with properties.

       <Function Name="CalculatedDetails">
          <ReturnType>
            <RowType>
              <Property Name="Age" Type="Int32" />
              <Property Name="FullName" Type="String"/>
            </RowType>
          </ReturnType>

Then the parameter and defining expression.

Here’s the kicker for the DefiningExpression. I kept trying to just use the subset of the ESQL expression above.

         <DefiningExpression>
             DiffYears(Person.PersonalDetail.BirthDate,CurrentDateTime()),Trim(Person.FirstName) + " " + Person.LastName
        </DefiningExpression>

It failed over and over. You don’t want to know how many things I tried and how much time I spent. But you can thank me for getting this out of the way so you don’t have to do what I did. ;-)

I was missing the fact that the type I defined above was a Row. I was only returning some scalars. They needed to be wrapped in a row which is an Entity SQL function I have known but never used.

Here is the complete correct function that returns a Row.

<Function Name="CalculatedDetails">
  <ReturnType>
    <RowType>
      <Property Name="Age" Type="Int32" />
      <Property Name="FullName" Type="String"/>
    </RowType>
  </ReturnType>
  <Parameter Name="Person" Type="TestModel.Person">
  </Parameter>
  <DefiningExpression>
        Row(DiffYears(Person.PersonalDetail.BirthDate,CurrentDateTime()),Trim(Person.FirstName) + " " + Person.LastName)
  </DefiningExpression>
</Function>

You could also return a collection, perhaps your own view of orders on a customer. To do that you need to wrap the RowType element inside a CollectionType.

       <ReturnType>
          <CollectionType>
          <RowType>
            <Property ….. />
            <Property ….. />
          </RowType>
          </CollectionType>
        </ReturnType>

Using the inline type is not as straightforward as using one that returns a scalar. If you have already worked with ESQL and DbDataRecords, this won’t be painful at all.

An Entity SQL expression that uses the function could looke like this

string esql= “SELECT p.PersonID, TestModel.CalculatedDetails(p) “ + 
“from TestEntities.People as p”; ObjectQuery<DbDataRecord> detailsQuery =
context.CreateQuery<DbDataRecord>(esql);
var detailsList = detailsQuery.ToList();

You can then dig through the resulting DbDataRecords the same as you would for a row of a DataReader.

detailsList[0][0] will return the PersonId of the first result.

detailsList[0][1] will return the type as a MaterializedDataRecord with two fields.

detailsList[0][1][0] will contain the Age and detailsList[0][1][1] will contain the Full Name.

What about LINQ?

I have experimented with this a lot and can only seem to succeed by returning dbDataRecords for LINQ also. I even tried defining a type with those two properties but can’t directly get that back.

So for now, I’m stuck with

        [EdmFunction("TestModel", "CalculatedDetails")]
        public static DbDataRecord CalculatedDetails(Person p)
        {
            throw new NotSupportedException
("This function can only be used in a query"); }

The results are anonymous types whose first field is an int called PersonID and second field is a MaterializedDataRecord called Details. Again, I have to dig through the MaterializedDataRecord to get the values.

So with this example, the value of the Model Defined Function might have gotten a bit blurry because of the extra effort of dealing with the DbDataRecord, therefore let me reiterate. :-)

These functions logically fit in the model and it’s been a drag to have to have to build them into the classes. That means if you wanted to reuse them you have to distribute the classes as well as the model.

Also having the functions in the model makes it possible to use them in queries. That’s huge.

DefiningExpression is only one way to leverage the functions. I imagine that will see more ways to embed logic into those functions as time goes on. The only thing I worry about which is the same worry that we have with SqlCLR is that they will be misunderstood and misused. Developers will have to apply some thought behind what logic belongs in the model vs. in the business classes.

For more details on MDFs, looks for the terms “Conceptual Model Functions” in the MSDN docs.

Here are the key articles in there so far.

There is a whole ‘nother related feature to look at which is the ability to define functions inline in your code.

Hope this helps! I *hate* not knowing how to use a feature that looks promising.

#1 John "Z-Bo" Zabroski on 10.15.2010 at 1:25 PM

Hi Julie,

I had a question about something related to this and somebody told me to Google Search for "model-defined functions". This blog post turned up. It seems to me like Model-Defined Functions don't inherently support refactoring and aren't guarded against conceptual schema changes. Is that right? In other words, it is basically just a string you slide into the select-list of a SQL query, and Entity Framework (and therefore probably the Visual Designer) have no support for alerting you that deleting c.LastName will break

<DefiningExpression>

Trim(c.FirstName) + “ “ + c.LastName

</DefiningExpression>

#2 John "Z-Bo" Zabroski on 10.15.2010 at 1:28 PM

Also, the DefiningExpression assumes that it knows what the table alias should be for the row it operates on.

Is there a better way or is this as good as it gets?

#3 John "Z-Bo" Zabroski on 10.15.2010 at 1:33 PM

Ignore my second comment. I get it now. <DefiningExpression></DefiningExpression> must appear inside <Function></Function> I hate when that happens! Figure it out as soon as I ask it!

My first comment still applies, and am interested in the answer.

#4 Julie on 10.18.2010 at 5:42 AM

John,

I think your assumption (first comment) is correct. I haven't tested it but it is perfectly logical that the csdl won't detect if you changed the name of the LastName property and fix up the function. I'm pretty sure that at least the model won't validate if you make a breaking change like that. So you'll get a warning in that way. You'll have to test it out.