Search code examples
entity-frameworklinq-to-entitiesentity-framework-6linq-expressions

Reuse ViewModel materializer in multiple EF queries?


I'd like to reuse the method that hydrates a view model from an Entity Framework 6 IQueryable<TEntity>. Most intuitively to me, that would look something like this:

ViewModel ToViewModel(Record record) {
    return new ViewModel {
        Title = record.Title
    }
}

// Get a single ViewModel
ViewModel GetRecord(int id) {
    return ToViewModel(Context.Records.Find(id));
}

// Get multiple ViewModels
IEnumerable<ViewModel> GetRecords() {
    return
        from record in Context.Records
        select ToViewModel(record);
}

Unfortunately EF tries to send the ToViewModel() method to the database, so enumerating the query result causes an Exception similar to "this method cannot be translated into a store expression".

Typically I'd prefer not to load the entire Entity<Record> (and all related objects referenced in the initializer) over the wire for performance reasons, otherwise I could do the following:

IEnumerable<ViewModel> GetRecords() {
    return
        from record in Context.Records.ToList()
        select ToViewModel(record);
}

I feel like I'm overlooking something fairly simple with Expression typing. Thoughts?


Solution

  • Yes, you think correctly that you should use Expression. Prepare the method, and a new helper, like this:

    public static Expression<Func<Record, ViewModel>> GetToViewModelExpression() {
        return r => new ViewModel {
            Title = r.Title
        };
    }
    
    public static ViewModel ToViewModel(Record record) {
        return GetToViewModelExpression().Compile()(record);
    }
    

    And use it in your dependent methods like this:

    // Get a single ViewModel
    ViewModel GetRecord(int id) {
        return ToViewModel(Context.Records.Find(id));
    }
    
    // Get multiple ViewModels
    IEnumerable<ViewModel> GetRecords() {
        return Context
            .Records
            .Select(GetToViewModelExpression());
    }