Reputation: 22212
I have an extension method to convert Address
into a oneline string:
public static class AddressExtensions
{
public static string ToOneLine(this Address address)
{
var sb = new StringBuilder();
sb.Append(address.Street);
if (!string.IsNullOrWhiteSpace(address.City)) sb.Append(string.Format("{0}, ",address.City));
if (!string.IsNullOrWhiteSpace(address.State)) sb.Append(string.Format("{0}, ", address.State));
if (!string.IsNullOrWhiteSpace(address.Zip)) sb.Append(string.Format("{0}, ", address.Zip));
return sb.ToString();
}
}
Then I use it to transfer data from domain model into my DTO. The code below uses foreach
and works fine:
var entity=_repository.GetAll();
var model = new List<SummaryViewModel>();
foreach (var e in entity)
{
model.Add(new SummaryViewModel
{
Address = e.Address.ToOneLine(),
Name = e.Name,
Id = e.Id
});
}
But when using LINQ
,
var model = entity.Select(e => new SummaryViewModel
{
Address = e.Address.ToOneLine(), Id = e.Id
}).ToList();
I got a System.NotSupportedException
.
LINQ to Entities does not recognize the method 'System.String ToOneLine
(Domain.Models.Address)' method, and this method cannot be translated
into a store expression.
What happened? I thought both method should work.
What is the correct way to use my extension method inside a LINQ statement?
Upvotes: 2
Views: 1981
Reputation: 152521
Becasue EF can't translate that extension method to SQL. Simplest way to fix it is to shift to Linq-to-Objects using AsEnumerable()
(which is effectively what foreach
does):
var model = entity.AsEnumerable()
.Select(e => new SummaryViewModel
{
Address = e.Address.ToOneLine(), Id = e.Id
}).ToList();
Unlike ToList
, using AsEnumerable
does not create an additional collection in memory, it just binds the Linq calls to Enumerable
instead of Queryable
, so the mapping is done in memory instead of in SQL.
Upvotes: 7
Reputation: 5007
This line:
foreach (var e in entity)
implicitly does the same thing as .AsEnumerable()
. Which enumerates and pulls object out of your database, when using the LINQ-expression LINQ2Entities is trying to convert it to a database expression. Ie executing it outside of the model-scope.
For a more in depth description of what foreach
actually does, have a look at How do foreach loops work in C#? , in reality entity
doesn't have to be an IEnumerable<T>
, it just has to implement a GetEnumerator()
. Cool, huh? :)
Ie in your solution, doing
var model = entity.AsEnumerable().Select(e => new SummaryViewModel
{
Address = e.Address.ToOneLine(), Id = e.Id
}).ToList();
would solve the issue.
Upvotes: 0
Reputation: 33815
Resolve your query, then map it.
var model = entity.ToList().Select(e => new SummaryViewModel
{
Address = e.Address.ToOneLine(), Id = e.Id
}).ToList();
As it stands right now, by using deferred execution, the database is trying to interpret your ToOneLine() extension method. By resolving it immediately with ToList(), you can properly iterate over the collection on the server and map it to your model.
Upvotes: 0