Мне очень нравится простота и возможности Dapper. Я хотел бы использовать Dapper для решения общих задач, с которыми я сталкиваюсь на повседневной основе. Они описаны ниже.
Вот моя простая модель.
public class OrderItem {
public long Id { get; set; }
public Item Item { get; set; }
public Vendor Vendor { get; set; }
public Money PurchasePrice { get; set; }
public Money SellingPrice { get; set; }
}
public class Item
{
public long Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Title { get; set; }
public long? CategoryId { get; set; }
}
public class Vendor
{
public long Id { get; set; }
public string Title { get; set; }
public Money Balance { get; set; }
public string SyncValue { get; set; }
}
public struct Money
{
public string Currency { get; set; }
public double Amount { get; set; }
}
Две проблемы превзошли меня.
Вопрос 1: Должен ли я всегда создавать DTO с логикой отображения между DTO-Entity в случаях, когда у меня есть одна разность свойств или простое перечисление enum/struct?
Например: существует объект Vendor, который имеет свойство Balance как struct (иначе это может быть Enum). Я не нашел ничего лучше, чем это решение:
public async Task<Vendor> Load(long id) {
const string query = @"
select * from [dbo].[Vendor] where [Id] = @id
";
var row = (await this._db.QueryAsync<LoadVendorRow>(query, new {id})).FirstOrDefault();
if (row == null) {
return null;
}
return row.Map();
}
В этом методе у меня есть 2 служебных кода: 1. Я должен создать LoadVendorRow как объект DTO; 2. Мне нужно написать собственное сопоставление между LoadVendorRow и Vendor:
public static class VendorMapper {
public static Vendor Map(this LoadVendorRow row) {
return new Vendor {
Id = row.Id,
Title = row.Title,
Balance = new Money() {Amount = row.Balance, Currency = "RUR"},
SyncValue = row.SyncValue
};
}
}
Возможно, вы могли бы предположить, что мне нужно хранить сумму и валюту вместе и получать ее как _db.QueryAsync<Vendor, Money, Vendor>(...)
. Возможно, вы правы. В этом случае, что мне делать, если мне нужно сохранить/вернуть свойство Enum (свойство OrderStatus)?
var order = new Order
{
Id = row.Id,
ExternalOrderId = row.ExternalOrderId,
CustomerFullName = row.CustomerFullName,
CustomerAddress = row.CustomerAddress,
CustomerPhone = row.CustomerPhone,
Note = row.Note,
CreatedAtUtc = row.CreatedAtUtc,
DeliveryPrice = row.DeliveryPrice.ToMoney(),
OrderStatus = EnumExtensions.ParseEnum<OrderStatus>(row.OrderStatus)
};
Могу ли я сделать эту работу без моих собственных реализаций и сэкономить время?
Вопрос 2: Что делать, если я хочу восстановить данные для объектов, которые немного сложнее, чем простой однопроцессорный DTO? OrderItem - прекрасный пример. Это метод, который я использую, чтобы восстановить его прямо сейчас:
public async Task<IList<OrderItem>> Load(long orderId) {
const string query = @"
select [oi].*,
[i].*,
[v].*,
[c].*
from [dbo].[OrderItem] [oi]
join [dbo].[Item] [i]
on [oi].[ItemId] = [i].[Id]
join [dbo].[Category] [c]
on [i].[CategoryId] = [c].[Id]
join [dbo].[Vendor] [v]
on [oi].[VendorId] = [v].[Id]
where [oi].[OrderId] = @orderId
";
var rows = (await this._db.QueryAsync<LoadOrderItemRow, LoadItemRow, LoadVendorRow, LoadCategoryRow, OrderItem>(query, this.Map, new { orderId }));
return rows.ToList();
}
Как вы можете видеть, моя проблема вопрос 1 заставляет меня писать настраиваемые mappers и DTO для каждого объекта в иерархии. Что мой картограф:
private OrderItem Map(LoadOrderItemRow row, LoadItemRow item, LoadVendorRow vendor, LoadCategoryRow category) {
return new OrderItem {
Id = row.Id,
Item = item.Map(category),
Vendor = vendor.Map(),
PurchasePrice = row.PurchasePrice.ToMoney(),
SellingPrice = row.SellingPrice.ToMoney()
};
}
Есть много картографов, которые я бы хотел устранить, чтобы предотвратить ненужную работу.