Esecuzione di inserti e aggiornamenti con Dapper


196

Sono interessato all'utilizzo di Dapper, ma da quello che posso dire supporta solo Query ed Execute. Non vedo che Dapper includa un modo per inserire e aggiornare oggetti.

Dato che il nostro progetto (la maggior parte dei progetti?) Deve eseguire inserimenti e aggiornamenti, qual è la migliore pratica per eseguire inserti e aggiornamenti insieme a dapper?

Preferibilmente non dovremmo ricorrere al metodo ADO.NET per la costruzione di parametri, ecc.

La migliore risposta che posso trovare a questo punto è usare LinqToSQL per inserimenti e aggiornamenti. C'è una risposta migliore?


3
C'è questa estensione Contrib da Dapper.NET stessa che uso. github.com/StackExchange/dapper-dot-net/tree/master/…
Rajiv

Risposte:


201

Stiamo cercando di creare alcuni aiutanti, decidendo ancora sulle API e se questo va nel nocciolo o no. Consulta: https://code.google.com/archive/p/dapper-dot-net/issues/6 per i progressi.

Nel frattempo puoi fare quanto segue

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

eccetera

Vedi anche il mio post sul blog: quel fastidioso problema INSERT

Aggiornare

Come sottolineato nei commenti, ora ci sono diverse estensioni disponibili nel progetto Dapper.Contrib sotto forma di questi IDbConnectionmetodi di estensione:

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
Ciao Sam, ho trovato la tua risposta SO con Google e mi chiedevo se l'ultima riga di codice dovesse includere la parola setcome cnn.Execute("update Table SET val = @val where Id = @id", new {val, id = 1});o è specifico questo dapper? Sono nuovo di dapper e stavo cercando un esempio di aggiornamento :)
JP Hellemons,

1
@JPHellemons Ho provato questo var updateCat = connection.Execute("UPDATE tCategories SET sCategory = @val WHERE iCategoryID = @id", new { val = "dapper test", id = 23 });e ha funzionato. Senza l'uso di SET ricevo un errore di sintassi SQLException vicino a sCategory.
Costoso


3
@RosdiKasim Questo non vanifica lo scopo dell'uso di Dapper? Voglio usare SQL. Questo lo riassume. Cosa mi sto perdendo?
Johnny,

2
@johnny È solo una classe di supporto ... alcune persone vogliono che il loro codice sia il più breve possibile ... non devi usarlo se non lo vuoi.
Rosdi Kasim,

69

Eseguire operazioni CRUD usando Dapper è un compito facile. Ho citato gli esempi seguenti che dovrebbero aiutarti nelle operazioni CRUD.

Codice per C RUD:

Metodo n. 1: questo metodo viene utilizzato quando si inseriscono valori da entità diverse.

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

Metodo n. 2: questo metodo viene utilizzato quando le proprietà dell'entità hanno gli stessi nomi delle colonne SQL. Quindi, Dapper essendo un ORM mappa le proprietà dell'entità con le colonne SQL corrispondenti.

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

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

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

Codice per 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

puoi farlo in questo modo:

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
È necessario utilizzare in using-statementmodo che la connessione venga chiusa anche in caso di un'eccezione.
Tim Schmelter,

12
potresti semplicemente passare customerEntity direttamente invece di utilizzare un tipo anonimo ...
Thomas Levesque,

@ThomasLevesque Che cosa intendi con questo? Puoi fornire un piccolo esempio di codice di cosa intendi?
Iacac

4
@iaacp, intendo che:sqlConnection.Execute(sqlQuery, customerEntity);
Thomas Levesque l'

1
@ThomasLevesque possiamo aggiornare anche usando lo stesso modello? cioè,sqlConnection.Execute(sqlQuery, customerEntity);
Shankar

16

Usando Dapper.Contrib è semplice come questo:

Inserisci elenco:

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

Inserisci singolo:

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

Elenco di aggiornamento:

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

Aggiornamento singolo:

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

