Виконання вставок та оновлень з Dapper


195

Мені цікаво використовувати Dapper - але з того, що я можу сказати, він підтримує лише Query and Execute. Я не бачу, що Dapper включає спосіб вставки та оновлення об'єктів.

Зважаючи на те, що нашому проекту (більшості проектів?) Потрібно робити вставки та оновлення, що є найкращою практикою для вставки та оновлень поряд із Dapper?

Переважно нам не довелося б вдаватися до методу побудови параметрів ADO.NET тощо.

Найкраща відповідь, яку я можу прийти до цього моменту, - це використовувати LinqToSQL для вставок та оновлень. Чи є краща відповідь?


3
Існує це розширення Contrib від самого Dapper.NET, яке я використовую. github.com/StackExchange/dapper-dot-net/tree/master/…
Раджив

Відповіді:


201

Ми розглядаємо питання створення декількох помічників, які все ще вирішують API і якщо це буде в основі чи ні. Дивіться: https://code.google.com/archive/p/dapper-dot-net/isissue/6 для прогресу.

Тим часом ви можете зробити наступне

val = "my value";
cnn.Execute("insert into Table(val) values (@val)", new {val});

cnn.Execute("update Table set val = @val where Id = @id", new {val, id = 1});

etcetera

Дивіться також мій пост у блозі: Ця дратівлива проблема ВСТУП

Оновлення

Як зазначено в коментарях, зараз у проекті Dapper.Contrib доступно кілька розширень у вигляді таких IDbConnectionметодів розширення:

T Get<T>(id);
IEnumerable<T> GetAll<T>();
int Insert<T>(T obj);
int Insert<T>(Enumerable<T> list);
bool Update<T>(T obj);
bool Update<T>(Enumerable<T> list);
bool Delete<T>(T obj);
bool Delete<T>(Enumerable<T> list);
bool DeleteAll<T>();

4
Привіт, Сем, знайшов вашу відповідь на відповідь у google, і мені було цікаво, чи в останньому рядку коду повинно входити слово setяк це cnn.Execute("update Table SET val = @val where Id = @id", new {val, id = 1});чи конкретно цей dapper? Я новачок у Dapper і шукав приклад оновлення :)
JP Hellemons

1
@JPHellemons Я спробував це, var updateCat = connection.Execute("UPDATE tCategories SET sCategory = @val WHERE iCategoryID = @id", new { val = "dapper test", id = 23 });і це спрацювало. Без використання SET я отримую помилку синтаксису SQLException поблизу sCategory.
Прайс

3
Перемотка вперед до грудня 2015 року: github.com/StackExchange/dapper-dot-net/tree/master/…
Росді Касім

3
@RosdiKasim Хіба це не переможе ціль використання Dapper? Я хочу використовувати SQL. Це абстрагує це. Що я пропускаю?
Johnny

2
@johnny Це просто клас помічників ... деякі люди хочуть, щоб їх код був максимально стислим ... вам не потрібно використовувати його, якщо ви цього не хочете.
Росді Касім

68

Виконання операцій CRUD за допомогою Dapper - це легке завдання. Я згадав наведені нижче приклади, які повинні допомогти вам в операціях з CRUD.

Код для C RUD:

Спосіб №1: Цей метод використовується, коли ви вставляєте значення з різних об'єктів.

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDbConnection"].ConnectionString))
{
    string insertQuery = @"INSERT INTO [dbo].[Customer]([FirstName], [LastName], [State], [City], [IsActive], [CreatedOn]) VALUES (@FirstName, @LastName, @State, @City, @IsActive, @CreatedOn)";

    var result = db.Execute(insertQuery, new
    {
        customerModel.FirstName,
        customerModel.LastName,
        StateModel.State,
        CityModel.City,
        isActive,
        CreatedOn = DateTime.Now
    });
}

Метод №2: Цей метод застосовується, коли властивості вашої сутності мають ті ж назви, що і стовпці SQL. Отже, Dapper як ORM відображає властивості сутності з відповідними стовпцями SQL.

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDbConnection"].ConnectionString))
{
    string insertQuery = @"INSERT INTO [dbo].[Customer]([FirstName], [LastName], [State], [City], [IsActive], [CreatedOn]) VALUES (@FirstName, @LastName, @State, @City, @IsActive, @CreatedOn)";

    var result = db.Execute(insertQuery, customerViewModel);
}

