Dapper: иерархия сопоставления и одно другое свойство
Мне очень нравится простота и возможности 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()
};
}
Есть много картографов, которые я бы хотел устранить, чтобы предотвратить ненужную работу.
Ответы
Ответ 1
Есть ли чистый способ получить и отобразить заказ объект с такими относительными свойствами, как Vendor, Item, Category и т.д.)
Вы не показываете свой объект Order
, но я возьму ваш OrderItem
в качестве примера и покажу вам, что вам не нужен инструмент сопоставления для конкретной проблемы (как указано). Вы можете получить OrderItem
вместе с информацией Item
и Vendor
каждой из них, выполнив следующие действия:
var sql = @"
select oi.*, i.*, v.*
from OrderItem
inner join Item i on i.Id = oi.ItemId
left join Vendor v on v.Id = oi.VendorId
left join Category c on c.Id = i.CategoryId";
var items = connection.Query<OrderItem, Item, Vendor, Category, OrderItem>(sql,
(oi,i,v,c)=>
{
oi.Item=i;oi.Item.Category=c;oi.Vendor=v;
oi.Vendor.Balance = new Money { Amount = v.Amount, Currency = v.Currency};
return oi;
});
ПРИМЕЧАНИЕ. Использование left join
и его корректировка в зависимости от структуры вашей таблицы.
Ответ 2
Я не уверен, что я понимаю ваш вопрос на 100%. И тот факт, что никто еще не пытался ответить на него, заставляет меня поверить, что я не одинок, когда говорю, что это может быть немного запутанным.
Вы отмечаете, что вам нравится функциональность Dapper, но я не вижу, чтобы вы использовали ее в своих примерах. Разве вы хотите разработать альтернативу Dapper? Или вы не знаете, как использовать Dapper в вашем коде?
В любом случае, здесь ссылка на базу кода Dapper для вашего обзора:
https://github.com/StackExchange/dapper-dot-net
Надеясь, что вы сможете уточнить свои вопросы, я с нетерпением жду вашего ответа.