How should I return custom result sets from repository?

I have business models named Product and Orders like below:

public class Product
{
    public int ProductId {get; set;}
    public string Name {get; set;}
}

public class Order
{
    public int OrderId{get; set;}
    public int ProductId {get; set;}
    ...
}

In my repository layer I want to return a collection of products with a count of orders placed against each product, but I can't figure out the correct way or returning custom result sets:

public ?? GetProductsWithOrderCount()
{
    var resultSet = from p in Products
              join o in Orders on p.ProductId equals o.ProductId into grp
              select new
              {
                   Product = p, 
                   OrdersCount = grp.Count(); // Does not work, just for demonstration
              };
    return resultSet;
}

Now I know I can use IEnumerable<object> as the return type, but I have to cast the result set where I use it in my service layer.

Another option is I can create another model and return IEnumerable<ProductWithOrderCount> but this adds unnecessary models that do not represent my system:

public class ProductWithOrderCount
{
    public Product Product {get; set;}
    public int OrdersCount {get; set;}
}

Is there any other way to do this?

Answers


This is what DTOs (Data Transfer Objects) are for, and yes, it's perfectly reasonable to add a class to handle the response. The class you have already, ProductWithOrderCount, is fine, only I would add DTO to the end of the class name to more clearly convey that this is a class intended to house a custom database result.


I had a very similar requirement in my app. It was for paging since my collection of returned objects would be something like 10, 15 or 20 items but the count would be a full database count of something in the hundreds. It seems like in your case though the number returned and the collection count would be the same.

I ended up creating a generic return type and it looked like this, but you could call the class whatever you want, like EnumerableWithCount

public class PagedList<T>
{
    public int TotalCount { get; set; }
    public IEnumerable<T> Items { get; set; }

    public PagedList(IEnumerable<T> collection, int totalCount)
    {
        Items = collection;
        TotalCount = totalCount;
    }
}

If I grasp what you're looking for correctly, you would have a return type that looked something like this.

IEnumerable<EnumerableWithCount<Product>>


Your repository layer is the one where all storage abstraction are hidden. Do not let them leak into your business logic ie. business code should not be able to issue additional queries to the storage directly, even if it looks very convenient. Same applies to the exceptions. If you use some sort of lazy loading you are risking of getting DB level exceptions in the business layer or worse in the presentation. So you need to fully load your object tree and handle all DB/connectivity exceptions there or wrap them up into something like StorageException and throw them up.

If your business logic needs ProductsWithOrders so be it - you need to create another class ProductsWithOrders. You can go fancy and create templated class like Ledger which you can later use like new Ledger but I'd personally wait till you have another pair of classes to justify it.


          from p in Products
          select new ProductWithOrderCount()
          {
               Product = p, 
               OrdersCount = p.Orders.Count(),
          };

Remove the join and use navigation properties.

The special DTO class is the right way to do it.


Need Your Help

Convert SVG to PNG with applied images as background to svg elements

javascript html5 canvas svg snap.svg

I have an external SVG file which contains some embedded image tags in pattern. Whenever I convert this SVG into PNG using toDataURL(), the generated PNG images does not contain the image I have a...

Related lookup field foreign key doesn't work in inline Django

django foreign-keys

i've a problem with my tabularinline field. I have model like this