Reputation: 10600
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
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
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