I'm struggling to get my head around how I should implement dapper in my application. I have a n-tier mvc application and have some experience with EF. Even that I think EF is good, I have not passed the learning curve to make it flow easy and not struggle with performance. In the new project we decided to give dapper a go, mostly to get control over the sql and hopefully get good performance.
Background
I created a layered aplication (core) with these layer
Web - mvc
Service - Business layer to handle the business logic
Data - datalayer to access the ms sql server
I went ahead and started implementing a UnitOfWork and generic Repositories in the datalayer.
A normal structure in the Database would be
Order
ref to User
ref to Address
OrderLine
ref to Product
And in many cases I want to retrieve multiple orders with all lines and products.
So what I did was to have navigation properties on the entity models as you would in EF and populate them with dapper using either multiquery or split the result into different entities and mapping them to the graph.
The problem
The problem I run into is when I do an insert. I have an sqlextension that maps the properties to table columns. But the navigation would also be mapped by default. I realize that I can decorate with attributes and read them on the mapping, but as I google I'm getting aware that maybe I should drop the UnitOfWork pattern and also repository, making the data-layer "super thin" and just expose the connection. Then the service-layer would call the Dapper with correct sql, kind of what I would do today but with repositories.
I would also drop the navigation properties and fetch each entity on it's own, and combine them in the ViewModel.
My problem with this is if we take the order table above I would have to do something like this to get a full list (normally paged, also I removed the User/address)
var listModel = new OrderListViewModel();
var orders = orderService.GetAll();
foreach(var order in orders) {
var orderModel = new OrderViewModel(); // also map fields
var orderLines = orderService.GetOrderLinesForOrder(order.OrderId);
foreach(var orderline in orderLines) {
var orderLineModel = new OrderLineViewModel(); // also map fields
var product = productService.GetProduct(orderline.ProductId);
orderLineModel.Product = new ProductViewModel(); // also map fields
orderModel.OrderLines(orderLineModel);
}
listModel.Orders.Add(orderModel);
}
This will generate ALOT of queries (almost like EF lazy loading). So I could do a mapping thing
var orders = orderService.GetAll();
var orderLines = orderService.GetOrderLinesForOrders(orders.Select(o => o.OrderId).ToArray() ); // get all orderlines for all orders
var products = productService.GetProductsForOrderLines(orderLines.Select(p => p.OrderLineId).ToArray() ); // get all products for all orderlines
foreach(var order in orders) {
var orderModel = new OrderViewModel(); // also map fields
var orderLines = orderLines.Where( o => o.OrderId == order.OrderId );
foreach(var orderline in orderLines) {
var orderLineModel = new OrderLineViewModel(); // also map fields
var product = products.First(p => p.ProductId == orderline.ProductId);
orderLineModel.Product = new ProductViewModel(); // also map fields
orderModel.OrderLines(orderLineModel);
}
listModel.Orders.Add(orderModel);
}
This will generate alot less sql queries and is optimal in performance I think. I know there can be a problem with more than 2100 (?) parameters, but I think that will not be a problem in my case. The problem is that many of out tables have different status, and many relations to other tables. I would have to do alot of these queries all the time.
When I first did repository and navigation I would do it like
repo.Get<Order, OrderLines, Product, Order>(sqlThatWouldJoinAllTables);
// split and map the structure into order Entity and just return that
That way I could just call orderService.GetAll() and retrieve a graph of order, orderlines and products.
I don't know which of the solutions is "best practice". I've tried to find a good open source project using layers and dapper to get some real world usage, but without success. The approach of removing navigation properties also remove some of the purpose of the service layer, since i'm in kind of a way moving some of the business logic to the mvc controller.
I can't find a good practice how I would go forward, please advice.