Wykonywanie wstawek i aktualizacji za pomocą Dapper


195

Interesuje mnie korzystanie z Dappera - ale z tego, co mogę powiedzieć, obsługuje tylko zapytania i wykonywanie. Nie widzę, aby Dapper zawierał sposób wstawiania i aktualizowania obiektów.

Biorąc pod uwagę, że nasz projekt (większość projektów?) Wymaga wstawiania i aktualizacji, jaka jest najlepsza praktyka wykonywania wkładek i aktualizacji obok eleganckiego?

Najlepiej nie musielibyśmy uciekać się do metody budowania parametrów ADO.NET itp.

Najlepszą odpowiedzią, jaką mogę teraz wymyślić, jest użycie LinqToSQL do wstawiania i aktualizacji. Czy jest lepsza odpowiedź?


3
Jest to rozszerzenie Contrib z samego Dapper.NET, którego używam. github.com/StackExchange/dapper-dot-net/tree/master/…
Rajiv

Odpowiedzi:


201

Chcemy zbudować kilka pomocników, nadal decydując się na interfejsy API i czy to będzie podstawowe, czy nie. Postęp : patrz: https://code.google.com/archive/p/dapper-dot-net/issues/6 .

W międzyczasie możesz wykonać następujące czynności

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

Zobacz także mój post na blogu: Ten denerwujący problem z WSTAWIENIEM

Aktualizacja

Jak wskazano w komentarzach, w projekcie Dapper.Contrib dostępnych jest teraz kilka rozszerzeń w postaci tych IDbConnectionmetod rozszerzenia:

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
Cześć Sam, znalazłem twoją SO odpowiedź w google i zastanawiałem się, czy ostatni wiersz kodu powinien zawierać słowo setas, cnn.Execute("update Table SET val = @val where Id = @id", new {val, id = 1});czy też jest specyficzny? Jestem nowy w eleganckim i szukałem przykładu aktualizacji :)
JP Hellemons

1
@JPHellemons Próbowałem tego var updateCat = connection.Execute("UPDATE tCategories SET sCategory = @val WHERE iCategoryID = @id", new { val = "dapper test", id = 23 });i zadziałało. Bez użycia SET otrzymuję błąd składni SQLException w pobliżu sCategory.
Drogo


3
@RosdiKasim Czy to nie pokonuje celu używania Dappera? Chcę używać SQL. To go streszcza. czego mi brakuje?
Johnny

2
@ johnny To tylko klasa pomocnicza ... niektórzy ludzie chcą, aby ich kod był jak najbardziej zwięzły ... nie musisz go używać, jeśli nie chcesz.
Rosdi Kasim

68

Wykonywanie operacji CRUD za pomocą Dappera jest łatwym zadaniem. Wspomniałem poniższe przykłady, które powinny pomóc w operacjach CRUD.

Kod dla C RUD:

Metoda nr 1: Ta metoda jest używana podczas wstawiania wartości z różnych encji.

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
    });
}

Metoda nr 2: Ta metoda jest używana, gdy właściwości encji mają takie same nazwy jak kolumny SQL. Zatem Dapper jest ORM odwzorowuje właściwości encji z pasującymi kolumnami 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);
}

Kod dla 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
    });
}

Kod dla 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
    });
}

Kod dla 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

możesz to zrobić w następujący sposób:

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
Powinieneś użyć, using-statementaby połączenie zostało zamknięte nawet w przypadku wyjątku.
Tim Schmelter,

12
możesz po prostu przekazać clientEntity bezpośrednio zamiast używać anonimowego typu ...
Thomas Levesque

@ThomasLevesque Co przez to rozumiesz? Czy możesz podać mały kod tego, co masz na myśli?
iaacp

4
@iaacp, mam na myśli, że:sqlConnection.Execute(sqlQuery, customerEntity);
Thomas Levesque

1
@ThomasLevesque czy możemy wykonać aktualizację również przy użyciu tego samego wzoru? tj.sqlConnection.Execute(sqlQuery, customerEntity);
Shankar,

16

Korzystanie z Dapper.Contrib jest tak proste:

Wstaw listę:

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

Wstaw pojedynczy:

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

Zaktualizuj listę:

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

Aktualizacja pojedyncza:

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

Źródło: https://github.com/StackExchange/Dapper/tree/master/Dapper.Contrib


