91

I have this linq query:

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

It has issues though. I'm trying to create tasks. For each new task when I set the link text to a constant string like "Hello" it is fine. However above I'm trying to build the property linktext using properties of the invoice.

I get this error:

base {System.SystemException} = {"LINQ to Entities does not recognize the method 'System.String Format(System.String, System.Object, System.Object)' method, and this method cannot be translated into a store expression."}

Anyone know why? Anyone know an alternative way of doing this to make it work?

cramopy
  • 3,459
  • 6
  • 28
  • 42
AnonyMouse
  • 18,108
  • 26
  • 79
  • 131
  • Yes, missed that out originally – AnonyMouse Apr 09 '12 at 21:21
  • possible duplicate of [LINQ to Entities does not recognize the method 'System.String ToString()' method](http://stackoverflow.com/questions/4121863/linq-to-entities-does-not-recognize-the-method-system-string-tostring-method) – John Saunders Dec 12 '13 at 13:32

3 Answers3

156

Entity Framework is trying to execute your projection on the SQL side, where there is no equivalent to string.Format. Use AsEnumerable() to force evaluation of that part with Linq to Objects.

Based on the previous answer I have given you I would restructure your query like this:

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

Also I see you use related entities in the query (Organisation.Name) make sure you add the proper Include to your query, or specifically materialize those properties for later use, i.e.:

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
                });
Community
  • 1
  • 1
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
  • In addition to the fact that the select-new-task part cannot happen server side due to expression tree translation, it should also be noted that it is undesirable to do so. Presumably, you want the tasks to be created client side. Therefore the separation of query and creation of tasks could be even more explicit. – Tormod Apr 09 '12 at 21:54
  • 4
    I would also recommend selecting an anonymous type that just has the needed InvoiceNumber and Organisation.Name in it. If the invoices entity is large then the select i with the subsequent AsEnumerable will pull back every column even though you are only using two. – Devin Apr 10 '12 at 21:45
  • 1
    @Devin: Yes, I agree - in fact that is exactly what the second query example is doing. – BrokenGlass Apr 10 '12 at 21:47
  • Agree with @Devin, but also you can simply change to string concatenation. For me, I am doing a bulk update and string concatenation is the only way to go. – Jaider Sep 15 '22 at 01:59
16

IQueryable derives from IEnumerable, the main resemblance is that when you make your query it is posted to the database engine in it's language, the thin moment is where you tell C# to handle the data on the server(not client side) or to tell SQL to handle data.

So basically when you say IEnumerable.ToString(), C# gets the data collection and calls ToString() on the object. But when you say IQueryable.ToString() C# tells SQL to call ToString() on the object but there is no such method in SQL.

The drawback is that when you handle data in C# the whole collection that you are looking through must be built up in memory before C# applies the filters.

Most efficient way to do it is to make the query as IQueryable with all the filters that you can apply.

And then build it up in memory and make the data formatting in 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")
                                                   });
d219
  • 2,707
  • 5
  • 31
  • 36
Nikolay
  • 1,076
  • 1
  • 13
  • 11
7

While SQL does not know what to do with a string.Format it can perform string concatenation.

If you run the following code then you should get the data you are after.

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

Once you actually perform the query this should be marginally faster than using AsEnumerable (at least that's what I found in my own code after having the same original error as you). If you are doing something more complex with C# then you will still need to use AsEnumerable though.

d219
  • 2,707
  • 5
  • 31
  • 36
  • 2
    Not sure why Linq could not be adapted to use FORMATMESSAGE function https://learn.microsoft.com/en-us/sql/t-sql/functions/formatmessage-transact-sql?view=sql-server-2017 Until then yours is the solution (without forcing materialisation) – MemeDeveloper Mar 06 '19 at 15:19
  • 3
    Depending on the database structure and number of related columns, using this method instead of `AsEnumerable()` can be vastly more efficient. Avoid `AsEnumerable()` and `ToList()` until you really do want to bring all results into memory. – Chris Schaller Jan 03 '20 at 04:54