Blaise
Blaise

Reputation: 22212

How to use Extension method inside a LINQ statement?

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

Answers (3)

D Stanley
D Stanley

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

flindeberg
flindeberg

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

David L
David L

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

Related Questions