DapperExtensions: Добавить "вставить... обновить дубликат ключа"

Сейчас я использую Dapper + Dapper.Extensions. И да, это просто и круто. Но я столкнулся с проблемой: в Dapper.Extensions есть только команда " Вставить", а не " InsertUpdateOnDUplicateKey". Я хочу добавить такой метод, но не вижу хорошего способа сделать это:

  1. Я хочу сделать этот метод общим, как Вставка
  2. Я не могу получить кэшированный список свойств для определенного типа, потому что я не хочу использовать отражение напрямую для создания сырых SQL

Возможен способ раскошелиться на github, но я хочу сделать это только в моем проекте. Кто-нибудь знает, как его продлить? Я понимаю, что эта функция ("вставить... обновление дубликата ключа") поддерживается только в MySQL. Но я не могу найти точки расширения в DapperExtensions, чтобы добавить эту функцию снаружи.
Обновление: это мой форк https://github.com/MaximTkachenko/Dapper-Extensions/commits/master

2 ответа

Решение

На самом деле я закрыл свой запрос на удаление и удалил свою вилку, потому что:

  1. Я вижу некоторые открытые запросы, созданные в 2014 году
  2. Я нашел способ "внедрить" мой код в Dapper.Extensions.

Напоминаю мою проблему: я хочу создать более общие запросы для Dapper.Extensions. Это означает, что мне нужен доступ к кэшу сопоставления для сущностей, SqlGenerator и т. Д. Так что вот мой путь. Я хочу добавить возможность сделать INSERT .. ОБНОВЛЕНИЕ НА ДУБЛИКОВАННОМ КЛЮЧЕ для MySQL. Я создал метод расширения для ISqlGenerator

   public static class SqlGeneratorExt
    {
        public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap)
        {
            var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity));
            if (!columns.Any())
            {
                throw new ArgumentException("No columns were mapped.");
            }

            var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
            var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
            var valuesSetters = columns.Select(p => string.Format("{0}=VALUES({1})", generator.GetColumnName(classMap, p, false), p.Name));

            string sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2}) ON DUPLICATE KEY UPDATE {3}",
                                       generator.GetTableName(classMap),
                                       columnNames.AppendStrings(),
                                       parameters.AppendStrings(),
                                       valuesSetters.AppendStrings());

            return sql;
        }
    }

Еще один метод расширения для IDapperImplementor

public static class DapperImplementorExt
{
    public static void InsertUpdateOnDuplicateKey<T>(this IDapperImplementor implementor, IDbConnection connection, IEnumerable<T> entities, int? commandTimeout = null) where T : class
    {
        IClassMapper classMap = implementor.SqlGenerator.Configuration.GetMap<T>();
        var properties = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
        string emptyGuidString = Guid.Empty.ToString();

        foreach (var e in entities)
        {
            foreach (var column in properties)
            {
                if (column.KeyType == KeyType.Guid)
                {
                    object value = column.PropertyInfo.GetValue(e, null);
                    string stringValue = value.ToString();
                    if (!string.IsNullOrEmpty(stringValue) && stringValue != emptyGuidString)
                    {
                        continue;
                    }

                    Guid comb = implementor.SqlGenerator.Configuration.GetNextGuid();
                    column.PropertyInfo.SetValue(e, comb, null);
                }
            }
        }

        string sql = implementor.SqlGenerator.InsertUpdateOnDuplicateKey(classMap);

        connection.Execute(sql, entities, null, commandTimeout, CommandType.Text);
    }
}

Теперь я могу создать новый класс, производный от класса Database, чтобы использовать свой собственный sql

public class Db : Database
{
    private readonly IDapperImplementor _dapperIml;

    public Db(IDbConnection connection, ISqlGenerator sqlGenerator) : base(connection, sqlGenerator)
    {
        _dapperIml = new DapperImplementor(sqlGenerator);
    }

    public void InsertUpdateOnDuplicateKey<T>(IEnumerable<T> entities, int? commandTimeout) where T : class
    {
        _dapperIml.InsertUpdateOnDuplicateKey(Connection, entities, commandTimeout);
    }
}

Да, требуется создать еще один экземпляр DapperImplementor, потому что экземпляр DapperImplementor из базового класса является частным:(. Так что теперь я могу использовать свой класс Db для вызова моих собственных общих запросов sql и собственных запросов из Dapper.Extension. Примеры использования Класс базы данных вместо Расширения IDbConnection можно найти здесь.

Этот кусок кода очень помог мне в проектах, связанных с MySQL, я определенно должен вам.

Я занимаюсь разработкой баз данных для MySQL и MS SQL. Я также стараюсь делиться как можно большим количеством кода между моими проектами.

MS SQL не имеет прямого эквивалента для "ON DUPLICATE KEY UPDATE", поэтому ранее я не мог использовать это расширение при работе с MS SQL.

При переносе веб-приложения (которое в значительной степени опирается на эту настройку Dapper.Extensions) с MySQL на MS SQL я наконец решил что-то с этим сделать.

Этот код использует подход "IF EXISTS => UPDATE ELSE INSERT", который в основном делает то же самое, что и "ON DUPLICATE KEY UPDATE" в MySQL.

Обратите внимание: фрагмент предполагает, что вы осуществляете транзакции вне этого метода. В качестве альтернативы вы можете добавить "BEGIN TRAN" в начало и "COMMIT" в конец сгенерированной строки SQL.

public static class SqlGeneratorExt
{
    public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap, bool hasIdentityKeyWithValue = false)
    {
        var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || (p.KeyType == KeyType.Identity && !hasIdentityKeyWithValue))).ToList();
        var keys = columns.Where(c => c.KeyType != KeyType.NotAKey).Select(p => $"{generator.GetColumnName(classMap, p, false)}=@{p.Name}");
        var nonkeycolumns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey).ToList();
        if (!columns.Any())
        {
            throw new ArgumentException("No columns were mapped.");
        }
        var tablename = generator.GetTableName(classMap);
        var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
        var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
        var valuesSetters = nonkeycolumns.Select(p => $"{generator.GetColumnName(classMap, p, false)}=@{p.Name}").ToList();
        var where = keys.AppendStrings(seperator: " and ");
        var sqlbuilder = new StringBuilder();
        sqlbuilder.AppendLine($"IF EXISTS (select * from {tablename} WITH (UPDLOCK, HOLDLOCK) WHERE ({where})) ");
        sqlbuilder.AppendLine(valuesSetters.Any() ? $"UPDATE {tablename} SET {valuesSetters.AppendStrings()} WHERE ({where}) " : "SELECT 0 ");
        sqlbuilder.AppendLine($"ELSE INSERT INTO {tablename} ({columnNames.AppendStrings()}) VALUES ({parameters.AppendStrings()}) ");
        return sqlbuilder.ToString();
    }
}
Другие вопросы по тегам