[筆記] C# 使用SqlBulkCopy搭配Transaction達成多Table大筆資料寫入

使用情境: 多個Table透過SqlBulkCopy同時批次寫入大筆資料搭配Transaction當某張Table寫入失敗時執行Rollback

程式範例:


void Main()
{
	var members = new List<Member>();

	members.Add(new Member()
	{
		Id = 1,
		UserName = "Allen",
		UserAge = 20
	});

	members.Add(new Member()
	{
		Id = 2,
		UserName = "Alex",
		UserAge = 30
	});

	var articles = new List<Article>();

	articles.Add(new Article()
	{
		Id = 1,
		Title = "標題A",
		Desc = "描述A"
	});

	articles.Add(new Article()
	{
		Id = 1,
		Title = "標題B",
		Desc = "描述B"
	});

    var input = new List<InsertModel>();

	input.Add(new InsertModel() { TableName = "Member", InsertDataList = members.Cast<object>().ToList() });
	input.Add(new InsertModel() { TableName = "Article" , InsertDataList = articles.Cast<object>().ToList()});

	using (var conn = new SqlConnection(connStr))
	{
		conn.Open();

		using (var transaction = conn.BeginTransaction())
		{
			using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction))
			{
				try
				{
					foreach (var item in input)
					{
						DataTable dt = new DataTable();

						dt = item.InsertDataList.ListToDataTable();

						sqlBulkCopy.BatchSize = 1000;
						sqlBulkCopy.BulkCopyTimeout = 300;

						sqlBulkCopy.DestinationTableName = item.TableName;
						sqlBulkCopy.WriteToServer(dt);
					}

					transaction.Commit();
				}
				catch (Exception ex)
				{
					transaction.Rollback();
				}
			}

		}

		conn.Close();
	}
}

// You can define other methods, fields, classes and namespaces here

public static class DataTableTools
{
	public static DataTable ListToDataTable<T>(this List<T> listValue)
	{
		//建立DataTable
		var dt = new DataTable();

		PropertyInfo[] propInfoList = null;

		foreach (var item in listValue)
		{
			//判斷DataTable又沒有定義欄位名稱與型態
			if (dt.Columns.Count == 0)
			{
				//取得本次輸入物件的所有的屬性
				propInfoList = item.GetType().GetProperties();

				//在DataTable中加入欄位的名稱與型別
				foreach (var propItem in propInfoList)
				{
					try
					{
						dt.Columns.Add(propItem.Name, Nullable.GetUnderlyingType(propItem.PropertyType) ?? propItem.PropertyType);
					}
					catch (Exception ex)
					{
						throw;
					}

				}
			}

			//建立新的列
			DataRow dr = dt.NewRow();

			//將資料逐筆加到DataTable
			foreach (var propItem in propInfoList)
			{
				try
				{
					var propValue = propItem.GetValue(item, null);
					propValue = propValue ?? DBNull.Value;
					dr[propItem.Name] = propValue;
				}
				catch (Exception ex)
				{
					throw;
				}

			}

			dt.Rows.Add(dr);
		}

		dt.AcceptChanges();

		return dt;
	}
}

public class InsertModel
{
	public string TableName { get; set; }
	public List<object> InsertDataList { get; set; }
}

public class Member
{
	public int Id { get; set; }

	public string UserName { get; set; }

	public int UserAge { get; set; }
}

public class Article
{
	public int Id { get; set; }

	public string Title { get; set; }

	public string Desc { get; set; }
}