Mark Sowul
Mark Sowul

Reputation: 10600

Reusable logic in Entity Framework queries

Suppose I have the following (for showing an item in a drop-down list):

class SelectItemViewModel<TId>
{
  string Name {get; set;}
  TId Id {get; set;}
}

as well as this on my database entities:

interface IEntity<TId>
{
  TId Id {get;
}

I would like to be able to reuse logic to create these drop-down items, passing in something for the name. For example, right now I have (simplified):

List<SelectItemViewModel<int>> GetAvailableEntity1Items(IQuerable<Entity1> entities)
{
  return entities
    .Select(e => new SelectItemViewModel<int>
    {
      Id = e.Id,
      Name = e.Title
    })
    .ToList();
}


List<SelectItemViewModel<short>> GetAvailableEntity2Items(IQuerable<Entity2> entities)
{
  return entities
    .Select(e => new SelectItemViewModel<short>
    {
      Id = e.Id,
      Name = e.Description
    })
    .ToList();
}

They obviously have the same basic structure. I would like to just operate on IEntity<T> and pass in the "get name" function, but I can't get it to work with Entity Framework.

Doesn't work:

List<SelectItemViewModel<TId> GetAvailableItems<TEntity, TId>(IQueryable<TEntity> entitiesQuery, Func<TEntity, string> getNameForEntity)
  where TEntity : class, IEntity<TId>
{
  return entitiesQuery
    .Select(e => new SelectItemViewModel<TId>
    {
      Id = e.Id,
      Name = getNameForEntity(e)
    }
}

and I can't figure out how I'd implement

List<SelectItemViewModel<TId> GetAvailableItems<TEntity, TId>(IQueryable<TEntity> entitiesQuery, Expression<Func<TEntity, string>> getNameForEntity)
  where TEntity : class, IEntity<TId>
{
  return entitiesQuery
    .Select(e => new SelectItemViewModel<TId>
    {
      Id = e.Id,
      Name = ??
    }
}

Is there any way to reuse logic here? The only other thing I can think is another interface e.g. IHasName to get the name, but frankly I don't want the database entities responsible for indicating how they should be displayed in the UI layer.

Upvotes: 0

Views: 206

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205569

and I can't figure out how I'd implement

Welcome to the System.Linq.Expressions. Something like this should do the job

List<SelectItemViewModel<TId>> GetAvailableItems<TEntity, TId>(IQueryable<TEntity> source, Expression<Func<TEntity, string>> nameSelector)
    where TEntity : class, IEntity<TId>
{
    var item = nameSelector.Parameters[0];
    var targetType = typeof(SelectItemViewModel<TId>);
    var selector = Expression.Lambda<Func<TEntity, SelectItemViewModel<TId>>>( 
        Expression.MemberInit(Expression.New(targetType),
            Expression.Bind(targetType.GetProperty("Name"), nameSelector.Body),
            Expression.Bind(targetType.GetProperty("Id"), Expression.Property(item, "Id"))
        ), item);
    return source.Select(selector).ToList();

}

Upvotes: 1

Vadim Martynov
Vadim Martynov

Reputation: 8892

The other way is to retrieve whole entity from the database and then convert it to SelectItemViewModel:

List<SelectItemViewModel<TId> GetAvailableItems<TEntity, TId>(IQueryable<TEntity> entitiesQuery, Func<TEntity, string> getNameForEntity)
  where TEntity : class, IEntity<TId>
{
  return entitiesQuery
    .AsEnumerable() 
    .Select(e => new SelectItemViewModel<TId>
    {
      Id = e.Id,
      Name = getNameForEntity(e)
    }
}

Upvotes: 1

Related Questions