Код для C R UD:

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDbConnection"].ConnectionString))
{
    string selectQuery = @"SELECT * FROM [dbo].[Customer] WHERE FirstName = @FirstName";

    var result = db.Query(selectQuery, new
    {
        customerModel.FirstName
    });
}

Код для CR U D:

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDbConnection"].ConnectionString))
{
    string updateQuery = @"UPDATE [dbo].[Customer] SET IsActive = @IsActive WHERE FirstName = @FirstName AND LastName = @LastName";

    var result = db.Execute(updateQuery, new
    {
        isActive,
        customerModel.FirstName,
        customerModel.LastName
    });
}

Код для CRU D :

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDbConnection"].ConnectionString))
{
    string deleteQuery = @"DELETE FROM [dbo].[Customer] WHERE FirstName = @FirstName AND LastName = @LastName";

    var result = db.Execute(deleteQuery, new
    {
        customerModel.FirstName,
        customerModel.LastName
    });
}

26

ви можете зробити це таким чином:

sqlConnection.Open();

string sqlQuery = "INSERT INTO [dbo].[Customer]([FirstName],[LastName],[Address],[City]) VALUES (@FirstName,@LastName,@Address,@City)";
sqlConnection.Execute(sqlQuery,
    new
    {
        customerEntity.FirstName,
        customerEntity.LastName,
        customerEntity.Address,
        customerEntity.City
    });

sqlConnection.Close();

36
Ви повинні використовувати using-statementтак, щоб з'єднання було закрито навіть у випадку винятку.
Тім Шмелтер

12
ви можете просто передати customerEntity безпосередньо замість використання анонімного типу ...
Thomas Levesque

@ThomasLevesque Що ти розумієш під цим? Чи можете ви надати крихітний приклад коду того, що ви маєте на увазі?
iaacp

4
@iaacp, я маю на увазі, що:sqlConnection.Execute(sqlQuery, customerEntity);
Томас Левеск

1
@ThomasLevesque ми можемо зробити оновлення також за тією ж схемою? тобто,sqlConnection.Execute(sqlQuery, customerEntity);
Шанкар

16

Використовувати Dapper.Contrib так само просто:

Вставити список:

public int Insert(IEnumerable<YourClass> yourClass)
{
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        conn.Open();
        return conn.Insert(yourClass) ;
    }
}

Вставити одиночний:

public int Insert(YourClass yourClass)
{
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        conn.Open();
        return conn.Insert(yourClass) ;
    }
}

Оновити список:

public bool Update(IEnumerable<YourClass> yourClass)
{
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        conn.Open();
        return conn.Update(yourClass) ;
    }
}

Оновлення одного:

public bool Update(YourClass yourClass)
{
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        conn.Open();
        return conn.Update(yourClass) ;
    }
}

Джерело: https://github.com/StackExchange/Dapper/tree/master/Dapper.Contrib


1
Використовуючи вищезазначене для вставки одного об’єкта, ви можете вимкнути новий ідентифікаційний номер і повернути його у вашу модель ... Але як це зробити для вставки списку об'єктів - об’єкти у списку не мають поле ідентичності. Чи потрібно повторювати список, а потім вставляти їх по одному, отримуючи новий ідентифікатор кожного разу?
Хараг

1
@harag Якщо вам потрібен новий ідентифікатор десь ще, я думаю, ви повинні зробити це так. Entity Framework обробляє типи посилань, як-от класи, без проблем із вставками, але я не знаю, як Dapper.Contrib працює з цим, якби це був ваш кут.
Оглас

5
@Ogglas, спасибі Я помітив, що "connection.Insert (myObject)" оновить властивість "[key]" myObject ", якщо я просто вставляю один об'єкт, але якщо я вставляю список скажімо, 5 об'єктів, використовуючи той самий "connection.Insert (myObjectList)", тоді жоден з [ключів] властивостей не оновлюється, тому мені доведеться вручну робити параметр foreach в списку і вставляти їх по одному.
Хараг

1
У conn.Update(yourClass)разі деякі властивості дорівнюють нулю , то ОНОВЛЕННЯ поля на NULL ? Не працює. Оновити поле до NULL . Not partials updates
Кікенет

