Effectuer des insertions et des mises à jour avec Dapper

206

Je suis intéressé par l'utilisation de Dapper - mais d'après ce que je peux dire, il ne prend en charge que la requête et l'exécution. Je ne vois pas que Dapper inclut un moyen d'insérer et de mettre à jour des objets.

Étant donné que notre projet (la plupart des projets?) Doit faire des insertions et des mises à jour, quelle est la meilleure pratique pour faire des insertions et des mises à jour avec dapper?

De préférence, nous n'aurions pas à recourir à la méthode ADO.NET de construction de paramètres, etc.

La meilleure réponse que je puisse trouver à ce stade est d'utiliser LinqToSQL pour les insertions et les mises à jour. Y a-t-il une meilleure réponse?

Slaggg
la source
3
Il y a cette extension Contrib de Dapper.NET lui-même que j'utilise. github.com/StackExchange/dapper-dot-net/tree/master/…
Rajiv

Réponses:

212

Nous cherchons à créer quelques helpers, en décidant toujours des API et si cela va dans le cœur ou non. Voir: https://code.google.com/archive/p/dapper-dot-net/issues/6 pour connaître la progression.

En attendant, vous pouvez faire ce qui suit

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

etc

Voir aussi mon article de blog: Ce problème INSERT ennuyeux

Mise à jour

Comme indiqué dans les commentaires, il existe désormais plusieurs extensions disponibles dans le projet Dapper.Contrib sous la forme de ces IDbConnectionméthodes d'extension:

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>();
Sam Safran
la source
4
Salut Sam, a trouvé votre réponse SO avec Google et je me demandais si la dernière ligne de code doit inclure le mot setcomme cnn.Execute("update Table SET val = @val where Id = @id", new {val, id = 1});ou est - ce spécifique pimpant? Je suis nouveau chez dapper et je cherchais un exemple de mise à jour :)
JP Hellemons
1
@JPHellemons J'ai essayé cela var updateCat = connection.Execute("UPDATE tCategories SET sCategory = @val WHERE iCategoryID = @id", new { val = "dapper test", id = 23 });et cela a fonctionné. Sans l'utilisation de SET, j'obtiens une erreur de syntaxe SQLException près de sCategory.
Cher
3
Avance rapide jusqu'en décembre 2015: github.com/StackExchange/dapper-dot-net/tree/master/…
Rosdi Kasim
3
@RosdiKasim Cela ne va-t-il pas à l'encontre de l'objectif d'utilisation de Dapper? Je veux utiliser SQL. Cela le résume. Qu'est-ce que je rate?
johnny
2
@johnny C'est juste une classe d'aide ... certaines personnes veulent que leur code soit aussi succinct que possible ... vous n'avez pas à l'utiliser si vous ne le voulez pas.
Rosdi Kasim
73

Effectuer des opérations CRUD à l'aide de Dapper est une tâche facile. J'ai mentionné les exemples ci-dessous qui devraient vous aider dans les opérations CRUD.

Code pour C RUD:

Méthode n ° 1: Cette méthode est utilisée lorsque vous insérez des valeurs provenant de différentes entités.

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

Méthode n ° 2: Cette méthode est utilisée lorsque les propriétés de votre entité ont les mêmes noms que les colonnes SQL. Ainsi, Dapper étant un ORM mappe les propriétés de l'entité avec les colonnes SQL correspondantes.

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

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

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

Code pour 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
    });
}
Reyan Chougle
la source
26

vous pouvez le faire de cette manière:

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();
Dana Addler
la source
36
Vous devez utiliser using-statementpour que la connexion se ferme même en cas d'exception.
Tim Schmelter
13
vous pouvez simplement passer customerEntity directement au lieu d'utiliser un type anonyme ...
Thomas Levesque
@ThomasLevesque Que voulez-vous dire par là? Pouvez-vous fournir un petit exemple de code de ce que vous voulez dire?
iaacp
4
@iaacp, je veux dire que:sqlConnection.Execute(sqlQuery, customerEntity);
Thomas Levesque
1
@ThomasLevesque pouvons-nous faire la mise à jour en utilisant également le même modèle? ie,sqlConnection.Execute(sqlQuery, customerEntity);
Shankar
20

En utilisant Dapper.Contrib, c'est aussi simple que ceci:

Insérer la liste:

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

