středa 24. listopadu 2010

The specified method 'xxx' on the type 'yyy' cannot be translated into a LINQ to Entities store expression because the instance over which it is invoked is not the ObjectContext over which the query in which it is used is evaluated.

I have a method defined in the partial class for one of the entities like this:

public bool IsAnonymous() {
  return (this.Roles_ & UserRoles.Registered) == 0;
 }



This of course doesn't work within LINQ to Entities queries, because the EF provider for LINQ doesn't know how to translate that to SQL so that it could send that to the database. No problem according to the docs. You can define a "Model Defined Function" and tell the EF to use that:

        <Function Name="IsAnonymous" ReturnType="Edm.Boolean" >
          <Parameter Name="user" Type="Kosmas.Models.User"/>
          <DefiningExpression>
            BitWiseAnd(user.Roles, 1) = 0
         </DefiningExpression>
        </Function>
and

[System.Data.Objects.DataClasses.EdmFunction("Kosmas.Models""IsAnonymous")]
 public bool IsAnonymous() {
  return (this.Roles_ & UserRoles.Registered) == 0;
 }


Or can you?


Well you can't. If you try this you get a very informative error: "The specified method 'Boolean IsAnonymous()' on the type 'Kosmas.Models.User' cannot be translated into a LINQ to Entities store expression because the instance over which it is invoked is not the ObjectContext over which the query in which it is used is evaluated."


A quick Google search did not get anything useful. The only suggestions were to either change the method so that instead of a User instance I call it on the context (making the syntax rather ... silly). Or use Entity SQL (which I'd rather not either).


So I tried whether the example in Programming Entity Framework book actually works and noticed one difference. In the example they were adding the [EdmFunction] attribute to an extension method, while I'm adding it to a plain old ordinary one.


OK, let'ts try that. Let's remove the IsAnonymous method from the partial class and add


public static class Functions {
  [System.Data.Objects.DataClasses.EdmFunction("Kosmas.Models""IsAnonymous")]
  public static bool IsAnonymous(this User obj) {
   return (obj.Roles_ & UserRoles.Registered) == 0;
  }
 }
et voila, it works. I would not call this bug ... if it wasn't. But it is! Anyway, if you do get the nonsensical error message, check whether the method is an ordinary method or an extension one. And see if you can change it from one to the other.

středa 10. listopadu 2010

CSharpCodeProvider.CreateEscapedIdentifier() is it good for anything?

Mkay, so I have an extended T4 template for Entity Framework. One of the things it does is generating enums out of a few marked tables. I mark the table in the Designer and specify the column to use for the enum item names, the values are taken from the primary key. The template then connects to the database, fetches the data from those (static loookup) tables and generates the enums. And I use the code.Escape(name) that the original template uses all over the place to escape the names of the entities and properties. So I am safe right? The options will not be exactly the same as the values in the database, because they have to be valid identifiers, but what the heck. We have intellisense.
Right?

Wrong!
The code.Escape() calls CSharpCodeProvider.CreateEscapedIdentifier() which does ... well nothing really. If the string contains a space, the result will contain a space. If it contains a dash, the result will contain a dash. Etc. etc. etc.
So far it seems the only thing it does is ... if the whole string matches a C# keyword, the method prepends @.

OK, let's see the docs.

Public methodCreateEscapedIdentifierCreates an escaped identifier for the specified value. (Inherited from CodeDomProvider.)
Yeah, sure.
Any other candidates?

Public methodCreateValidIdentifierCreates a valid identifier for the specified value. (Inherited from CodeDomProvider.)
OK, let's try ... nope. Seems the only difference is that instead of @ we get an underscore. But just like the CreateEscapedIdentifier()
code.CreateValidIdentifier("Hello world") == "Hello world"

How's that a valid identifier I really do not know.
Funny thing is that code.IsValidIdentifier("Hello world") returns false. Just like code.IsValidIdentifier(code.CreateValidIdentifier("Hello world")) of course.

Thank you very much Microsoft once again!

pondělí 1. listopadu 2010

Closures and properties -> problems

OK. So C# has lambdas and closures. Fine. You've got to be carefull though! I have not tested all options, but one thing I know for sure already. It's unable to close over an object whose property I access. So if you have something like

foreach (var column in tracked.TrackedColumns) {
 if (column.Type.Contains("char")) {
  column.AsChar = (t => "'\"'+" + (t.Contains("[") ? t + column.Name + "]" : t + ".[" + column.Name + "]") + "+'\"'");
 } else if (column.Type.Contains("date")) {
  column.AsChar = (t => "'\"'" + (t.Contains("[") ? HistoryDatetimeFormat(t + column.Name + "]") : HistoryDatetimeFormat(t + ".[" + column.Name + "]")));
 } else if ...

you will find out that this doesn't work. It'll behave as if all the objects in the collection had the same function, and you end up with the column name of the first one. You have to copy the column name into a local variable. 

foreach (var column in tracked.TrackedColumns) {
 string name = column.Name;
 if (column.Type.Contains("char")) {
  column.AsChar = (t => "'\"'+" + (t.Contains("[") ? t + name + "]" : t + ".[" + name + "]") + "+'\"'");
 } else if (column.Type.Contains("date")) {
  column.AsChar = (t => "'\"'" + (t.Contains("[") ? HistoryDatetimeFormat(t + name + "]") : HistoryDatetimeFormat(t + ".[" + name + "]")));
 } else if