在修剪应用程序以挖掘我面临的性能问题后,我有一个非常基本的数据模型。数据模型存在两个实体,例如callend Product和Category:
class Product {
public virtual int ID {get;set;}
public virtual string Name {get;set;}
public virtual int CategoryID {get;set;}
public virtual Category Category {get;set;}
}
class Category {
public virtual int ID {get;set;}
public virtual string Name {get;set;}
}
当我添加100个新产品时,都使用CategoryID属性将它们的引用设置为相同的类别,并且我调用db.SaveChanges()可能需要相当长的时间。我可以通过将AutoDetectChangesEnabled设置为false来减少所需的时间。但经过几次运行后,我注意到当数据库中的产品数量增加时,插入100种新产品所需的时间会增加:
23416 --> +/- 7000 ms
25516 --> +/- 7500 ms
因为我知道这个表会在生产中变得更大,所以在我回到使用原始Sql查询或BulkInsert之前,我想知道我的选择是什么。
回应Stanley和Raphaël的评论:
在我的测试用例中,ID属性都是主键,CategoryID有索引。没有触发器等。
我正在执行一个循环100次,其中我初始化一个新的DbContext,插入100个产品并处理DbContext。
要明确:我可以创建一个新项目,使用SqlCe设置EF,使用Code-First按照上面的方式构建数据模型,并获得相同的结果。
在进行了几项其他测试之后,似乎问题出在Sql Server Compact Edition上。使用常规的MSSQL服务器,观察到的性能问题已经消失,现在插入100行一直需要大约50ms。我现在似乎找到了问题的根源,它仍然没有回答为什么使用外部查询工具在紧凑型数据库中插入100行时没有与使用EF插入100行时相同的性能问题。
答案 0 :(得分:0)
我之前使用过Entity Framework for ETL,并遇到了内存消耗问题,尽管我在处理我的上下文方面很勤奋。我发现运行垃圾收集作为我的例程的一部分减少了内存消耗,但只是部分。
在使用Entity Framework进行批量插入或更新时,我不知道避免使用n + 1反模式的方法。如果这种加载数百或数千条记录的方案是你可以控制而且很少会做的,你可以试试这个:
using (MyDbContext db = new MyDbContext())
{
// prep your records and add them to your context
// Save the records
db.SaveChanges
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
这对我有用,因为ETL是一次性负载,我们有时间延长它几天。我不会将此类内容推送到Production中,特别是如果您打算允许不受控制的用户或系统有权进行批量插入。
我的团队最终只使用EF进行交易使用,我们有一个单独的数据访问层,用于批量加载,集成和报告。
在尝试与模型驱动的开发保持一致时,我解决此问题的方法是利用SQL Server中的xml数据类型。我准备对象作为ICollection的一部分,将它们序列化为XML,然后将该字符串作为参数发送到存储过程,如下所示:
create procedure [dbo].[BulkAddProducts]
@Values xml = null
as begin
insert into dbo.Products
select
Products.Product.value('*:Name[1]', 'nvarchar(512)') as Name
,Products.Product.value('*:CategoryID[1]', 'int') as CateogryID
from @Values.nodes(N'/*:ArrayOfProduct/*:Product') Products(Product)
end
go
以下是没有proc的查询的工作示例:
declare @Val xml = '
<ArrayOfProduct xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyAppName.Models">
<Product>
<Name>Product1</Name>
<CategoryID>5</CategoryID>
</Product>
<Product>
<Name>Product2</Name>
<CategoryID>5</CategoryID>
</Product>
</ArrayOfProduct>
'
select
Products.Product.value('*:Name[1]', 'nvarchar(512)') as Name
,Products.Product.value('*:CategoryID[1]', 'int') as CateogryID
from @Val.nodes(N'/*:ArrayOfProduct/*:Product') Products(Product)
go