Skip to content

Commit

Permalink
Merge pull request #1 from tbudurovych/master
Browse files Browse the repository at this point in the history
Add class SimpleSqlBulkUpdate to support bulk updates.
  • Loading branch information
cdemi committed May 11, 2015
2 parents a30e7ad + b16d025 commit 738932a
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 33 deletions.
13 changes: 13 additions & 0 deletions Demo/CreateSampleTable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- sample table schema
CREATE TABLE [dbo].[TableName](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Score] [int] NULL,
[Winner] [varchar](100) NULL,
[CreatedOn] [datetime] NULL,
[IsFinal] [bit] NOT NULL CONSTRAINT [DF_TableName_IsFinal] DEFAULT ((0)),
CONSTRAINT [PK_TableName] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
*/
3 changes: 3 additions & 0 deletions Demo/Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
<Name>Logic</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="CreateSampleTable.sql" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
36 changes: 34 additions & 2 deletions Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,47 @@ private static void Main(string[] args)
{
var rand = new Random();
var randomNumbers = new List<int>();
var connectionString = "Database=MyDataBase;Server=(localdb)\\MSSQLLocalDB";
for (int i = 0; i < 5000; i++)
{
randomNumbers.Add(rand.Next());
}

using (var ssbc = new SimpleSqlBulkCopy("ConnectionString"))
using (var ssbc = new SimpleSqlBulkCopy(connectionString))
{
ssbc.WriteToServer("TableName", randomNumbers.Select(rn => new { Score = rn }));
ssbc.WriteToServer("TableName", randomNumbers.Select(rn => new MyClass { Score = rn }));
}

using (var ssbc = new SimpleSqlBulkUpdate(connectionString))
{
ssbc.BulkUpdate("TableName", // update in this table
new[] {
new MyClass(1, 100, "Norris"),
new MyClass(5, 500, "Willis")
}, // update using this data
"Id", // update when values in this column match
new[] { "Score", "Winner"} // columns to update
);
}
}
}

public class MyClass
{
public MyClass()
{
}

public MyClass(int id, int score, string winner)
{
Winner = winner;
Id = id;
Score = score;
}

public string Winner { get; private set; }
public int Score { get; set; }
public int Id { get; set; }
public bool IsFinal { get; set; }
}
}
30 changes: 30 additions & 0 deletions Logic/Common.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Reflection;

namespace System.Data.SqlClient
{
public class Common
{
public static DataTable GetDataTableFromFields<T>(IEnumerable<T> data, SqlBulkCopy sqlBulkCopy)
{
var dt = new DataTable();
Type listType = typeof (T);
foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
dt.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
sqlBulkCopy.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name);
}

foreach (T value in data)
{
DataRow dr = dt.NewRow();
foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
dr[propertyInfo.Name] = propertyInfo.GetValue(value, null);
}
dt.Rows.Add(dr);
}
return dt;
}
}
}
4 changes: 3 additions & 1 deletion Logic/Logic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ProjectGuid>{535F1A7B-53F8-4323-A1EA-44EFDC57B3FA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>System.Data.SqlClient</RootNamespace>
<RootNamespace>SimpleSqlBulkCopy</RootNamespace>
<AssemblyName>SimpleSqlBulkCopy</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
Expand Down Expand Up @@ -39,7 +39,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SimpleSqlBulkUpdate.cs" />
<Compile Include="SimpleSqlBulkCopy.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Expand Down
32 changes: 2 additions & 30 deletions Logic/SimpleSqlBulkCopy.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace System.Data.SqlClient
Expand Down Expand Up @@ -110,44 +109,17 @@ public void Dispose()
public void WriteToServer<T>(string destinationTableName, IEnumerable<T> data)
{
sqlBulkCopy.DestinationTableName = destinationTableName;
DataTable dt = getDataTableFromFields(data);
DataTable dt = Common.GetDataTableFromFields(data, sqlBulkCopy);

sqlBulkCopy.WriteToServer(dt);
}

public async Task WriteToServerAsync<T>(string destinationTableName, IEnumerable<T> data)
{
sqlBulkCopy.DestinationTableName = destinationTableName;
DataTable dt = getDataTableFromFields(data);
DataTable dt = Common.GetDataTableFromFields(data, sqlBulkCopy);

await sqlBulkCopy.WriteToServerAsync(dt);
}

private DataTable getDataTableFromFields<T>(IEnumerable<T> data)
{
var dt = new DataTable();

Type listType = typeof (T);

foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
dt.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
sqlBulkCopy.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name);
}