Insérer un seul:

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

Mettre à jour la liste:

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

Mettre à jour single:

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

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

Ogglas
la source
1
En utilisant ce qui précède pour insérer un seul objet, vous pouvez récupérer le nouveau numéro d'identité et le remettre dans votre modèle ... Mais comment le faire pour insérer une liste d'objets - les objets de la liste n'ont pas le champ d'identité. Devez-vous parcourir la liste, puis les insérer un par un, en obtenant le nouvel identifiant à chaque fois?
Harag le
1
@harag Si vous avez besoin du nouvel identifiant ailleurs, je suppose que vous devez le faire comme ça. Entity Framework gère les types de référence, comme les classes, sans problème avec les insertions mais je ne sais pas comment Dapper.Contrib fonctionne avec cela si c'était votre angle.
Ogglas
5
@Ogglas, merci. J'ai remarqué que "connection.Insert (myObject)" mettra à jour la propriété "[key]" de "myObject" si j'insère juste un objet, mais si j'insère une liste de 5 objets en utilisant le même "connection.Insert (myObjectList)" alors aucune des propriétés [keys] n'est mise à jour, donc je dois faire manuellement pour chaque élément de la liste et les insérer un par un.
Harag
1
Dans le conn.Update(yourClass)cas des propriétés sont nulles , puis mettre à jour les champs à NULL ? Ca ne fonctionne pas. Mettez à jour le champ sur NULL . Not partials updates
Kiquenet
5

Vous pouvez également utiliser dapper avec une procédure stockée et une manière générique par laquelle tout est facilement gérable.

Définissez votre connexion:

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

Créez une interface pour définir les méthodes Dapper dont vous avez réellement besoin:

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

Implémentez l'interface:

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

    }

Vous pouvez maintenant appeler du modèle selon vos besoins:

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

Vous pouvez également passer des paramètres:

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

        }

Appelez maintenant depuis vos contrôleurs:

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

J'espère que cela empêchera la répétition de votre code et assurera la sécurité;

Majedur Rahaman
la source
2

Au lieu d'utiliser une bibliothèque tierce pour les opérations de requête, je suggérerais plutôt d'écrire des requêtes par vous-même. Parce que l'utilisation de tout autre package tiers enlèverait le principal avantage de l'utilisation de dapper, c'est-à-dire la flexibilité d'écrire des requêtes.

Maintenant, il y a un problème avec l'écriture de la requête d'insertion ou de mise à jour pour l'objet entier. Pour cela, on peut simplement créer des helpers comme ci-dessous:

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

Désormais, en passant simplement le nom des colonnes à insérer, toute la requête sera créée automatiquement, comme ci-dessous:

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

Vous pouvez également modifier la fonction pour renvoyer l'instruction INSERT entière en transmettant le paramètre TableName.

Assurez-vous que les noms de propriété de classe correspondent aux noms de champ dans la base de données. Ensuite, vous seul pouvez passer l'obj entier (comme userObj dans notre cas) et les valeurs seront automatiquement mappées.

De la même manière, vous pouvez également avoir la fonction d'assistance pour la requête 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(',', ' ');
  }

Et utilisez-le comme:

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

Bien que dans ces fonctions d'assistance également, vous devez transmettre le nom des champs que vous souhaitez insérer ou mettre à jour, mais au moins vous avez un contrôle total sur la requête et pouvez également inclure différentes clauses WHERE si nécessaire.

Grâce à ces fonctions d'assistance, vous enregistrerez les lignes de code suivantes:

Pour une requête d'insertion:

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

Pour la requête de mise à jour:

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

Il semble y avoir une différence de quelques lignes de code, mais quand il s'agit d'effectuer une opération d'insertion ou de mise à jour avec une table comportant plus de 10 champs, on peut sentir la différence.

Vous pouvez utiliser l'opérateur nameof pour passer le nom du champ dans la fonction afin d'éviter les fautes de frappe

Au lieu de:

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

Tu peux écrire:

List < string > columns = new List < string > {
nameof(UserEntity.UserName),
nameof(UserEntity.City),
}
Ensoleillé
la source
1

Vous pouvez essayer ceci:

 string sql = "UPDATE Customer SET City = @City WHERE CustomerId = @CustomerId";             
 conn.Execute(sql, customerEntity);
Luiz Fernando Corrêa Leite
la source