5

Ви також можете використовувати Dapper із збереженою процедурою та загальним способом, завдяки якому все легко керується.

Визначте ваше з'єднання:

public class Connection: IDisposable
{
    private static SqlConnectionStringBuilder ConnectionString(string dbName)
    {
        return new SqlConnectionStringBuilder
            {
                ApplicationName = "Apllication Name",
                DataSource = @"Your source",
                IntegratedSecurity = false,
                InitialCatalog = Database Name,
                Password = "Your Password",
                PersistSecurityInfo = false,
                UserID = "User Id",
                Pooling = true
            };
    }

    protected static IDbConnection LiveConnection(string dbName)
    {
        var connection = OpenConnection(ConnectionString(dbName));
        connection.Open();
        return connection;
    }

    private static IDbConnection OpenConnection(DbConnectionStringBuilder connectionString)
    {
        return new SqlConnection(connectionString.ConnectionString);
    }

    protected static bool CloseConnection(IDbConnection connection)
    {
        if (connection.State != ConnectionState.Closed)
        {
            connection.Close();
            // connection.Dispose();
        }
        return true;
    }

    private static void ClearPool()
    {
        SqlConnection.ClearAllPools();
    }

    public void Dispose()
    {
        ClearPool();
    }
}

Створіть інтерфейс для визначення методів Dapper тих, які вам справді потрібні:

 public interface IDatabaseHub
    {
   long Execute<TModel>(string storedProcedureName, TModel model, string dbName);

        /// <summary>
        /// This method is used to execute the stored procedures with parameter.This is the generic version of the method.
        /// </summary>
        /// <param name="storedProcedureName">This is the type of POCO class that will be returned. For more info, refer to https://msdn.microsoft.com/en-us/library/vstudio/dd456872(v=vs.100).aspx. </param>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="model">The model object containing all the values that passes as Stored Procedure's parameter.</param>
        /// <returns>Returns how many rows have been affected.</returns>
        Task<long> ExecuteAsync<TModel>(string storedProcedureName, TModel model, string dbName);

        /// <summary>
        /// This method is used to execute the stored procedures with parameter. This is the generic version of the method.
        /// </summary>
        /// <param name="storedProcedureName">Stored Procedure's name. Expected to be a Verbatim String, e.g. @"[Schema].[Stored-Procedure-Name]"</param>
        /// <param name="parameters">Parameter required for executing Stored Procedure.</param>        
        /// <returns>Returns how many rows have been affected.</returns>         
        long Execute(string storedProcedureName, DynamicParameters parameters, string dbName);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="storedProcedureName"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        Task<long> ExecuteAsync(string storedProcedureName, DynamicParameters parameters, string dbName);
}

