C# 如何在.NET中优化代码性能
我有一个导出作业,将数据从旧数据库迁移到新数据库。我面临的问题是,用户数量约为300万,完成这项工作需要很长时间(15个多小时)。我正在使用的机器只有一个处理器,所以我不确定C# 如何在.NET中优化代码性能,c#,multithreading,entity-framework,optimization,C#,Multithreading,Entity Framework,Optimization,我有一个导出作业,将数据从旧数据库迁移到新数据库。我面临的问题是,用户数量约为300万,完成这项工作需要很长时间(15个多小时)。我正在使用的机器只有一个处理器,所以我不确定线程化是否是我应该做的。有人能帮我优化这个代码吗 static void ExportFromLegacy() { var usersQuery = _oldDb.users.Where(x => x.status == 'active'); int BatchSize = 1000;
线程化
是否是我应该做的。有人能帮我优化这个代码吗
static void ExportFromLegacy()
{
var usersQuery = _oldDb.users.Where(x =>
x.status == 'active');
int BatchSize = 1000;
var errorCount = 0;
var successCount = 0;
var batchCount = 0;
// Using MoreLinq's Batch for sequences
// https://www.nuget.org/packages/MoreLinq.Source.MoreEnumerable.Batch
foreach (IEnumerable<users> batch in usersQuery.Batch(BatchSize))
{
Console.WriteLine(String.Format("Batch count at {0}", batchCount));
batchCount++;
foreach(var user in batch)
{
try
{
var userData = _oldDb.userData.Where(x =>
x.user_id == user.user_id).ToList();
if (userData.Count > 0)
{
// Insert into table
var newData = new newData()
{
UserId = user.user_id; // shortened code for brevity.
};
_db.newUserData.Add(newData);
_db.SaveChanges();
// Insert item(s) into table
foreach (var item in userData.items)
{
if (!_db.userDataItems.Any(x => x.id == item.id)
{
var item = new Item()
{
UserId = user.user_id, // shortened code for brevity.
DataId = newData.id // id from object created above
};
_db.userDataItems.Add(item);
}
_db.SaveChanges();
successCount++;
}
}
}
catch(Exception ex)
{
errorCount++;
Console.WriteLine(String.Format("Error saving changes for user_id: {0} at {1}.", user.user_id.ToString(), DateTime.Now));
Console.WriteLine("Message: " + ex.Message);
Console.WriteLine("InnerException: " + ex.InnerException);
}
}
}
Console.WriteLine(String.Format("End at {0}...", DateTime.Now));
Console.WriteLine(String.Format("Successful imports: {0} | Errors: {1}", successCount, errorCount));
Console.WriteLine(String.Format("Total running time: {0}", (exportStart - DateTime.Now).ToString(@"hh\:mm\:ss")));
}
静态void exportfromleagacy()
{
var usersQuery=\u oldDb.users.Where(x=>
x、 状态==“活动”);
int BatchSize=1000;
var errorCount=0;
var successCount=0;
var-batchCount=0;
//使用MoreLinq批处理序列
// https://www.nuget.org/packages/MoreLinq.Source.MoreEnumerable.Batch
foreach(usersqery.batch(BatchSize)中的IEnumerable批处理)
{
WriteLine(String.Format(“在{0}处的批计数”,batchCount));
batchCount++;
foreach(批处理中的var用户)
{
尝试
{
var userData=\u oldDb.userData.Where(x=>
x、 user\u id==user.user\u id).ToList();
如果(userData.Count>0)
{
//插入表格
var newData=newnewdata()
{
UserId=user.user\u id;//为简洁起见,缩短了代码。
};
_db.newUserData.Add(newData);
_db.SaveChanges();
//将项目插入表中
foreach(userData.items中的var项)
{
if(!\u db.userDataItems.Any(x=>x.id==item.id)
{
变量项=新项()
{
UserId=user.user\u id,//为简洁起见,缩短了代码。
DataId=newData.id//id来自上面创建的对象
};
_db.userDataItems.Add(item);
}
_db.SaveChanges();
successCount++;
}
}
}
捕获(例外情况除外)
{
errorCount++;
WriteLine(String.Format(“在{1}保存用户{0}的更改时出错)”,user.user.id.ToString(),DateTime.Now);
Console.WriteLine(“消息:+ex.Message”);
Console.WriteLine(“InnerException:+ex.InnerException”);
}
}
}
WriteLine(String.Format(“结束于{0}…”,DateTime.Now));
WriteLine(String.Format(“成功导入:{0}|错误:{1}”,成功计数,错误计数));
WriteLine(String.Format(“总运行时间:{0}”,(exportStart-DateTime.Now).ToString(@“hh\:mm\:ss”));
}
实体框架是导入大量数据的一个非常糟糕的选择。我从个人经验中知道这一点
也就是说,当我尝试以与您相同的方式使用它时,我发现了一些优化方法
Context
将在添加对象时缓存对象,插入次数越多,以后的插入速度就越慢。我的解决方案是在处理该实例并创建新实例之前,将每个上下文限制在500次左右。这大大提高了性能
我可以使用多个线程来提高性能,但您必须非常小心资源争用。每个线程都肯定需要自己的上下文
,甚至不要考虑尝试在线程之间共享它。我的机器有8个内核,因此线程可能对您没有多大帮助;只有一个内核我怀疑这对你有什么帮助
使用AutoDetectChangesEnabled=false;
关闭更改跟踪,更改跟踪速度非常慢。不幸的是,这意味着您必须修改代码,以便直接通过上下文进行所有更改。不再使用Entity.Property=“Some Value”
,它将变成context.Entity(e=>e.Property).SetValue(“Some Value”);
(或者类似的东西,我不记得确切的语法),这使得代码很难看
任何查询都应该使用AsNoTracking
尽管如此,我还是能够将大约20小时的过程缩短到6小时左右,但我仍然不推荐使用EF。这是一个非常痛苦的项目,因为我几乎完全没有选择EF来添加数据。请使用其他东西…任何其他东西
我不想给人留下这样的印象,EF是一个糟糕的数据访问库,它在设计上非常出色,不幸的是,这不是它的设计目的。实体框架对于导入大量数据来说是一个非常糟糕的选择。我从个人经验中知道这一点 也就是说,当我尝试以与您相同的方式使用它时,我发现了一些优化方法
Context
将在添加对象时缓存对象,插入次数越多,以后的插入速度就越慢。我的解决方案是在处理该实例并创建新实例之前,将每个上下文限制在500次左右。这大大提高了性能
我可以使用多个线程来提高性能,但您必须非常小心资源争用。每个线程都肯定需要自己的上下文
,甚至不要考虑尝试在线程之间共享它。我的机器有8个内核,因此线程可能对您没有多大帮助;只有一个内核我怀疑这对你有什么帮助
使用AutoDetectChangesEnabled=false;
关闭变更跟踪,变更跟踪速度非常慢。不幸的是,这意味着您需要
foreach (...){
}
successCount += _db.SaveChanges();
List<ObjClass> list = new List<ObjClass>();
foreach (...)
{
list.Add(new ObjClass() { ... });
}
_db.newUserData.AddRange(list);
successCount += _db.SaveChanges();
List<ObjClass> list = new List<ObjClass>();
int cnt=0;
foreach (...)
{
list.Add(new ObjClass() { ... });
if (++cnt % 100 == 0) // bunches of 100
{
_db.newUserData.AddRange(list);
successCount += _db.SaveChanges();
list.Clear();
// Optional if a HUGE amount of data
if (cnt % 1000 == 0)
{
_db = new MyDbContext();
}
}
}
// Don't forget that!
_db.newUserData.AddRange(list);
successCount += _db.SaveChanges();
list.Clear();
var list = batch.Select(x => x.user_id).ToList();
var userDatas = _oldDb.userData
.AsNoTracking()
.Where(x => list.Contains(x.user_id))
.ToList();
foreach(var userData in userDatas)
{
....
}
foreach (IEnumerable<users> batch in usersQuery.Batch(BatchSize))
{
// Retrieve all users for the batch at once.
var list = batch.Select(x => x.user_id).ToList();
var userDatas = _oldDb.userData
.AsNoTracking()
.Where(x => list.Contains(x.user_id))
.ToList();
// Create list used for BulkInsert
var newDatas = new List<newData>();
var newDataItems = new List<Item();
foreach(var userData in userDatas)
{
// newDatas.Add(newData);
// newDataItem.OwnerData = newData;
// newDataItems.Add(newDataItem);
}
_db.BulkInsert(newDatas);
_db.BulkInsert(newDataItems);
}
public class UserData
{
public int UserDataID { get; set; }
// ... properties ...
public List<UserDataItem> Items { get; set; }
}
public class UserDataItem
{
public int UserDataItemID { get; set; }
// ... properties ...
public UserData OwnerData { get; set; }
}
var userData = new UserData();
var userDataItem = new UserDataItem();
// Use navigation property to set the parent.
userDataItem.OwnerData = userData;
foreach (IEnumerable<users> batch in usersQuery.Batch(BatchSize))
{
// Retrieve all users for the batch at once.
var list = batch.Select(x => x.user_id).ToList();
var userDatas = _oldDb.userData
.AsNoTracking()
.Where(x => list.Contains(x.user_id))
.ToList();
// Create list used for BulkInsert
var newDatas = new List<newData>();
var newDataItems = new List<Item();
foreach(var userData in userDatas)
{
// newDatas.Add(newData);
// newDataItem.OwnerData = newData;
// newDataItems.Add(newDataItem);
}
var context = new UserContext();
context.userDatas.AddRange(newDatas);
context.userDataItems.AddRange(newDataItems);
context.BulkSaveChanges();
}