1
Używając powyższego, aby wstawić pojedynczy obiekt, możesz odzyskać nowy numer identyfikacyjny i umieścić go z powrotem w swoim modelu ... Ale jak to zrobić, aby wstawić listę obiektów - obiekty na liście nie mają pole tożsamości. Czy musisz iterować listę, a następnie wstawiać je pojedynczo, za każdym razem uzyskując nowy identyfikator?
Harag

1
@harag Jeśli potrzebujesz nowego identyfikatora w innym miejscu, myślę, że musisz to zrobić w ten sposób. Entity Framework obsługuje typy referencyjne, takie jak klasy, bez problemu z wstawkami, ale nie wiem, jak działa Dapper.Contrib, jeśli to byłby twój kąt.
Ogglas,

5
@Ogglas, dzięki. Zauważyłem, że „connection.Insert (myObject)” zaktualizuje właściwość „[key]” „myObject”, jeśli tylko wstawię jeden obiekt, ale jeśli wstawię listę powiedzmy 5 obiektów używających tego samego „connection.Insert (myObjectList)”, a następnie żadna z właściwości [kluczy] nie jest aktualizowana, więc muszę ręcznie wykonać każdy element na liście i wstawić je pojedynczo.
Harag

1
W conn.Update(yourClass)przypadku, gdy niektóre właściwości są puste , UPDATE pola do NULL ? Nie działa. Zaktualizuj pole do NULL . Not partials updates
Kiquenet,

5

Możesz także użyć narzędzia dapper z procedurą składowaną i ogólnym sposobem, dzięki któremu wszystko jest łatwe do zarządzania.

Zdefiniuj swoje połączenie:

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();
    }
}

Utwórz interfejs, aby zdefiniować metody Dappera, których naprawdę potrzebujesz:

 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);
}

Zaimplementuj interfejs:

     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);
                    }
                }
            }

    }

Możesz teraz dzwonić z modelu w zależności od potrzeb:

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.
        }
}

Możesz również przekazać parametry:

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);

        }

Teraz zadzwoń z kontrolerów:

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

Mam nadzieję, że zapobiegnie to powtórzeniu kodu i zapewni bezpieczeństwo;



0

Zamiast używać bibliotek zewnętrznych do operacji zapytań, wolałbym raczej pisać zapytania we własnym zakresie. Ponieważ korzystanie z jakichkolwiek pakietów innych firm odbierałoby główną zaletę korzystania z programu dapper, tj. Elastyczność pisania zapytań.

Teraz występuje problem z pisaniem zapytania wstawiania lub aktualizacji dla całego obiektu. W tym celu można po prostu utworzyć pomocników, takich jak poniżej:

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;
 }

Teraz, po prostu przekazując nazwę kolumn do wstawienia, całe zapytanie zostanie utworzone automatycznie, jak poniżej:

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);

Można również zmodyfikować funkcję, aby zwracała całą instrukcję INSERT, przekazując parametr TableName.

Upewnij się, że nazwy właściwości Class są zgodne z nazwami pól w bazie danych. Wtedy tylko Ty możesz przekazać cały obiekt (jak w naszym przypadku userObj), a wartości zostaną automatycznie zmapowane.

W ten sam sposób możesz również mieć funkcję pomocniczą dla zapytania 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(',', ' ');
  }

I używaj go w następujący sposób:

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);

Chociaż w tych funkcjach pomocniczych również musisz przekazać nazwę pól, które chcesz wstawić lub zaktualizować, ale przynajmniej masz pełną kontrolę nad zapytaniem i możesz również zawierać różne klauzule WHERE, gdy jest to wymagane.

Dzięki tym funkcjom pomocniczym zapisujesz następujące wiersze kodu:

Wstaw zapytanie:

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

W przypadku zapytania o aktualizację:

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

Wydaje się, że istnieje różnica kilku linii kodu, ale jeśli chodzi o wykonywanie operacji wstawiania lub aktualizacji z tabelą zawierającą więcej niż 10 pól, można poczuć różnicę.

Możesz użyć operatora nameof, aby przekazać nazwę pola w funkcji, aby uniknąć literówek

Zamiast:

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

Możesz pisać:

List < string > columns = new List < string > {
nameof(UserEntity.UserName),
nameof(UserEntity.City),
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.