From 79458d480304e7b1d466541e199a0c922f635d92 Mon Sep 17 00:00:00 2001 From: julien Date: Sat, 10 Mar 2018 01:19:22 +0100 Subject: [PATCH] Fix bug SqlBulkCopy DataReader ordinal - https://github.com/dotnet/corefx/pull/24655 --- .../Database/IDatabaseContext.cs | 3 +- .../Database/ITransactionScope.cs | 11 ++++++ .../Database/DbContextTransactionScope.cs | 30 ++++++++++++++++ .../Database/EFCoreContextAdapter.cs | 7 +++- .../Database/DbTransactionTransactionScope.cs | 34 +++++++++++++++++++ .../Database/ObjectContextDatabaseAdapter.cs | 6 ++-- .../QueryBuilder.EntityFramework.csproj | 1 + .../Bulk/DataReader/BulkCopyDataReaderBase.cs | 18 ++++------ .../Bulk/DataReader/IBulkDataReader.cs | 8 +++-- .../Bulk/SqlBulkCopyExecutor.cs | 13 +++---- 10 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 src/QueryBuilder.Core/Database/ITransactionScope.cs create mode 100644 src/QueryBuilder.EFCore/Database/DbContextTransactionScope.cs create mode 100644 src/QueryBuilder.EntityFramework/Database/DbTransactionTransactionScope.cs diff --git a/src/QueryBuilder.Core/Database/IDatabaseContext.cs b/src/QueryBuilder.Core/Database/IDatabaseContext.cs index 547e8b2..06736fd 100644 --- a/src/QueryBuilder.Core/Database/IDatabaseContext.cs +++ b/src/QueryBuilder.Core/Database/IDatabaseContext.cs @@ -4,8 +4,7 @@ public interface IDatabaseContext where TConnection : class where TTransaction : class { - // TODO move it elsewhere - TTransaction BeginTransaction(); + ITransactionScope BeginTransaction(); TConnection GetConnection(); } diff --git a/src/QueryBuilder.Core/Database/ITransactionScope.cs b/src/QueryBuilder.Core/Database/ITransactionScope.cs new file mode 100644 index 0000000..a2b3da8 --- /dev/null +++ b/src/QueryBuilder.Core/Database/ITransactionScope.cs @@ -0,0 +1,11 @@ +using System; + +namespace QueryBuilder.Core.Database +{ + public interface ITransactionScope : IDisposable + { + TTransaction Current { get; } + + void Commit(); + } +} diff --git a/src/QueryBuilder.EFCore/Database/DbContextTransactionScope.cs b/src/QueryBuilder.EFCore/Database/DbContextTransactionScope.cs new file mode 100644 index 0000000..dbdecc7 --- /dev/null +++ b/src/QueryBuilder.EFCore/Database/DbContextTransactionScope.cs @@ -0,0 +1,30 @@ +using System; +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Storage; +using QueryBuilder.Core.Database; + +namespace QueryBuilder.EFCore.Database +{ + public class DbContextTransactionScope + : ITransactionScope + where TTransaction : DbTransaction + { + private readonly IDbContextTransaction _dbContextTransaction; + public TTransaction Current => (TTransaction)_dbContextTransaction.GetDbTransaction(); + + public DbContextTransactionScope(IDbContextTransaction dbContextTransaction) + { + _dbContextTransaction = dbContextTransaction ?? throw new ArgumentNullException(nameof(dbContextTransaction)); + } + + public void Commit() + { + _dbContextTransaction.Commit(); + } + + public void Dispose() + { + _dbContextTransaction?.Dispose(); + } + } +} diff --git a/src/QueryBuilder.EFCore/Database/EFCoreContextAdapter.cs b/src/QueryBuilder.EFCore/Database/EFCoreContextAdapter.cs index ed7d3f1..05006d5 100644 --- a/src/QueryBuilder.EFCore/Database/EFCoreContextAdapter.cs +++ b/src/QueryBuilder.EFCore/Database/EFCoreContextAdapter.cs @@ -21,7 +21,12 @@ public EFCoreContextAdapter(DatabaseFacade dbFacade) _dbFacade = dbFacade ?? throw new ArgumentNullException(nameof(dbFacade)); } - public TTransaction BeginTransaction() => (TTransaction) _dbFacade.BeginTransaction().GetDbTransaction(); + public ITransactionScope BeginTransaction() { + IDbContextTransaction transaction = _dbFacade.CurrentTransaction + ?? _dbFacade.BeginTransaction(); + + return new DbContextTransactionScope(transaction); + } public int ExecuteCommand(string query, IEnumerable parameters) => _dbFacade.ExecuteSqlCommand(query, parameters); diff --git a/src/QueryBuilder.EntityFramework/Database/DbTransactionTransactionScope.cs b/src/QueryBuilder.EntityFramework/Database/DbTransactionTransactionScope.cs new file mode 100644 index 0000000..426c62a --- /dev/null +++ b/src/QueryBuilder.EntityFramework/Database/DbTransactionTransactionScope.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Entity.Core.EntityClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using QueryBuilder.Core.Database; + +namespace QueryBuilder.EntityFramework.Database +{ + public class DbTransactionTransactionScope + : ITransactionScope + where TTransaction : DbTransaction + { + private readonly DbTransaction _dbTransaction; + public TTransaction Current => (TTransaction)((EntityTransaction)_dbTransaction).StoreTransaction; + + public DbTransactionTransactionScope(DbTransaction dbTransaction) + { + _dbTransaction = dbTransaction ?? throw new ArgumentNullException(nameof(dbTransaction)); + } + + public void Commit() + { + _dbTransaction.Commit(); + } + + public void Dispose() + { + _dbTransaction?.Dispose(); + } + } +} diff --git a/src/QueryBuilder.EntityFramework/Database/ObjectContextDatabaseAdapter.cs b/src/QueryBuilder.EntityFramework/Database/ObjectContextDatabaseAdapter.cs index 782020f..d3459a9 100644 --- a/src/QueryBuilder.EntityFramework/Database/ObjectContextDatabaseAdapter.cs +++ b/src/QueryBuilder.EntityFramework/Database/ObjectContextDatabaseAdapter.cs @@ -20,9 +20,11 @@ public ObjectContextDatabaseAdapter(ObjectContext objectContext) _objectContext = objectContext ?? throw new ArgumentNullException(nameof(objectContext)); } - public TTransaction BeginTransaction() + public ITransactionScope BeginTransaction() { - return ((EntityTransaction)_objectContext.Connection.BeginTransaction()).StoreTransaction as TTransaction; + DbTransaction dbTransaction = _objectContext.Connection.BeginTransaction(); + + return new DbTransactionTransactionScope(dbTransaction); } public TConnection GetConnection() diff --git a/src/QueryBuilder.EntityFramework/QueryBuilder.EntityFramework.csproj b/src/QueryBuilder.EntityFramework/QueryBuilder.EntityFramework.csproj index a25d124..40481f2 100644 --- a/src/QueryBuilder.EntityFramework/QueryBuilder.EntityFramework.csproj +++ b/src/QueryBuilder.EntityFramework/QueryBuilder.EntityFramework.csproj @@ -48,6 +48,7 @@ + diff --git a/src/QueryBuilder.SqlServer/Bulk/DataReader/BulkCopyDataReaderBase.cs b/src/QueryBuilder.SqlServer/Bulk/DataReader/BulkCopyDataReaderBase.cs index ca46f38..b9782ac 100644 --- a/src/QueryBuilder.SqlServer/Bulk/DataReader/BulkCopyDataReaderBase.cs +++ b/src/QueryBuilder.SqlServer/Bulk/DataReader/BulkCopyDataReaderBase.cs @@ -18,26 +18,20 @@ public BulkCopyDataReaderBase(IEnumerable columns) { ThrowHelper.ThrowIfNullOrEmpty(columns, nameof(columns)); - Columns = new ReadOnlyCollection(columns.ToList()); + Columns = columns.Select((value, index) => Tuple.Create(index, value)) + .ToList() + .AsReadOnly(); IsClosed = false; } - public IReadOnlyCollection Columns { get; private set; } + public IReadOnlyCollection> Columns { get; private set; } public int FieldCount => Columns.Count; public int GetOrdinal(string name) { ThrowHelper.ThrowIfNullOrWhiteSpace(name, nameof(name)); - int index = 0; - - using(IEnumerator columnEnumerator = Columns.GetEnumerator()) - { - while (columnEnumerator.MoveNext()) - if (columnEnumerator.Current == name) return index; - else index++; - } - - throw new ArgumentOutOfRangeException(); + Tuple column = Columns.First(c => c.Item2 == name); + return column.Item1; } public bool IsClosed { get; private set; } diff --git a/src/QueryBuilder.SqlServer/Bulk/DataReader/IBulkDataReader.cs b/src/QueryBuilder.SqlServer/Bulk/DataReader/IBulkDataReader.cs index f7a9807..8c663aa 100644 --- a/src/QueryBuilder.SqlServer/Bulk/DataReader/IBulkDataReader.cs +++ b/src/QueryBuilder.SqlServer/Bulk/DataReader/IBulkDataReader.cs @@ -1,10 +1,14 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; namespace QueryBuilder.SqlServer.Bulk.DataReader { public interface IBulkDataReader : IDataReader { - IReadOnlyCollection Columns { get; } + /// + /// The ordinal and name of the columns + /// + IReadOnlyCollection> Columns { get; } } } diff --git a/src/QueryBuilder.SqlServer/Bulk/SqlBulkCopyExecutor.cs b/src/QueryBuilder.SqlServer/Bulk/SqlBulkCopyExecutor.cs index 05563f9..4b0425b 100644 --- a/src/QueryBuilder.SqlServer/Bulk/SqlBulkCopyExecutor.cs +++ b/src/QueryBuilder.SqlServer/Bulk/SqlBulkCopyExecutor.cs @@ -25,10 +25,9 @@ public void Write(string tableName, IBulkDataReader dataReader) if(sqlConn.State != ConnectionState.Open) sqlConn.Open(); - using (SqlTransaction sqlTransaction = _sqlContext.BeginTransaction()) + using (ITransactionScope sqlTransaction = _sqlContext.BeginTransaction()) { - Write(tableName, dataReader, sqlConn, sqlTransaction); - + Write(tableName, dataReader, sqlConn, sqlTransaction.Current); sqlTransaction.Commit(); } } @@ -37,10 +36,12 @@ public virtual void Write(string tableName, IBulkDataReader dataReader, SqlConne { using (var bulkCopy = new SqlBulkCopy(sqlConn, SqlBulkCopyOptions.KeepIdentity, transaction)) { - foreach (string columnName in dataReader.Columns) - // TODO use ordinal when adding mapping instead of column name + foreach (Tuple column in dataReader.Columns) + { + // use ordinal when adding mapping instead of column name // bug will be solved in Standard Library 2.1 https://github.com/dotnet/corefx/pull/24655 - bulkCopy.ColumnMappings.Add(columnName, columnName); + bulkCopy.ColumnMappings.Add(column.Item1, column.Item2); + } bulkCopy.DestinationTableName = tableName; bulkCopy.WriteToServer(dataReader);