Fonte: https://github.com/StackExchange/Dapper/tree/master/Dapper.Contrib


1
Usando quanto sopra per inserire un singolo oggetto è possibile ottenere il nuovo numero identificativo e rimetterlo nel modello ... Ma come si fa per inserire un elenco di oggetti - gli oggetti nell'elenco non hanno il campo identità. Devi scorrere l'elenco e quindi inserirli uno alla volta, ottenendo il nuovo ID ogni volta?
Harag,

1
@harag Se hai bisogno del nuovo ID da qualche altra parte, suppongo che tu debba farlo in questo modo. Entity Framework gestisce i tipi di riferimento, come le classi, senza problemi con gli inserti, ma non so come funziona Dapper.Contrib se questo fosse il tuo angolo.
Ogglas,

5
@Ogglas, grazie. Ho notato che "connection.Insert (myObject)" aggiornerà la proprietà "[chiave]" di "myObject" se sto solo inserendo un oggetto, ma se inserisco un elenco di 5 oggetti che usano lo stesso "connection.Insert (myObjectList)" quindi nessuna delle proprietà [keys] viene aggiornata, quindi devo eseguire manualmente ogni elemento nell'elenco e inserirli uno alla volta.
Harag,

1
In conn.Update(yourClass)se alcune proprietà sono nulli , quindi aggiornare i campi a NULL ? Non funziona. Aggiorna campo su NULL . Not partials updates
Kiquenet,

5

Puoi anche usare dapper con una procedura memorizzata e un modo generico con cui tutto facilmente gestibile.

Definisci la tua connessione:

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

Crea un'interfaccia per definire i metodi Dapper di cui hai effettivamente bisogno:

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

Implementa l'interfaccia:

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

    }

Ora puoi chiamare dal modello secondo le tue necessità:

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

Puoi anche passare parametri:

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

        }

Ora chiama dai tuoi controller:

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

Spero che impedisca la ripetizione del codice e fornisca sicurezza;


1

Puoi provare questo:

 string sql = "UPDATE Customer SET City = @City WHERE CustomerId = @CustomerId";             
 conn.Execute(sql, customerEntity);

0

Invece di utilizzare qualsiasi libreria di terze parti per le operazioni di query, preferirei suggerire di scrivere query da solo. Perché l'utilizzo di altri pacchetti di terze parti porterebbe via il vantaggio principale dell'utilizzo di dapper, ovvero la flessibilità di scrivere query.

Ora, c'è un problema con la scrittura della query Inserisci o Aggiorna per l'intero oggetto. Per questo, si può semplicemente creare helper come di seguito:

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

Ora, semplicemente passando il nome delle colonne da inserire, l'intera query verrà creata automaticamente, come di seguito:

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

È inoltre possibile modificare la funzione per restituire l'intera istruzione INSERT passando il parametro TableName.

Assicurarsi che i nomi delle proprietà della classe corrispondano ai nomi dei campi nel database. Quindi solo tu puoi passare l'intero obj (come userObj nel nostro caso) e i valori verranno mappati automaticamente.

Allo stesso modo, puoi avere la funzione di supporto anche per la query 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(',', ' ');
  }

E usalo come:

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

Sebbene anche in queste funzioni di supporto, è necessario passare il nome dei campi che si desidera inserire o aggiornare, ma almeno si ha il pieno controllo sulla query e si possono anche includere diverse clausole WHERE come e quando richiesto.

Attraverso queste funzioni di aiuto, salverai le seguenti righe di codice:

Per inserire query:

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

Per query di aggiornamento:

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

Sembra che ci sia una differenza di poche righe di codice, ma quando si tratta di eseguire operazioni di inserimento o aggiornamento con una tabella con più di 10 campi, si può percepire la differenza.

È possibile utilizzare l'operatore nameof per passare il nome del campo nella funzione per evitare errori di battitura

Invece di:

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

Tu puoi scrivere:

List < string > columns = new List < string > {
nameof(UserEntity.UserName),
nameof(UserEntity.City),
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.