Как выполнить вставку и обновление для объекта со свойством навигации с помощью Dapper.Rainbow (или, возможно, с помощью Dapper.Contrib)
Я начал изучать Dapper только недавно. Я тестирую его и смог создать базовый CRUD, и я имел в виду базовый, работая над классом с такой структурой:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
}
Теперь я искал что-то, что облегчило бы вставки и обновления, и нашел Dapper.Rainbow. Я проверил это и смог использовать его для получения и вставки объектов, как описано выше. Моя проблема в том, что когда Product
имеет свойство навигации, я не могу сделать вставку в это поле. Так что, если у меня есть это:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
public ProductCategory Category {get;set;}
}
Я не смогу сделать это:
// connection is a valid and opened connection
var db = TestDatabase.Init(connection , 300);
var newId = db.Products.Insert(newProduct);
по этой причине:
The member Category of type ProductCategory cannot be used as a parameter value
Проблема может быть решена, если я заменю Category
с типом int
(тот же тип данных в базе данных). Однако, если я сделаю это, я не смогу запросить товар с информацией о его категории, а не только с идентификатором (категории).
Так что, не прибегая к необработанному Dapper, как я могу сделать вставку и обновление, используя класс со свойством навигации? Я надеялся, что смогу сделать следующее и сказать Dapper.Rainbow игнорировать Category
при вставке или обновлении.
public class Product {
public int Id {get;set;}
public string Name {get;set;}
public ProductCategory Category {get;set;}
public int CategoryId {get;set;} // this will be the same field name in the database
}
Этот сценарий возможен с NHibernate, где я могу иметь прокси-объект Category
и назначить его Product
и сохранить его, и отображение работает отлично. Но я бы с удовольствием использовал Dapper, и поэтому я изучаю и хочу узнать, как такие вещи можно сделать.
1 ответ
Не с Dapper.Rainbow
Это невозможно с Dapper.Rainbow
в его текущем виде, но мой запрос на получение в github делает это возможным.
Я удивлен, что никто не предлагает использовать Dapper.Contrib
, Я знаю, я спросил, есть ли функциональность в Rainbow. Но я не ожидал, что никто не заметит это утверждение (особенно текст, выделенный жирным шрифтом):
Теперь я искал что-то, что облегчило бы вставки и обновления, и нашел Dapper.Rainbow. Я проверил это и смог использовать его для получения и вставки объектов, как описано выше. Моя проблема в том, что, когда у продукта есть свойство навигации, я не могу сделать вставку в это поле.
... и предложить альтернативу, решение, которое уже есть в библиотеке Dapper. Думаю, мне следовало бы прояснить свой вопрос и четко спросить, существует ли решение где-нибудь во всей библиотеке Dapper, которая находится в github. Так что после еще большего поиска в библиотеке я обнаружил, что есть проблема в моей проблеме.
Путь к Dapper.Contrib
Все работает хорошо с моим проектом и Rainbow
пока мне не нужно больше из этого. У меня есть несколько таблиц, в которых много полей. Если я просто кормлю Радугу своим объектом, он будет обновлять все поля, что не очень хорошо. Но это не заставляет меня быстро выпрыгивать из лодки и возвращаться в NH. Поэтому, прежде чем я реализовал свой собственный change tracking
и я не хочу изобретать велосипед, особенно если кто-то уже хорошо поработал, я погуглил и нашел эту ТАКУЮ нить. Эта тема подтвердила мои знания о том, что Rainbow
не поддерживает отслеживание изменений, но есть другой зверь, который делает, и это называется Dapper.Contrib
, И поэтому я начал экспериментировать с этим.
И вот мы снова встречаемся
Категория члена типа ProductCategory не может использоваться в качестве значения параметра
Я получил ту же проблему, что и с Rainbow
, Contrib
не поддерживает свойство навигации!? Я начинаю чувствовать, что трачу свое время на Dapper
и представление, которое он предлагает, к которому я очень стремлюсь, будет просто желанным размышлением. До тех пор...
WriteAttribute, пришедший на помощь...
Этот класс живет в SqlMapperExtensions.cs
файл, который включен в Dapper.Contrib
проект. Я не нашел никакой документации по этому классу, и при этом у него нет никаких комментариев, которые могут сделать это легко найденным и кричать на меня и говорить hey I'm the one you're looking for
, Я наткнулся на это, когда отложил Радугу, как я описал выше.
Использование этого класса такое же, как то, что я сделал с IgnorePropertyAttribute
это атрибут, которым вы можете украсить свойство вашего класса. Этим атрибутом следует украсить любое свойство, которое вы не хотите включать в sql
тот Dapper
создает. Так что в моем примере, чтобы я сказал Dapper исключить Category
поле мне нужно было сделать это:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
[Write(false)] // tell Dapper to exclude this field from the sql
public ProductCategory Category {get;set;}
public int CategoryId {get;set;}
}
Я почти на месте
Помните, что причина, по которой я иду Contrib
из-за change tracking
функциональность. В этой теме, по той же ссылке, что и выше, говорится, что для отслеживания изменений необходимо иметь интерфейс для своего класса и использовать его с Contrib
, Так что для моего примера класса мне нужно иметь:
public interface IProduct {
int Id {get;set;}
string Name {get;set;}
ProductCategory Category {get;set;}
int Category {get;set;}
}
// and implement it on my Product class
public class Product : IProduct {
public int Id {get;set;}
public string Name {get;set;}
[Write(false)]
public ProductCategory Category {get;set;}
int Category {get;set;}
}
Я думал, что это было почти! Вы можете спросить, зачем мне определять Category
в моем интерфейсе, если Dapper
не заботится об этом вообще. Фактически это вызовет только проблему, проблему, которую я затем решу.
В моем конкретном сценарии есть моменты, когда мне нужно работать над Category
поле при сохранении отслеживания изменений для Product
объект. Чтобы поддерживать возможность отслеживания, вызов get должен передаваться с интерфейсом такого типа:
var product = connection.Get<IProduct>(id);
и с этим вызовом я не смогу получить доступ к Category
поле, если я не буду определять его в моем интерфейсе. Но если я определю это в своем интерфейсе, я получу знакомую ошибку
Элемент {member} типа {type} нельзя использовать в качестве значения параметра.
Действительно снова? Сделайте эту остановку, пожалуйста.
Вердикт
Не нужно беспокоиться, так как этот вопрос легко решить, украсив элемент интерфейса так же, как мы сделали для класса. Итак, окончательная конфигурация, чтобы все работало, должна быть:
public interface IProduct {
// I will not discuss here what this attribute does
// as this is documented already in the github source.
// Just take note that this is needed,
// both here and in the implementing class.
[Key]
int Id {get;set;}
string Name {get;set;}
[Write(false)]
ProductCategory Category {get;set;}
int Category {get;set;}
}
// and implement it on my Product class
public class Product : IProduct {
[Key]
public int Id {get;set;}
public string Name {get;set;}
[Write(false)]
public ProductCategory Category {get;set;}
int Category {get;set;}
}
Вы можете использовать этот подход, если вы предпочитаете работать с Contrib
это имеет change tracking
возможность. Если вы хотите работать с Rainbow
и, как и у меня, возникают проблемы со свойством навигации, тогда вы можете поиграть с моим запросом на получение. Это работает так же, как WriteAttribute
только то, что это работает с Rainbow
,
Если вы не любите украшать свои классы атрибутами, тогда оба проекта расширения не для вас. Я знаю, что есть еще один проект расширения, который позволит вам выполнить какую-то конфигурацию беглого типа, но это не относится к (не входит в состав основной части) Dapper
библиотека, которая находится в github. Я предпочитаю работать только с основной библиотекой, побудив меня исследовать всю библиотеку и посмотреть, есть ли уже что-то там или можно ли ее улучшить для удовлетворения моих потребностей. И вот что я сделал и объяснил здесь, для обоихRainbow
а также Contrib
,
Я надеюсь, что этот вклад, очень простой класс, который я добавил, советы по настройке, которые я показал, и сценарий, который приводит меня к ним, помогут в будущем кому-то, кто желает использовать Dapper, и у меня будут аналогичные настройки, которые у меня есть. Кроме того, этот ответ научит разработчиков больше того, что Даппер может и не может делать. Этотзамечательный инструмент под названием Dapper
заслуживает лучшей вики, и я надеюсь, что этот ответ / статья здесь, в SO, поможет даже в малой степени.
** И если то, что я здесь написал, уже где-то написано, что я не нашел за две недели ожидания ответа, то я буду рад, если кто-нибудь свяжет меня с ним. Прошло две недели, и 29 человек, которые изучили мой вопрос, не предложили никаких ссылок или решений, поэтому я предположил, что информация, которой я поделился здесь, является новой для Dapper*:)
ПРИМЕЧАНИЕ. Я изменил название своего вопроса, чтобы другие могли увидеть это потенциальное решение своей проблемы.Новое название основано на новых знаниях, которые я получил о Dapper.
Не ответ, а напоминание... Если вы пришли сюда, пытаясь найти исправление ошибки:
Член {member} типа {type} не может использоваться в качестве значения параметра.
Не забудьте настроить сопоставления, например:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new List<Assembly>()
{
typeof(SomeClassMapConfig).Assembly,
// ...
});
А также...
public class SomeClassMapConfig : ClassMapper<SomeClass>
{
public SomeClassMapConfig()
{
Table("tablename");
Map(p => p.Id).Key(KeyType.Identity).Column("Id");
Map(p => p.SomeProp).Column("SomeField");
// etc..