使用(实体框架)EF5 处理批量更新的最佳方法是什么?我有两个我感兴趣的特殊案例:
更新 100 到 100.000 个 Id 之间的列表 (List) 的字段(例如 UpdateDate),该列表是主键。单独调用每个更新似乎开销很大并且需要很长时间。
一次插入许多(也在 100 和 100.000 之间)相同的对象(例如用户)。
有什么好的建议吗?
使用(实体框架)EF5 处理批量更新的最佳方法是什么?我有两个我感兴趣的特殊案例:
更新 100 到 100.000 个 Id 之间的列表 (List) 的字段(例如 UpdateDate),该列表是主键。单独调用每个更新似乎开销很大并且需要很长时间。
一次插入许多(也在 100 和 100.000 之间)相同的对象(例如用户)。
有什么好的建议吗?
我看到以下选项:
1. 最简单的方法 - 手动创建 SQL 请求并通过ObjectContext.ExecuteStoreCommand执行
context.ExecuteStoreCommand("UPDATE TABLE SET FIELD1 = {0} WHERE FIELD2 = {1}", value1, value2);
context.Tasks.Update(
t => t.StatusId == 1,
t => new Task {StatusId = 2});
3. 为 EF 创建自己的扩展。有一篇文章Bulk Delete通过继承ObjectContext类来实现这个目标。值得一看。批量插入/更新可以以相同的方式实现。
您可能不想听到它,但最好的选择是不要将 EF 用于批量操作。要跨记录表更新字段,请在数据库中使用 Update 语句(可能通过映射到 EF 函数的存储过程调用)。您还可以使用 Context.ExecuteStoreQuery 方法向数据库发出更新语句。
对于大量插入,最好的选择是使用批量复制或 SSIS。EF 将要求为插入的每一行单独命中数据库。
应该使用 SqlBulkCopy 类来完成批量插入。请参阅预先存在的 StackOverflow Q&A 关于整合两者:SqlBulkCopy 和实体框架
SqlBulkCopy 比 bcp(批量复制命令行实用程序)甚至 OPEN ROWSET 更加用户友好。
public static bool BulkDelete(string tableName, string columnName, List<object> val)
{
bool ret = true;
var max = 2000;
var pages = Math.Ceiling((double)val.Count / max);
for (int i = 0; i < pages; i++)
{
var count = max;
if (i == pages - 1) { count = val.Count % max; }
var args = val.GetRange(i * max, count);
var cond = string.Join("", args.Select((t, index) => $",@p{index}")).Substring(1);
var sql = $"DELETE FROM {tableName} WHERE {columnName} IN ({cond}) ";
ret &= Db.ExecuteSqlCommand(sql, args.ToArray()) > 0;
}
return ret;
}
我同意公认的答案,即 ef 可能是批量插入的错误技术。但是,我认为值得一看EntityFramework.BulkInsert。
这是我成功完成的工作:
private void BulkUpdate()
{
var oc = ((IObjectContextAdapter)_dbContext).ObjectContext;
var updateQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters.
var updateParams = GetSqlParametersForIQueryable(updateQuery).ToArray();
var updateSql = $@"UPDATE dbo.myTable
SET col1 = x.alias2
FROM dbo.myTable
JOIN ({updateQuery}) x(alias1, alias2) ON x.alias1 = dbo.myTable.Id";
oc.ExecuteStoreCommand(updateSql, updateParams);
}
private void BulkInsert()
{
var oc = ((IObjectContextAdapter)_dbContext).ObjectContext;
var insertQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters.
var insertParams = GetSqlParametersForIQueryable(insertQuery).ToArray();
var insertSql = $@"INSERT INTO dbo.myTable (col1, col2)
SELECT x.alias1, x.alias2
FROM ({insertQuery}) x(alias1, alias2)";
oc.ExecuteStoreCommand(insertSql, insertParams.ToArray());
}
private static IEnumerable<SqlParameter> GetSqlParametersForIQueryable<T>(IQueryable<T> queryable)
{
var objectQuery = GetObjectQueryFromIQueryable(queryable);
return objectQuery.Parameters.Select(x => new SqlParameter(x.Name, x.Value));
}
private static ObjectQuery<T> GetObjectQueryFromIQueryable<T>(IQueryable<T> queryable)
{
var dbQuery = (DbQuery<T>)queryable;
var iqProp = dbQuery.GetType().GetProperty("InternalQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var iq = iqProp.GetValue(dbQuery, null);
var oqProp = iq.GetType().GetProperty("ObjectQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
return (ObjectQuery<T>)oqProp.GetValue(iq, null);
}