2
\$\begingroup\$

What do you think about the following approach to CRUD? Here is the reading+pagination example. DTO types have Dto* prefix, EF Entity Types have E* prefix.

DAL example:

class UserQuery : PagingQuery
{
   public int CompanyId { get; set; }
}

interface IUserReader
{
   IPage<DtoUser> Read(UserQuery query);
}

class UserReader : IUserReader
{
    public IPage<DtoUser> Read(UserQuery query) =>
        EUsers()
            .Where(u => u.CompanyId == query.CompanyId)
            .OrderBy(u => u.LastLoginAt)    
            .AsPage(query, u => u.ToDto());      

    IQueryable<EUser> EUsers() =>
       new EContext()
          .Users
          .Include(u => u.Certificates)
          .AsNonTracking();
}

static class UserMapper
{
   public static DtoUser ToDto(this EUser e) =>
      new DtoUser 
      { 
        Id = e.Id, 
        Name = e.Name,
        Certificates = e.Certificates.Select(c => c.ToDto())
      };
} 

static class CertificateMapper
{
   public static DtoCertificate ToDto(this ECertificate e) =>
      new DtoCertificate 
      { 
        Id = e.Id, 
        Name = e.Name 
      };
} 

where library code for pagination is (feel free to borrow if it works for you :)

public class PagingQuery
{
    int _pageSize;
    int _page;

    protected PagingQuery()
    {
        _pageSize = 25;
        _page = 1;
    }

    public int PageSize
    {
        get { return _pageSize; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException(
                   "PageSize must be bigger than 0.");

            if (value > 1000)
                throw new ArgumentOutOfRangeException(
                   "PageSize must be less than 1000.");

            _pageSize = value;
        }
    }

    public int Page
    {
        get { return _page; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException(
                    "Page must be bigger than 0.");

            _page = value;
        }
    }
}

public interface IPage<T> : IEnumerable<T>
{
    Pagination Pagination { get; }
}

class Page<T> : IPage<T>
{
    public Page(Pagination pagination, IEnumerable<T> items)
    {
        Pagination = pagination;
        Items = items;
    }

    public Pagination Pagination { get; }
    IEnumerable<T> Items { get; }
    public IEnumerator<T> GetEnumerator() => Items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public static class Page
{
    public static IPage<TResult> AsPage<T, TResult>(
        this IQueryable<T> source, 
        PagingQuery query, 
        Func<T, TResult> selector) =>
        source
            .AsPage(query)
            .Select(selector);

    public static IPage<T> AsPage<T>(
        this IQueryable<T> source, 
        PagingQuery query) =>
        new Page<T>(
            new Pagination(query, source.Count()),
            source
                 .Skip((query.Page - 1) * query.PageSize)
                 .Take(query.PageSize)
                 .AsEnumerable());

    public static IPage<TResult> Select<T, TResult>(
        this IPage<T> source, 
        Func<T, TResult> selector) =>
        new Page<TResult>(
            source.Pagination,
            source
                .AsEnumerable()
                .Select(selector));

    public static IPage<TResult> ToPage<T, TResult>(
        this IQueryable<T> source, 
        PagingQuery query, 
        Func<T, TResult> selector) =>
        source
            .AsPage(query, selector)
            .Load();

    public static IPage<T> ToPage<T>(
        this IQueryable<T> source, 
        PagingQuery query) =>
        source
            .AsPage(query)
            .Load();

    static IPage<T> Load<T>(this IPage<T> source) =>
        new Page<T>(
            source.Pagination,
            source.ToArray());
}

public class Pagination
{
    public Pagination(PagingQuery query, int total)
    {
        Page = query.Page;
        PageSize = query.PageSize;
        Total = total;
    }

    public int Page { get; set; }
    public int PageSize { get; private set; }
    public int Total { get; private set; }
    public int Pages
    {
        get { return (int)Math.Ceiling((double)Total / (double)PageSize); }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.