foreach (T value in data)
{
DataRow dr = dt.NewRow();

foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
dr[propertyInfo.Name] = propertyInfo.GetValue(value, null);
}

dt.Rows.Add(dr);
}

return dt;
}
}
}
102 changes: 102 additions & 0 deletions Logic/SimpleSqlBulkUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Linq;

namespace System.Data.SqlClient
{
public class SimpleSqlBulkUpdate : IDisposable
{
private readonly SqlConnection _connection;
private readonly SqlTransaction _externalTransaction;
private readonly ConnectionState _initialState = ConnectionState.Closed;

public SimpleSqlBulkUpdate(SqlConnection connection)
{
_connection = connection;
_initialState = connection.State;
}

public SimpleSqlBulkUpdate(SqlConnection connection,
SqlTransaction externalTransaction)
{
_externalTransaction = externalTransaction;
_connection = connection;
_initialState = connection.State;
}

public SimpleSqlBulkUpdate(string connectionString)
{
_connection = new SqlConnection(connectionString);
}

public void Dispose()
{
// recycle connection if was not initially open
if (_initialState != ConnectionState.Open
&&_connection != null)
_connection.Dispose();
}

public void BulkUpdate<T>(string destinationTableName, IEnumerable<T> data,
string columnNameToMatch,
string[] columnNamesToUpdate)
{
bool wasOpen = _connection.State == ConnectionState.Open;
if (!wasOpen)
_connection.Open();
string tempTablename = "#" + destinationTableName + "_" + Guid.NewGuid().ToString("N");
CreateTempTable(destinationTableName, tempTablename);
var dataAsArray = data as T[] ?? data.ToArray();

using (SqlBulkCopy sbc = new SqlBulkCopy(_connection, SqlBulkCopyOptions.KeepIdentity, _externalTransaction))
{
sbc.DestinationTableName = tempTablename;
DataTable dt = Common.GetDataTableFromFields(dataAsArray, sbc);
sbc.WriteToServer(dt);
}
MergeTempAndDestination(destinationTableName, tempTablename, columnNameToMatch, columnNamesToUpdate);
DropTempTable(tempTablename);

if (!wasOpen)
_connection.Close();
}

private void DropTempTable(string tempTablename)
{
var cmdTempTable = _connection.CreateCommand();
cmdTempTable.CommandText = "DROP TABLE " + tempTablename;
cmdTempTable.ExecuteNonQuery();
}

private void MergeTempAndDestination(string destinationTableName, string tempTablename,
string matchingColumn,
string[] columnNamesToUpdate)
{
var updateSql = "";
for (var i = 0; i < columnNamesToUpdate.Length; i++)
{
updateSql += String.Format("Target.[{0}]=Source.[{0}]", columnNamesToUpdate[i]);
if (i < columnNamesToUpdate.Length - 1)
updateSql += ",";
}
var mergeSql = "MERGE INTO " + destinationTableName + " AS Target\r\n" +
"USING " + tempTablename + " AS Source\r\n" +
"ON\r\n" +
"Target." + matchingColumn + " = Source." + matchingColumn + "\r\n" +
"WHEN MATCHED THEN\r\n" +
"UPDATE SET " + updateSql + ";";

var cmdTempTable = _connection.CreateCommand();
cmdTempTable.CommandText = mergeSql;
cmdTempTable.ExecuteNonQuery();
}

private void CreateTempTable(string destinationTableName, string tempTablename)
{
var cmdTempTable = _connection.CreateCommand();
cmdTempTable.CommandText = "SELECT TOP 0 * \r\n" +
"INTO " + tempTablename + "\r\n" +
"FROM " + destinationTableName;
cmdTempTable.ExecuteNonQuery();
}
}
}

0 comments on commit 738932a

Please sign in to comment.