LINQ to Entities不识别方法'System.String Format(System.String, System.Object, System.Object)'。

91

我有这个Linq查询:

private void GetReceivedInvoiceTasks(User user, List<Task> tasks)
{
    var areaIds = user.Areas.Select(x => x.AreaId).ToArray();

    var taskList = from i in _db.Invoices
                   join a in _db.Areas on i.AreaId equals a.AreaId
                   where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
                   select new Task {
                       LinkText = string.Format(Invoice {0} has been received from {1}, i.InvoiceNumber, i.Organisation.Name),
                       Link = Views.Edit
                   };
}

虽然存在问题。我正在尝试创建任务。当我将链接文本设置为常量字符串(如“Hello”)时,一切都正常。但是在上面,我试图使用发票属性构建linktext属性。

我收到以下错误:

base {System.SystemException} = {"LINQ to Entities不识别方法“System.String Format(System.String, System.Object, System.Object)”方法,而此方法无法转换为存储表达式。"}

有人知道为什么吗?有没有其他方法可以让它工作?


是的,最初漏掉了那个。 - AnonyMouse
3个回答

156

实体框架尝试在 SQL 端执行您的投影,但是那里没有类似于 string.Format 方法的等效物。请使用 AsEnumerable() 强制使用对象的 LINQ 进行评估。

根据我之前给您提供的答案,我会像这样重构您的查询:

int statusReceived = (int)InvoiceStatuses.Received;
var areaIds = user.Areas.Select(x=> x.AreaId).ToArray();

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select i)
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.Organisation.Name),
                  Link = Views.Edit
                });

我还看到你在查询中使用相关的实体(Organisation.Name),请确保将适当的Include添加到查询中,或者特别地为以后的使用实体属性进行实现,如下所示:

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select new { i.InvoiceNumber, OrganisationName = i.Organisation.Name})
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.OrganisationName),
                  Link = Views.Edit
                });

除了选择新任务部分由于表达式树翻译而无法在服务器端执行之外,还应该注意到这样做是不可取的。大概你希望任务在客户端创建。因此,查询和任务创建的分离可以更加明确。 - Tormod
4
我建议选择一个匿名类型,仅包含所需的InvoiceNumber和Organisation.Name。如果invoices实体很大,则使用后续的AsEnumerable选择i会将每一列都返回,即使您只使用了其中两列。 - Devin
1
@Devin:是的,我同意 - 实际上第二个查询示例就是这样做的。 - BrokenGlass
同意@Devin的观点,但你也可以简单地改为字符串连接。对我来说,我正在进行大量更新,字符串连接是唯一的选择。 - Jaider

16

IQueryable 继承自 IEnumerable,主要的相似点在于当你发起查询时,它会以其语言将查询发送到数据库引擎。区别在于,你可以告诉 C# 在服务器(而非客户端)上处理数据,或者让 SQL 处理数据。

因此,当你调用 IEnumerable.ToString() 时,C# 获取数据集并在对象上调用 ToString() 方法。 但是当你调用 IQueryable.ToString() 时,C# 告诉 SQL 在对象上调用 ToString() 方法,但是 SQL 中没有这样的方法。

缺点是当你在 C# 中处理数据时,在应用筛选器之前,必须将整个集合建立在内存中。

最有效的方法是使用所有可用筛选器创建 IQueryable 查询。然后在内存中构建数据并进行数据格式化。

IQueryable<Customer> dataQuery = Customers.Where(c => c.ID < 100 && c.ZIP == 12345 && c.Name == "John Doe");

 var inMemCollection = dataQuery.AsEnumerable().Select(c => new
                                                  {
                                                     c.ID
                                                     c.Name,
                                                     c.ZIP,
                                                     c.DateRegisterred.ToString("dd,MMM,yyyy")
                                                   });

7

虽然 SQL 不知道如何处理 string.Format,但它可以执行字符串连接。

如果您运行以下代码,则应获得所需的数据。

var taskList = from i in _db.Invoices
               join a in _db.Areas on i.AreaId equals a.AreaId
               where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
               select new Task {
                   LinkText = "Invoice " + i.InvoiceNumber + "has been received from " + i.Organisation.Name),
                   Link = Views.Edit
               };

一旦您执行查询,这应该比使用AsEnumerable稍微快一些(至少在我自己的代码中发现了相同的原始错误之后是这样的)。如果您正在使用C#进行更复杂的操作,则仍然需要使用AsEnumerable


2
不确定为什么Linq不能适应使用FORMATMESSAGE函数https://learn.microsoft.com/en-us/sql/t-sql/functions/formatmessage-transact-sql?view=sql-server-2017 在那之前,您的解决方案是(无需强制实现材料化)。 - MemeDeveloper
3
根据数据库结构和相关列数量的不同,使用这种方法而不是AsEnumerable(),可以大大提高效率。在真正需要将所有结果带入内存之前,避免使用AsEnumerable()ToList() - Chris Schaller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接