1

I have the following PaginatedList class.

public class PaginatedList<T> : List<T>
{
    public int PageIndex { get; }
    public int TotalRecords { get; }
    public int TotalPages { get; }

    public PaginatedList(IEnumerable<T> items, int totalRecords, int pageIndex, int pageSize)
    {
        PageIndex = pageIndex;
        TotalRecords = totalRecords;
        TotalPages = (int)Math.Ceiling(TotalRecords / (double)pageSize);

        AddRange(items);
    }

    public bool HasPreviousPage => PageIndex > 1;

    public bool HasNextPage => PageIndex < TotalPages;

    public static async Task<PaginatedList<T>> CreateAsync(
        IQueryable<T> source, int pageIndex, int pageSize)
    {
        var count = await source.CountAsync();
        var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        return new PaginatedList<T>(items, count, pageIndex, pageSize);
    }
}

I'm using this class to get pagination information for list of entity that is retrieved with EF.

This is how I use this class to return list of users with pagination info.

var users = await PaginatedList<User>.CreateAsync(userQuery, pageIndex, pageSize);

Above call will return PaginatedList<User> object.

If I have a DTO class for that entity, let's call it UserDto. How do I use automapper to convert PaginatedList<User> into PaginatedList<UserDto> so the result will have all userDto objects and also the pagination info?

Otherwise, is there another/better way to achieve something similar?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
muhihsan
  • 2,250
  • 6
  • 27
  • 42
  • 1
    Deriving from `List` is an extremely poor choice. I highly recommend reading [Is-A Versus Has-A](https://www.c-sharpcorner.com/blogs/isa-versus-hasa1). – Erik Philips Jan 03 '19 at 03:16
  • @ErikPhilips thanks for the suggestion, I'll take a look – muhihsan Jan 03 '19 at 05:55
  • @ErikPhilips Why is deriving a bad choice? I think PaginatedList is a List, but with additional attributes. – georgesolc Dec 24 '20 at 17:11
  • @user3049658 [Why not inherit from List](https://stackoverflow.com/questions/21692193). There are properties `PageIndex` on THIS questions implementation that are not *what a list nor paginatedlist* is. `PageIndex` is property of a State, not a property of a List. – Erik Philips Dec 25 '20 at 08:41

2 Answers2

2

I ended up creating another factory method within the PaginatedList which will handle the conversion.

public static async Task<PaginatedList<TResult>> CreateAsync<TSource>(
    IQueryable<TSource> source, int pageIndex, int pageSize)
{
    Ensure.It.IsGreaterThan(0, pageIndex);
    Ensure.It.IsGreaterThan(0, pageSize);

    var count = await source.CountAsync();
    var items = await source.Skip((pageIndex - 1) * pageSize)
        .Take(pageSize)
        .Select(ufc => Mapper.Map<TResult>(ufc))
        .ToListAsync();
    return new PaginatedList<TResult>(items, count, pageIndex, pageSize);
}

Then make the existing CreateAsync method to call this new CreateAsync<TSource> method.

public static async Task<PaginatedList<TResult>> CreateAsync(
    IQueryable<TResult> source, int pageIndex, int pageSize)
{
    return await CreateAsync<TResult>(source, pageIndex, pageSize);
}

With this, if we want to keep returning the same Entity class, we can use it like this

await PaginatedList<User>.CreateAsync(_dbContext.User.AsQueryable(), pageIndex, pageSize)

And if we want to convert the entity class to return a Dto class or other class, it can be used like this

await PaginatedList<UserDto>.CreateAsync(_dbContext.User.AsQueryable(), pageIndex, pageSize)

If we don't convert the Entity into other class, we don't need to specify anything within the automapper configuration. But if you want to map the entity into other class, then it needs to be configured within the automapper.

More or less, here is how the PaginatedList class looks like

public class PaginatedList<TResult> : List<TResult>
{
    public int PageIndex { get; }
    public int TotalRecords { get; }
    public int TotalPages { get; }

    public PaginatedList(IEnumerable<TResult> items, int count, int pageIndex, int pageSize)
    {
        PageIndex = pageIndex;
        TotalRecords = count;
        TotalPages = (int)Math.Ceiling(TotalRecords / (double)pageSize);

        AddRange(items);
    }

    public bool HasPreviousPage => PageIndex > 1;

    public bool HasNextPage => PageIndex < TotalPages;

    public static async Task<PaginatedList<TResult>> CreateAsync<TSource>(
        IQueryable<TSource> source, int pageIndex, int pageSize)
    {
        var count = await source.CountAsync();
        var items = await source.Skip((pageIndex - 1) * pageSize)
            .Take(pageSize)
            .Select(ufc => Mapper.Map<TResult>(ufc))
            .ToListAsync();
        return new PaginatedList<TResult>(items, count, pageIndex, pageSize);
    }

    public static async Task<PaginatedList<TResult>> CreateAsync(
        IQueryable<TResult> source, int pageIndex, int pageSize)
    {
        return await CreateAsync<TResult>(source, pageIndex, pageSize);
    }
}
muhihsan
  • 2,250
  • 6
  • 27
  • 42
0

Automapper is perfect for me, but are going to need to create and default constructor on the PaginatedList class

public PaginatedList()
{
    PageIndex = 0;
    TotalRecords = 0;
    TotalPages = 0;
}

-------------------------------- Example

class User
{
    public string Name { get; set; }
}

class UserDto
{
    public string NameDto { get; set; }
}

public class UserProfile: Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(target => target.NameDto, x => x.MapFrom(source => source.Name))
            .ReverseMap();
    }
}

internal class Program
{
   private static void Main(string[] args)
   {
       Mapper.Initialize(config =>
       {
           config.AddProfile<UserProfile>();
       });

       var items = new List<User> { new User { Name = "First name" } };

       var users = new PaginatedList<User>(items, 1, 0, 1);

       var usersDtos = Mapper.Map<PaginatedList<UserDto>>(users);

   }

}