Как выполнить вставку и обновление для объекта со свойством навигации с помощью 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..
Другие вопросы по тегам