Как сделать вставку и обновление для объекта с навигационным свойством с помощью Dapper.Rainbow(или, при необходимости, с помощью Dapper.Contrib)

Я начал изучать Даппера совсем недавно. Я тестирую его и умею делать базовый 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, пока мне не понадобится больше. У меня есть несколько таблиц, в которых есть много полей. Если я просто буду кормить Rainbow моим объектом, тогда он будет делать обновление со всеми полями, это не все хорошо. Но это не заставляет меня быстро выпрыгнуть из лодки и вернуться в NH. Поэтому, прежде чем я внедрил свой собственный change tracking, и я не хочу изобретать велосипед, особенно если кто-то уже сделал хорошую работу, я googled вокруг и нашел этот поток SO. Этот поток подтвердил мои знания о том, что 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. Этот поток SO, та же самая ссылка, которую я дал выше, утверждает, что для отслеживания изменений, которые нужно выполнить, вам нужно иметь интерфейс для своего класса и использовать его с помощью 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. Этот отличный инструмент Dapper заслуживает лучшей вики, и я надеюсь, что этот ответ/статья здесь в SO помогает даже в малой степени.


** И если то, что я написал здесь, уже где-то написано, что я не нашел в течение двух недель, что я ждал ответа, тогда я буду рад, если кто-нибудь свяжет меня с ним. Прошло уже две недели, и 29 человек, которые заглянули в мой вопрос, не предложили никаких ссылок или решений, поэтому я предположил, что информация, которую я разделяю здесь, является новой для Dapper *:)

ПРИМЕЧАНИЕ. Я изменил название вопроса, чтобы другие могли увидеть это потенциальное решение своей проблемы. Новое название основано на новых знаниях, которые я получил о Dapper.

Ответ 2

Не ответ, а напоминание... Если вы пришли сюда, пытаясь найти исправление для ошибки:

Элемент {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..