diff --git a/src/MicroOrm.Dapper.Repositories/DapperRepository.cs b/src/MicroOrm.Dapper.Repositories/DapperRepository.cs index 10924064..bcf62892 100644 --- a/src/MicroOrm.Dapper.Repositories/DapperRepository.cs +++ b/src/MicroOrm.Dapper.Repositories/DapperRepository.cs @@ -615,6 +615,22 @@ public virtual async Task BulkInsertAsync(IEnumerable instances, I return count; } + /// + public bool BulkUpdate(IEnumerable instances, IDbTransaction transaction = null) + { + var queryResult = SqlGenerator.GetBulkUpdate(instances); + var result = Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction) > 0; + return result; + } + + /// + public async Task BulkUpdateAsync(IEnumerable instances, IDbTransaction transaction = null) + { + var queryResult = SqlGenerator.GetBulkUpdate(instances); + var result = await Connection.ExecuteAsync(queryResult.GetSql(), queryResult.Param, transaction) > 0; + return result; + } + /// public virtual bool Delete(TEntity instance, IDbTransaction transaction = null) { diff --git a/src/MicroOrm.Dapper.Repositories/IDapperRepository.cs b/src/MicroOrm.Dapper.Repositories/IDapperRepository.cs index a54240b2..fd75d02c 100644 --- a/src/MicroOrm.Dapper.Repositories/IDapperRepository.cs +++ b/src/MicroOrm.Dapper.Repositories/IDapperRepository.cs @@ -471,6 +471,16 @@ Task> FindAllAsync bool Update(TEntity instance, IDbTransaction transaction = null); + /// + /// Bulk Update objects in DB + /// + Task BulkUpdateAsync(IEnumerable instances, IDbTransaction transaction = null); + + /// + /// Bulk Update objects in DB + /// + bool BulkUpdate(IEnumerable instances, IDbTransaction transaction = null); + /// /// Update object in DB /// diff --git a/src/MicroOrm.Dapper.Repositories/SqlGenerator/ISqlGenerator.cs b/src/MicroOrm.Dapper.Repositories/SqlGenerator/ISqlGenerator.cs index 8c6449e8..fb26e5f9 100644 --- a/src/MicroOrm.Dapper.Repositories/SqlGenerator/ISqlGenerator.cs +++ b/src/MicroOrm.Dapper.Repositories/SqlGenerator/ISqlGenerator.cs @@ -97,6 +97,11 @@ public interface ISqlGenerator where TEntity : class /// SqlQuery GetUpdate(TEntity entity); + /// + /// Get SQL for bulk UPDATE Query + /// + SqlQuery GetBulkUpdate(IEnumerable entities); + /// /// Get SQL for SELECT Query by Id /// diff --git a/src/MicroOrm.Dapper.Repositories/SqlGenerator/SqlGenerator.cs b/src/MicroOrm.Dapper.Repositories/SqlGenerator/SqlGenerator.cs index f2753cd1..08933b0f 100644 --- a/src/MicroOrm.Dapper.Repositories/SqlGenerator/SqlGenerator.cs +++ b/src/MicroOrm.Dapper.Repositories/SqlGenerator/SqlGenerator.cs @@ -219,8 +219,7 @@ public virtual SqlQuery GetBulkInsert(IEnumerable entities) if (!entitiesArray.Any()) throw new ArgumentException("collection is empty"); - var arrayEntities = entitiesArray.ToArray(); - var entityType = arrayEntities[0].GetType(); + var entityType = entitiesArray[0].GetType(); var properties = (IsIdentity ? SqlProperties.Where(p => !p.PropertyName.Equals(IdentitySqlProperty.PropertyName, StringComparison.OrdinalIgnoreCase)) : SqlProperties).ToList(); @@ -229,13 +228,13 @@ public virtual SqlQuery GetBulkInsert(IEnumerable entities) var values = new List(); var parameters = new Dictionary(); - for (var i = 0; i < arrayEntities.Length; i++) + for (var i = 0; i < entitiesArray.Length; i++) { if (HasUpdatedAt) - UpdatedAtProperty.SetValue(arrayEntities[i], DateTime.UtcNow); + UpdatedAtProperty.SetValue(entitiesArray[i], DateTime.UtcNow); foreach (var property in properties) - parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName).GetValue(arrayEntities[i], null)); + parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName).GetValue(entitiesArray[i], null)); values.Add("(" + string.Join(", ", properties.Select(p => "@" + p.PropertyName + i)) + ")"); } @@ -264,6 +263,44 @@ public virtual SqlQuery GetUpdate(TEntity entity) return query; } + /// + public virtual SqlQuery GetBulkUpdate(IEnumerable entities) + { + var entitiesArray = entities as TEntity[] ?? entities.ToArray(); + if (!entitiesArray.Any()) + throw new ArgumentException("collection is empty"); + + var entityType = entitiesArray[0].GetType(); + + var properties = SqlProperties.Where(p => !KeySqlProperties.Any(k => k.PropertyName.Equals(p.PropertyName, StringComparison.OrdinalIgnoreCase)) && !p.IgnoreUpdate); + + var query = new SqlQuery(); + + var parameters = new Dictionary(); + + for (var i = 0; i < entitiesArray.Length; i++) + { + if (HasUpdatedAt) + UpdatedAtProperty.SetValue(entitiesArray[i], DateTime.UtcNow); + + query.SqlBuilder.Append(" UPDATE " + TableName + " SET " + string.Join(", ", properties.Select(p => p.ColumnName + " = @" + p.PropertyName + i)) + " WHERE " + string.Join(" AND ", KeySqlProperties.Select(p => p.ColumnName + " = @" + p.PropertyName + i))); + + foreach (var property in properties) + { + parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName).GetValue(entitiesArray[i], null)); + } + + foreach (var property in KeySqlProperties) + { + parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName).GetValue(entitiesArray[i], null)); + } + } + + query.SetParam(parameters); + + return query; + } + private SqlQuery AppendWhereQuery(SqlQuery sqlQuery, Expression> predicate) { IDictionary dictionary = new Dictionary(); diff --git a/test/MicroOrm.Dapper.Repositories.Tests/Tests/MsSqlRepositoriesTests.cs b/test/MicroOrm.Dapper.Repositories.Tests/Tests/MsSqlRepositoriesTests.cs index 41e0e707..b3196150 100644 --- a/test/MicroOrm.Dapper.Repositories.Tests/Tests/MsSqlRepositoriesTests.cs +++ b/test/MicroOrm.Dapper.Repositories.Tests/Tests/MsSqlRepositoriesTests.cs @@ -434,5 +434,61 @@ public void Delete() Assert.Equal(1, objectsCount); } + + [Fact] + public void BulkUpdate() + { + var phone1 = new Phone { Code = "Kiev123", Number = "Kiev123" }; + var phone2 = new Phone { Code = "Kiev123", Number = "Kiev333" }; + + _sqlDatabaseFixture.Db.Phones.Insert(phone1); + _sqlDatabaseFixture.Db.Phones.Insert(phone2); + + var insertedPhone1 = _sqlDatabaseFixture.Db.Phones.FindById(phone1.Id); + var insertedPhone2 = _sqlDatabaseFixture.Db.Phones.FindById(phone2.Id); + Assert.Equal("Kiev123", phone1.Number); + Assert.Equal("Kiev333", phone2.Number); + + insertedPhone1.Number = "Kiev666"; + insertedPhone2.Number = "Kiev777"; + + bool result = _sqlDatabaseFixture.Db.Phones.BulkUpdate(new List { insertedPhone1, insertedPhone2 }); + + Assert.True(result); + + var newPhone1 = _sqlDatabaseFixture.Db.Phones.FindById(phone1.Id); + var newPhone2 = _sqlDatabaseFixture.Db.Phones.FindById(phone2.Id); + + Assert.Equal("Kiev666", newPhone1.Number); + Assert.Equal("Kiev777", newPhone2.Number); + } + + [Fact] + public async void BulkUpdateAsync() + { + var phone1 = new Phone { Code = "MSK123", Number = "MSK123" }; + var phone2 = new Phone { Code = "MSK123", Number = "MSK333" }; + + await _sqlDatabaseFixture.Db.Phones.InsertAsync(phone1); + await _sqlDatabaseFixture.Db.Phones.InsertAsync(phone2); + + var insertedPhone1 = await _sqlDatabaseFixture.Db.Phones.FindByIdAsync(phone1.Id); + var insertedPhone2 = await _sqlDatabaseFixture.Db.Phones.FindByIdAsync(phone2.Id); + Assert.Equal("MSK123", phone1.Number); + Assert.Equal("MSK333", phone2.Number); + + insertedPhone1.Number = "MSK666"; + insertedPhone2.Number = "MSK777"; + + bool result = await _sqlDatabaseFixture.Db.Phones.BulkUpdateAsync(new List { insertedPhone1, insertedPhone2 }); + + Assert.True(result); + + var newPhone1 = await _sqlDatabaseFixture.Db.Phones.FindByIdAsync(phone1.Id); + var newPhone2 = await _sqlDatabaseFixture.Db.Phones.FindByIdAsync(phone2.Id); + + Assert.Equal("MSK666", newPhone1.Number); + Assert.Equal("MSK777", newPhone2.Number); + } } } \ No newline at end of file diff --git a/test/MicroOrm.Dapper.Repositories.Tests/Tests/SqlGeneratorTests.cs b/test/MicroOrm.Dapper.Repositories.Tests/Tests/SqlGeneratorTests.cs index 02363c76..52592479 100644 --- a/test/MicroOrm.Dapper.Repositories.Tests/Tests/SqlGeneratorTests.cs +++ b/test/MicroOrm.Dapper.Repositories.Tests/Tests/SqlGeneratorTests.cs @@ -368,5 +368,37 @@ public void MsSqlDeleteWithMultiplePredicate() Assert.Equal("DELETE FROM [DAB].[Phones] WHERE [DAB].[Phones].[IsActive] = @IsActive AND [DAB].[Phones].[Number] = @Number", sqlQuery.GetSql()); } + + [Fact] + public void MsSqlBulkUpdate() + { + ISqlGenerator userSqlGenerator = new SqlGenerator(ESqlConnector.MSSQL, true); + List phones = new List + { + new Phone { Id = 10, IsActive = true, Number = "111" }, + new Phone { Id = 10, IsActive = false, Number = "222" } + }; + + var sqlQuery = userSqlGenerator.GetBulkUpdate(phones); + + Assert.Equal(" UPDATE [DAB].[Phones] SET [Number] = @Number0, [IsActive] = @IsActive0 WHERE [Id] = @Id0" + + " UPDATE [DAB].[Phones] SET [Number] = @Number1, [IsActive] = @IsActive1 WHERE [Id] = @Id1", sqlQuery.GetSql()); + } + + [Fact] + public void MySqlBulkUpdate() + { + ISqlGenerator userSqlGenerator = new SqlGenerator(ESqlConnector.MySQL, true); + List phones = new List + { + new Phone { Id = 10, IsActive = true, Number = "111" }, + new Phone { Id = 10, IsActive = false, Number = "222" } + }; + + var sqlQuery = userSqlGenerator.GetBulkUpdate(phones); + + Assert.Equal(" UPDATE `DAB`.`Phones` SET `Number` = @Number0, `IsActive` = @IsActive0 WHERE `Id` = @Id0" + + " UPDATE `DAB`.`Phones` SET `Number` = @Number1, `IsActive` = @IsActive1 WHERE `Id` = @Id1", sqlQuery.GetSql()); + } } } \ No newline at end of file