EntityFramework批量写⼊和修改数据
最近在⼯作中遇到⼀些性能问题,在⼤批量的数据写⼊和修改数据库时太慢了,甚⾄会出现操作超时。
所以去⽹上找了下资料,找到了⼀些解决⽅案SqlBulkCopy和SqlDataAdapter(SqlDataAdapter实测了下,批量修改数据的时候速度不快,可能是我使⽤的姿势不对。哪位⼤神知道正确使⽤姿势,望留⾔指点)。下⾯主要介绍下SqlBulkCopy:
SqlBulkCopy
SqlBulkCopy⽤于做批量写⼊的操作,通过调⽤WriteToServer⽅法来实现批量写⼊的功能(WriteToServer⽅法的实现原理没有深⼊研究)。从测试结果来看,执⾏效率完全可以满⾜公司的需求。
壹.先来说下批量写⼊的具体实现:
1.在调⽤WriteToServer⽅法之前,需要准备⼀个DataTable实例,DataTable的实例中存放的是需要批量写⼊的数据集。
如图,先创建⼀个⽣成DataTable的⽅法:
第⼀个参数,需要批量写⼊的集合,这⾥使⽤的泛型集合,以⽅便共⽤。
第⼆个参数,SqlConnection对象,⽤于连接数据库。
化妆品会过期吗 第三个参数,被写⼊⽬标表的名字,因为是⼀个共⽤的⽅法,不然⽆法知道要向哪张表写数据。
⽅法内部实现:
创建⼀条查询的sql语句,如图:
上⾯图中的sql语句,使⽤来获取⽬标表的字段。通过SqlCommand的ExecuteReader⽅法来获取SqlDataReader的对象。然后在通过获取到的SqlDataReader对象来获取表的列名和对应的类型。
拿到列名和类型后,使⽤DataColumn的有参构造函数创建DataColumn的对象(如:column),将
对象(column)添加到DataTable对象的Columns属性中。如图:
到这⾥,要写⼊⽬标表的所以列都已经添加到DataTable中了(别忘记关闭DataReader,不然运⾏的时候回报错哦O(∩_∩)O),下⾯就可以去处理要批量写⼊到表的集合数据了。
在处理集合数据之前,先利⽤反射技术获取到实体的所有属性。⾸先通过typeof()获取到Type对象,然后通过对象的GetProperties()来获取所有属性的数组,如图:
获取到了实体的所有属性,接下来就可以处理要写⼊的数据集合了。
循环数据集合,对数据进⾏逐条处理。在循环的内部通过循环实体属性的数组对象来获取对应的数据。在根据属性获取数据值的时候,需要进⼀步处理。因为实体属性的数组中很有可能包含导航属性(导航属性:ef中codefirst建表时,建⽴表之间关联关系的属性),如果不处理掉导航属性的话,执⾏语句的时候会出问题。将处理之后的属性和获取到的值添加到DataRow中。如下图:
写到这⾥,DataTable的对象就已经准备好了,要写⼈的数据也已经添加到DataTable的对象中了。
接下来就使⽤SqlBulkCopy来批量将数据写⼊到数据库中。这⼀步很简单,只要创建⼀个SqlBulkCopy的对象,告诉SqlBulkCopy被写⼊表的名字,然后调⽤WriteToServer()将准备好的DataTable对象放到⽅法中就OK了。如下图:
满天飞舞
批量写⼊数据的⽅法到这⾥就完全结束了。最后别忘了将SqlConnection对象关闭。
具体代码如下:
a)批量插⼊数据代码⽚段
///<summary>
///批量插⼊数据
吹不散眉弯///</summary>
///<typeparam name="TModel"></typeparam>
///<param name="modelList">数据集合</param>
///<param name="connectionString">数据库连接字符串</param>
///<param name="tableName">表名</param>
public static void BulkInert<TModel>(IList<TModel> modelList, string connectionString, string tableName)
{
try
{
using (SqlConnection sqlConnect = new SqlConnection(connectionString))
{
DataTable dt = ToSqlBulkCopyDataTable(modelList, sqlConnect, tableName);
SqlBulkCopy sqlBulk = null;
sqlBulk = new SqlBulkCopy(sqlConnect);
using (sqlBulk)
{
sqlBulk.DestinationTableName = tableName;
if (sqlConnect.State != ConnectionState.Open)
{
sqlConnect.Open();
}
sqlBulk.WriteToServer(dt);
}
}
}
catch (Exception ex)
{
throw ex;
}
}
b)⽣成DataTable对象代码⽚段
///<summary>
///⽣成DataTable
///</summary>
///<typeparam name="TModel"></typeparam>
///<param name="modelList">数据集合</param>
/
//<param name="conn">SqlConnection对象</param>
///<param name="tableName">表名</param>
///<returns></returns>
private static DataTable ToSqlBulkCopyDataTable<TModel>(IList<TModel> modelList, SqlConnection conn, string tableName) {
DataTable dt = new DataTable();
#region获取所有表字段
string sql = string.Format("lect top 0 * from {0}", tableName);
if (conn.State != ConnectionState.Open)
{
conn.Open();
日本关东大地震}
SqlCommand command = new SqlCommand();
command.CommandText = sql;
command.CommandType = CommandType.Text;
command.Connection = conn;
SqlDataReader reader = command.ExecuteReader();
try
{
for (int i = 0; i < reader.FieldCount; i++)
{
var re_name = reader.GetName(i);
var re_type = reader.GetFieldType(i);
DataColumn column = new DataColumn(re_name, re_type);
dt.Columns.Add(column);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
{
reader.Clo();
}
}
#endregion
//获取实体
Type mType = typeof(TModel);
var mType_Properts = mType.GetProperties();
foreach (var model in modelList)
{
DataRow dr = dt.NewRow();
foreach (var proper in mType_Properts)
{
string fullName = proper.PropertyType.FullName;
bool isValueType = proper.PropertyType.IsValueType;
bool isClass = proper.PropertyType.IsClass;
bool isEnum = proper.PropertyType.IsEnum;
if ((isValueType || isEnum) && !isClass)
{
object value = proper.GetValue(model);
if (proper.PropertyType.IsEnum)
{
if (value != null)
{
value = (int)value;
}
}
dr[proper.Name] = value ?? DBNull.Value;
}
el if (fullName == "System.String")
{
object value = proper.GetValue(model);
dr[proper.Name] = value ?? DBNull.Value;
}
}
dt.Rows.Add(dr);
}
return dt;
}
到这⾥批量写⼊就结束了O(∩_∩)O
贰.接下来要说的就是批量修改了
为了能更快的更新数据,在批量修改的实现中还需要⽤到SqlBulkCopy。这⾥实现批量修改的操作很简单,只要在批量写⼊的操作之后再执⾏批量修改的操作就⾏了(⽅法⽐较笨^_^)。
龙爪怎么折 下⾯开始正式介绍批量修改的实现:
泽普
1.⾸先要准备⼀个临时表,⽤于保存要修改的数据(注意:临时表的名字不可重复,不然会报错,这⾥要做处理,保证表名的唯⼀性)。
2.使⽤SqlBulkCopy来向临时表 中写⼊数据,以确保数据能快速写⼊到临时表中。
3.同样,使⽤反射技术来获取到修改的实体属性,然后根据获取到的属性来组建update语句,⽤update语句将临时表和被修改的⽬标表关联起来进线批量修改操作。
批量修改的操作就这么简单的⼏个步骤。需要注意的地⽅就是在组建update语句的时候对实体属性的处理,⼀定要将不属于表的导航属性去除掉。不然在执⾏修改的时候会报出错误“XXX列不存在”的提⽰。下⾯之间贴出代码:
a)批量修改代码⽚段
///<summary>
输血不良反应
///批量修改数据
///</summary>
///<param name="modelList"></param>
///<param name="connectionString">数据库连接字符串</param>
///<param name="tableName">表名</param>
///<param name="primaryKey">主键</param>
///<returns></returns>
public static int BulkUpdate<TModel>(IList<TModel> modelList, string connectionString, string tableName, string primaryKey)
{
try
{
Debug.WriteLine("进⼊BulkCopy");
//临时表名使⽤⽇期加随机数
Random ran = new Random();
int ranNumber = ran.Next(1, 10000);
string dateStr = DateTime.Now.ToString("yyyyMMddHHmmss");
string tempName = "#" + tableName + dateStr + ranNumber;
var model = typeof(TModel);
var propers = model.GetProperties();
StringBuilder updateStrBuild = new StringBuilder();
foreach (var item in propers)
{
string fullName = item.PropertyType.FullName;
bool isValueType = item.PropertyType.IsValueType;
bool isClass = item.PropertyType.IsClass;
bool isEnum = item.PropertyType.IsEnum;
if ((isValueType || isEnum) && !isClass)
{
updateStrBuild.Append(" t2." + item.Name + " = t1." + item.Name + ",");
}
el if (fullName == "System.String")
西塞山前白鹭飞下一句{
updateStrBuild.Append(" t2." + item.Name + " = t1." + item.Name + ",");
}
}
string updaSql = updateStrBuild.ToString();
updaSql = updaSql.TrimEnd(',');
Debug.WriteLine("修改语句" + updaSql);
string updateSql = string.Format("update t2 SET {2} FROM {0} AS t1,{1} AS t2 WHERE t1.{3} = t2.{3}", tempName, tableName, updaSql, primaryKey);
Debug.WriteLine(updateSql);
StringBuilder strB = new StringBuilder();
foreach (var item in propers)
{
string fullName = item.PropertyType.FullName;
bool isValueType = item.PropertyType.IsValueType;
bool isClass = item.PropertyType.IsClass;
bool isEnum = item.PropertyType.IsEnum;
if ((isValueType || isEnum) && !isClass)
{
strB.Append("" + item.Name + ", ");
}
el if (fullName == "System.String")
{