user495093
user495093

Reputation:

Select -> Group -> Order -> Select In one repository function

I know the title is not good.

I have a service that contain all of my entity functions.

In my service there is a function with this structure

IList<T> GetAllPaged<TKey>(
    List<Expression<Func<T, bool>>> predicate, 
    Expression<Func<T, TKey>> orderKeySelector
);

This function gets data from one entity and orders it.

Now I want to do more.

First to select from one entity then group it in order it and at the end select new items from grouped items.

Something like this:

IList<TReturn> GetAllPaged<TResult, TKey, TGroup, TReturn>(
    List<Expression<Func<T, bool>>> predicate, 
    Expression<Func<T, TResult>> firstSelector, 
    Expression<Func<TResult, TKey>> orderSelector, 
    Func<TResult, TGroup> groupSelector, 
    Func<IGrouping<TGroup, TResult>, TReturn> selector
);

Is that possible?

Upvotes: 3

Views: 3167

Answers (2)

Mikael &#214;stberg
Mikael &#214;stberg

Reputation: 17146

Here is a solution to what I think you asked for.

First, the base repository. I've created a base repository because I hope you will not expose this madness to the application but to the repositories only.

abstract class BaseRepo<TEntity>
{
    // If you're using Entity Framework, this method could 
    // be implemented here instead.
    protected abstract IQueryable<TEntity> Entities { get; }

    protected IList<TReturn> GetAllPaged<TResult, TKey, TGroup, TReturn>(
        List<Expression<Func<TEntity, bool>>> predicates,
        Expression<Func<TEntity, TResult>> firstSelector,
        Expression<Func<TResult, TKey>> orderSelector,
        Func<TResult, TGroup> groupSelector,
        Func<IGrouping<TGroup, TResult>, TReturn> selector)
    {
        return predicates
            .Aggregate(Entities, (current, predicate) => current.Where(predicate))
            .Select(firstSelector)
            .OrderBy(orderSelector)
            .GroupBy(groupSelector)
            .Select(selector)
            .ToList();
    }
}

Then the implementation.

class HorseRepo : BaseRepo<Horse>
{
    // This will of course be some data source
    protected override IQueryable<Horse> Entities
    {
        get
        {
            return new List<Horse> {
                new Horse { Id = 1, Name = "Mr Horse", Color = "Brown" },
                new Horse { Id = 2, Name = "Mrs Horse", Color = "White" },
                new Horse { Id = 3, Name = "Jr Horse", Color = "White" },
                new Horse { Id = 4, Name = "Sr Horse", Color = "Black" },
                new Horse { Id = 5, Name = "Dr Horse", Color = "Brown" },
            }.AsQueryable();
        }
    }

    // This is what I think you should expose to the application
    // This is the usage of the expression fest above.
    public IEnumerable<GroupedHorses> GetGroupedByColor() {
        return horseRepo.GetAllPaged(
            new List<Expression<Func<Horse, bool>>> {
                h => h.Name != string.Empty, 
                h => h.Id > 0
            },
            h => new HorseShape { Id = h.Id, Name = h.Name, Color = h.Color },
            hs => hs.Name,
            hs => hs.Color,
            g => new GroupedHorses
            {
                Color = g.Key,
                Horses = g.ToList()
            }
        );
    }
}

Some classes needed:

class GroupedHorses
{
    public string Color { get; set; }
    public IList<HorseShape> Horses { get; set; }
}

class HorseShape
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
}

// Define other methods and classes here
class Horse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
    public string UninterestingProperty { get; set; }
}

Upvotes: 2

Moeri
Moeri

Reputation: 9294

Maybe you can pass a generic function that does something with your entities? I know Expressions are fun, but you should always consider if the end result is better than something simpler.

Something like this:

TResult Query<TEntity, TResult>(Func<IQueryable<TEntity>, TResult> queryFunction)

Implementation for Entity Framework:

using(var context = new SomeContext())
{
    return queryFunction(context.Set<TEntity>());
}

Usage:

var listOfCars = carService.Query(cars => cars
    .Select(c => new { Id = c.Id, Name = c.Name })  // c stands for car
    .GroupBy(a => a.Name) // a stands for "anonymous object"
    .OrderBy( /* order your group in some fashion */)
    .ToList()
);

If you put all this logic in separate parameters, you will lose the ability to use anonymous objects and you will find yourself writing a lot of service methods.

Upvotes: 1

Related Questions