是否有可能说服实体框架在从select中调用方法时创建表达式?

时间:2013-12-10 11:15:27

标签: c# .net linq entity-framework entity-framework-6

我正在使用Entity Framework 6(POCO,代码优先方法),我有以下情况......

我有一个具有枚举属性的实体(称为Status) - 数据库中的值是一个整数,我想要做的是向用户显示该值的本地化描述。

因此,我的代码看起来像这样(实际上,它调用ResourceManager并且有更多状态值,但它是指示性的):

public static string ToDisplayable(Status status) 
{
    switch(status)
    {
        case Status.One:
            return "Status ONE";
        case Status.Two:
            return "Status TWO";
        default:
            return "Unknown Status";
     }
}

...

var results = someIqueryable
    .ToList()
    .Select(r => new 
    { 
        Status = r.Status, 
        DisplayableStatus = ToDisplayable(r.Status) 
    });

现在,这很好,因为当然好的'toList意味着在调用Select之前我的所有内容都已从数据库中撤回。如果我删除了ToList,我当然会得到以下例外:

  

发生了'System.NotSupportedException'类型的异常   mscorlib.dll但未在用户代码中处理

     

其他信息:LINQ to Entities无法识别该方法   'System.String ToDisplayable(Status)'方法,而且这个方法不行   被翻译成商店表达。

好吧,公平地说,它无法将我的函数调用转换为SQL,我并没有真正责怪它。

事情是我真的,真的,真的不希望ToList在那里,因为稍后某些Where条款可能(或可能不会)被添加,此外,我只是不需要我的实体上的所有列。

因此,我认为我唯一的选择就是这样做:

var results = someIqueryable
    .Select(r => new 
    { 
        Status = r.Status, 
        DisplayableStatus = r.Status ==  Status.One
            ? "Status ONE"
            :  r.Status ==  Status.Two
                ? "Status TWO"
                : "Unknown Status"
     });

这有点丑陋和繁琐(我的实际代码中有大约15个状态值),最糟糕的是,我无法重复使用它(但它至少可以工作)。

一开始简单方法的优点在于,因为还有其他具有Status列的实体,它们具有相同的可能值,我可以调用相同的方法。现在,如果我添加一个新的状态值,例如我必须通过我的代码搜索所有执行此转换的地方。可怕。

那么,是否有人知道如何创建可重用的代码片段,可以将其转换为可以插入Select方法的SQL表达式?

我可以用Expression编织一些魔法吗?

2 个答案:

答案 0 :(得分:0)

是不是将枚举值转换为显示代码应该处理的人类可读文本?返回Enum并在将数据绑定到View之前将Enumeration转换为可显示的值。

在最糟糕的情况下,你总能做到:

var results = someIqueryable
    .Where(...)
    .Select(r => new Status = r.Status, /* other column you're after */)
    .ToList()
    .Select(r => new 
    { 
        Status = r.Status, 
        /* other column you're after */,
        DisplayableStatus = ToDisplayable(r.Status) 
    });

答案 1 :(得分:0)

我找到了一种方法来做到这一点。项目LinqKit正是我所需要的,甚至还有一个方便的Nuget package,所以我做了:

Install-Package LinqKit

我认为我最初使用的这个库中的代码来自Tomas Petricek。我希望它在主Entity Framework项目中,但无论如何......

现在我引用了该库,我可以创建一个帮助器:

public static class StatusHelper
{
    public static readonly Expression<Func<Status, string>> ToDisplayable = s => 
        s == Status.One
            ? "Status ONE"
            : s == Status.Two
                ? "Status TWO"
                : "Unknown Status";
}

然后像这样使用帮助器:

// It is important to assign to a local variable here and
// not attempt to use the StatusHelper in the query
// see - http://stackoverflow.com/q/22223944/1039947
// Not ideal, of course, but I can live with it
var toDisplayable = StatusHelper.ToDisplayable;

var results = someIqueryable
    .AsExpandable()  // <-- This is needed for LinqKit
    .Select(x => new 
    { 
        Status = x.Status, 
        DisplayableStatus = toDisplayable.Invoke(x.Status)
    })
    .ToList();

正如您所看到的,我可以Invoke Select中的表达式,并且在针对数据库的查询中恰好发生了正确的事情。

像这样,我可以很轻松地分享我的小方法。因此,我是一个快乐的兔子,正如你所看到的,我也不需要提前评估查询。