Реалізуйте інтерфейс:

     public class DatabaseHub : Connection, IDatabaseHub
        {

 /// <summary>
        /// This function is used for validating if the Stored Procedure's name is correct.
        /// </summary>
        /// <param name="storedProcedureName">Stored Procedure's name. Expected to be a Verbatim String, e.g. @"[Schema].[Stored-Procedure-Name]"</param>
        /// <returns>Returns true if name is not empty and matches naming patter, otherwise returns false.</returns>

        private static bool IsStoredProcedureNameCorrect(string storedProcedureName)
        {
            if (string.IsNullOrEmpty(storedProcedureName))
            {
                return false;
            }

            if (storedProcedureName.StartsWith("[") && storedProcedureName.EndsWith("]"))
            {
                return Regex.IsMatch(storedProcedureName,
                    @"^[\[]{1}[A-Za-z0-9_]+[\]]{1}[\.]{1}[\[]{1}[A-Za-z0-9_]+[\]]{1}$");
            }
            return Regex.IsMatch(storedProcedureName, @"^[A-Za-z0-9]+[\.]{1}[A-Za-z0-9]+$");
        }

     /// <summary>
            /// This method is used to execute the stored procedures without parameter.
            /// </summary>
            /// <param name="storedProcedureName">Stored Procedure's name. Expected to be a Verbatim String, e.g. @"[Schema].[Stored-Procedure-Name]"</param>
            /// <param name="model">The model object containing all the values that passes as Stored Procedure's parameter.</param>
            /// <typeparam name="TModel">This is the type of POCO class that will be returned. For more info, refer to https://msdn.microsoft.com/en-us/library/vstudio/dd456872(v=vs.100).aspx. </typeparam>
            /// <returns>Returns how many rows have been affected.</returns>

            public long Execute<TModel>(string storedProcedureName, TModel model, string dbName)
            {
                if (!IsStoredProcedureNameCorrect(storedProcedureName))
                {
                    return 0;
                }

                using (var connection = LiveConnection(dbName))
                {
                    try
                    {
                        return connection.Execute(
                            sql: storedProcedureName,
                            param: model,
                            commandTimeout: null,
                            commandType: CommandType.StoredProcedure
                            );

                    }
                    catch (Exception exception)
                    {
                        throw exception;
                    }
                    finally
                    {
                        CloseConnection(connection);
                    }
                }
            }

            public async Task<long> ExecuteAsync<TModel>(string storedProcedureName, TModel model, string dbName)
            {
                if (!IsStoredProcedureNameCorrect(storedProcedureName))
                {
                    return 0;
                }

                using (var connection = LiveConnection(dbName))
                {
                    try
                    {
                        return await connection.ExecuteAsync(
                            sql: storedProcedureName,
                            param: model,
                            commandTimeout: null,
                            commandType: CommandType.StoredProcedure
                            );

                    }
                    catch (Exception exception)
                    {
                        throw exception;
                    }
                    finally
                    {
                        CloseConnection(connection);
                    }
                }
            }

            /// <summary>
            /// This method is used to execute the stored procedures with parameter. This is the generic version of the method.
            /// </summary>
            /// <param name="storedProcedureName">Stored Procedure's name. Expected to be a Verbatim String, e.g. @"[Schema].[Stored-Procedure-Name]"</param>
            /// <param name="parameters">Parameter required for executing Stored Procedure.</param>        
            /// <returns>Returns how many rows have been affected.</returns>

            public long Execute(string storedProcedureName, DynamicParameters parameters, string dbName)
            {
                if (!IsStoredProcedureNameCorrect(storedProcedureName))
                {
                    return 0;
                }

                using (var connection = LiveConnection(dbName))
                {
                    try
                    {
                        return connection.Execute(
                            sql: storedProcedureName,
                            param: parameters,
                            commandTimeout: null,
                            commandType: CommandType.StoredProcedure
                            );
                    }
                    catch (Exception exception)
                    {
                        throw exception;
                    }
                    finally
                    {
                        CloseConnection(connection);
                    }
                }
            }



            public async Task<long> ExecuteAsync(string storedProcedureName, DynamicParameters parameters, string dbName)
            {
                if (!IsStoredProcedureNameCorrect(storedProcedureName))
                {
                    return 0;
                }

                using (var connection = LiveConnection(dbName))
                {
                    try
                    {
                        return await connection.ExecuteAsync(
                            sql: storedProcedureName,
                            param: parameters,
                            commandTimeout: null,
                            commandType: CommandType.StoredProcedure
                            );

                    }
                    catch (Exception exception)
                    {
                        throw exception;
                    }
                    finally
                    {
                        CloseConnection(connection);
                    }
                }
            }

    }

Тепер ви можете зателефонувати з моделі за потребою:

public class DeviceDriverModel : Base
    {
 public class DeviceDriverSaveUpdate
        {
            public string DeviceVehicleId { get; set; }
            public string DeviceId { get; set; }
            public string DriverId { get; set; }
            public string PhoneNo { get; set; }
            public bool IsActive { get; set; }
            public string UserId { get; set; }
            public string HostIP { get; set; }
        }


        public Task<long> DeviceDriver_SaveUpdate(DeviceDriverSaveUpdate obj)
        {

            return DatabaseHub.ExecuteAsync(
                    storedProcedureName: "[dbo].[sp_SaveUpdate_DeviceDriver]", model: obj, dbName: AMSDB);//Database name defined in Base Class.
        }
}

Ви також можете передавати параметри:

public Task<long> DeleteFuelPriceEntryByID(string FuelPriceId, string UserId)
        {


            var parameters = new DynamicParameters();
            parameters.Add(name: "@FuelPriceId", value: FuelPriceId, dbType: DbType.Int32, direction: ParameterDirection.Input);
            parameters.Add(name: "@UserId", value: UserId, dbType: DbType.String, direction: ParameterDirection.Input);

            return DatabaseHub.ExecuteAsync(
                    storedProcedureName: @"[dbo].[sp_Delete_FuelPriceEntryByID]", parameters: parameters, dbName: AMSDB);

        }

Тепер зателефонуйте від своїх контролерів:

var queryData = new DeviceDriverModel().DeviceInfo_Save(obj);

Сподіваємось, це запобіжить повторенню коду та забезпечить безпеку;



0

Замість використання будь-якої сторонньої бібліотеки для операцій із запитами, я б краще запропонувати писати запити самостійно. Оскільки використання будь-яких інших пакетів третьої сторони забирає головну перевагу використання Dapper, тобто гнучкості для запиту запитів.

Тепер виникла проблема із написанням Insert або Update query для всього об’єкта. Для цього можна просто створити помічників, як показано нижче:

InsertQueryBuilder:

 public static string InsertQueryBuilder(IEnumerable < string > fields) {


  StringBuilder columns = new StringBuilder();
  StringBuilder values = new StringBuilder();


  foreach(string columnName in fields) {
   columns.Append($ "{columnName}, ");
   values.Append($ "@{columnName}, ");

  }
  string insertQuery = $ "({ columns.ToString().TrimEnd(',', ' ')}) VALUES ({ values.ToString().TrimEnd(',', ' ')}) ";

  return insertQuery;
 }

Тепер, просто передавши назву стовпців, які потрібно вставити, весь запит буде створений автоматично, як нижче:

List < string > columns = new List < string > {
 "UserName",
 "City"
}
//QueryBuilder is the class having the InsertQueryBuilder()
string insertQueryValues = QueryBuilderUtil.InsertQueryBuilder(columns);

string insertQuery = $ "INSERT INTO UserDetails {insertQueryValues} RETURNING UserId";

Guid insertedId = await _connection.ExecuteScalarAsync < Guid > (insertQuery, userObj);

Ви також можете змінити функцію повернення всього оператора INSERT, передавши параметр TableName.

Переконайтесь, що назви властивостей Class збігаються з іменами полів у базі даних. Тоді тільки ви можете передавати весь obj (як у нашому випадку userObj), і значення будуть автоматично відображатися.

Таким же чином, ви можете мати функцію помічника для UPDATE запиту:

  public static string UpdateQueryBuilder(List < string > fields) {
   StringBuilder updateQueryBuilder = new StringBuilder();

   foreach(string columnName in fields) {
    updateQueryBuilder.AppendFormat("{0}=@{0}, ", columnName);
   }
   return updateQueryBuilder.ToString().TrimEnd(',', ' ');
  }

І використовувати його так:

List < string > columns = new List < string > {
 "UserName",
 "City"
}
//QueryBuilder is the class having the UpdateQueryBuilder()
string updateQueryValues = QueryBuilderUtil.UpdateQueryBuilder(columns);

string updateQuery =  $"UPDATE UserDetails SET {updateQueryValues} WHERE UserId=@UserId";

await _connection.ExecuteAsync(updateQuery, userObj);

Хоча в цих допоміжних функціях також потрібно передати ім’я полів, які ви хочете вставити чи оновити, але принаймні ви маєте повний контроль над запитом, а також можете включати різні пункти WHERE як і коли потрібно.

За допомогою цих допоміжних функцій ви збережете наступні рядки коду:

Для вставки запиту:

 $ "INSERT INTO UserDetails (UserName,City) VALUES (@UserName,@City) RETURNING UserId";

Для оновлення запиту:

$"UPDATE UserDetails SET UserName=@UserName, City=@City WHERE UserId=@UserId";

Здається, є різниця в декількох рядках коду, але коли мова йде про виконання операцій вставки або оновлення з таблицею, що містить більше 10 полів, можна відчути різницю.

Ви можете використовувати оператор nameof, щоб передати ім'я поля у функції, щоб уникнути помилок

Замість:

List < string > columns = new List < string > {
 "UserName",
 "City"
}

Ви можете написати:

List < string > columns = new List < string > {
nameof(UserEntity.UserName),
nameof(UserEntity.City),
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.