LINQ to Entities no reconoce el método 'System.String Format (System.String, System.Object, System.Object)'

88

Tengo esta consulta de 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
                   };
}

Sin embargo, tiene problemas. Estoy intentando crear tareas. Para cada nueva tarea, cuando establezco el texto del enlace en una cadena constante como "Hola", está bien. Sin embargo, arriba estoy tratando de construir el linktext de la propiedad usando las propiedades de la factura.

Me sale este error:

base {System.SystemException} = {"LINQ to Entities no reconoce el método 'System.String Format (System.String, System.Object, System.Object)', y este método no se puede traducir a una expresión de tienda". }

¿Alguien sabe por qué? ¿Alguien conoce una forma alternativa de hacer esto para que funcione?

AnonyMouse
fuente
Sí, me lo perdí originalmente
AnonyMouse

Respuestas:

148

Entity Framework está tratando de ejecutar su proyección en el lado de SQL, donde no hay equivalente a string.Format. Úselo AsEnumerable()para forzar la evaluación de esa parte con Linq to Objects.

Según la respuesta anterior que le he dado, reestructuraría su consulta de esta manera:

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
                });

También veo que usa entidades relacionadas en la consulta ( Organisation.Name) asegúrese de agregar lo adecuado Includea su consulta, o materialice específicamente esas propiedades para su uso posterior, es decir:

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
                });
Vidrio roto
fuente
Además del hecho de que la parte de seleccionar una nueva tarea no puede suceder en el lado del servidor debido a la traducción del árbol de expresión, también debe tenerse en cuenta que no es deseable hacerlo. Presumiblemente, desea que las tareas se creen del lado del cliente. Por tanto, la separación de consulta y creación de tareas podría ser aún más explícita.
Tormod
3
También recomendaría seleccionar un tipo anónimo que solo tenga el InvoiceNumber y Organisation.Name necesarios. Si la entidad de facturas es grande, la i de selección con el AsEnumerable posterior retirará todas las columnas aunque solo esté usando dos.
Devin
1
@Devin: Sí, estoy de acuerdo; de hecho, eso es exactamente lo que está haciendo el segundo ejemplo de consulta.
BrokenGlass
15

IQueryable deriva de IEnumerable , el parecido principal es que cuando realiza su consulta, se publica en el motor de la base de datos en su idioma, el momento delgado es cuando le dice a C # que maneje los datos en el servidor (no en el lado del cliente) o que le diga a SQL que maneje datos.

Básicamente, cuando dices IEnumerable.ToString(), C # obtiene la recopilación de datos y llama ToString()al objeto. Pero cuando dices IQueryable.ToString()C # le dice a SQL que llameToString() al objeto, pero no existe tal método en SQL.

El inconveniente es que cuando maneja datos en C #, toda la colección que está buscando debe estar construida en la memoria antes de que C # aplique los filtros.

La forma más eficaz de hacerlo es realizar la consulta como IQueryablecon todos los filtros que puede aplicar.

Y luego constrúyalo en la memoria y haga el formato de datos en C #.

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")
                                                   });
Nikolay
fuente
3

Si bien SQL no sabe qué hacer con un string.Format, puede realizar una concatenación de cadenas.

Si ejecuta el siguiente código, debería obtener los datos que busca.

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
               };

Una vez que realizas la consulta, esto debería ser ligeramente más rápido que usar AsEnumerable(al menos eso es lo que encontré en mi propio código después de tener el mismo error original que tú). Sin embargo, si está haciendo algo más complejo con C #, aún necesitará usarlo AsEnumerable.

d219
fuente
2
No estoy seguro de por qué Linq no se pudo adaptar para usar la función FORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/… Hasta entonces, la suya es la solución (sin forzar la materialización)
MemeDeveloper
2
Dependiendo de la estructura de la base de datos y el número de columnas relacionadas, usar este método en lugar de AsEnumerable()puede ser mucho más eficiente. Evite AsEnumerable()y ToList()hasta que realmente desee recordar todos los resultados.
Chris Schaller