我有一张桌子如下图所示。它有固定和储蓄类型的帐户。我需要更新用户1的所有帐户的状态。该用户有10000个帐户。本质上,逻辑将如以下SQL存储过程脚本中所示。该脚本执行时间不到1秒(83毫秒)。
但是当我使用 LINQ to SQL 将其转换为ORM时,它需要超过3分钟(204814毫秒)。它至少 240,000%更慢。
LINQ to SQL(或其他ORM)中是否存在有助于克服性能损失的模式?
什么可以强制它在一次数据库中进行更新?
注意:我知道从LINQ调用存储过程。我不认为这是ORM而不是我的选择。
手动存储过程脚本
DECLARE @UserID INT
DECLARE @StatusForFixed VARCHAR(50)
DECLARE @StatusForSavings VARCHAR(50)
SET @UserID = 1
SET @StatusForFixed = 'FrozenFA11'
SET @StatusForSavings = 'FrozenSB22'
UPDATE BankAccount
SET Status =
CASE
WHEN BankAccount.AccountType='Fixed' THEN @StatusForFixed
WHEN BankAccount.AccountType='Savings' THEN @StatusForSavings
END
WHERE AccountOwnerID=@UserID
LINQ生成的代码示例
Note: This type of statements happen 10000 times
UPDATE [dbo].[BankAccount]
SET [Status] = @p3
WHERE [BankAccountID] = @p0
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [3585]
-- @p3: Input NChar (Size = 10; Prec = 0; Scale = 0) [FrozenSB]
应用ORM后的代码
public class BankAccountAppService
{
public RepositoryLayer.ILijosBankRepository AccountRepository { get; set; }
public void FreezeAllAccountsForUser(int userId)
{
IEnumerable<DBML_Project.BankAccount> accounts = AccountRepository.GetAllAccountsForUser(userId);
foreach (DBML_Project.BankAccount acc in accounts)
{
acc.Freeze();
}
AccountRepository.UpdateAccount();
}
}
public class LijosSimpleBankRepository : ILijosBankRepository
{
public System.Data.Linq.DataContext Context
{
get;
set;
}
public List<DBML_Project.BankAccount> GetAllAccountsForUser(int userID)
{
IQueryable<DBML_Project.BankAccount> queryResultEntities = Context.GetTable<DBML_Project.BankAccount>().Where(p => p.AccountOwnerID == userID);
return queryResultEntities.ToList();
}
public List<T> GetAllAccountsofType<T>() where T : DBML_Project.BankAccount
{
var query = from p in Context.GetTable<DBML_Project.BankAccount>().OfType<T>()
select p;
List<T> typeList = query.ToList();
return typeList;
}
public virtual void UpdateAccount()
{
Context.SubmitChanges();
}
}
namespace DBML_Project
{
public partial class BankAccount
{
//Define the domain behaviors
public virtual void Freeze()
{
//Do nothing
}
}
public class FixedBankAccount : BankAccount
{
public override void Freeze()
{
this.Status = "FrozenFA";
}
}
public class SavingsBankAccount : BankAccount
{
public override void Freeze()
{
this.Status = "FrozenSB";
}
}
}
参考
答案 0 :(得分:6)
您正在比较两种截然不同的场景:
1:在SQL服务器上本地运行脚本,一个基于集合的UPDATE
2:通过网络获取10,000条记录,更新每条记录,单独提交
您可以通过将SubmitChanges()
推迟到10,000个而不是10,000个批次的1个批次中来提高2个位(只是:不要调用SubmitChanges()
直到但是仍然涉及向两个方向发送10,000条记录的详细信息,加上所有开销(例如,SubmitChanges()
可能仍选择这样做通过10,000个人电话)。
基本上,基于对象的工具不适用于对记录进行批量更新。如果SP有效,请使用SP。也许通过数据上下文调用 SP,只是为了方便添加方法/参数等。
答案 1 :(得分:1)
您仍然可以从应用程序执行存储过程/自定义SQL脚本。您甚至可以在Linq-to-sql模型中映射该过程,这样您就不需要手动打开连接和创建命令。
我不确定Linq-to-sql是否总是在单独的往返数据库中执行每个修改命令,但我想它确实如此(至少在大多数情况下)。 EF总能做到。 NHibernate对这些操作有更好的支持,因为它有命令批处理。
您在此处展示的不是批量更新(单个命令更新大量记录) - 大多数ORM将始终单独更新每个记录 - 这就是这些工具的工作方式。如果加载记录并在循环中修改每个记录,则与用于加载记录的原始查询的关系将丢失。您现在在应用程序中有10.000个已加载的记录,必须更新。无法进行批量更新,因为您必须将10.000更改从应用程序移动到数据库。
如果要进行批量更新,您应该使用直接SQL或实现一些逻辑,这些逻辑将从Linq-to-sql进行更新,而不是在应用程序中加载记录和更新它们。检查this article或只是在Linq-to-sql中搜索批量/批量更新。
答案 2 :(得分:0)
这是因为Linq to SQL First从服务器加载数据,然后单独更新每条记录,包括数据查询/传输到客户端,更新每条记录的请求。而在SP的情况下,只有一个SP的调用,它直接在服务器上执行Update查询,它不包括每个记录的数据获取和更新。它批量更新记录
答案 3 :(得分:0)
我做的另一种方法是将对象值作为XML数据类型传递给存储过程。但是当记录数超过1000时会出现超时异常(大约25秒后)。这是由于巨大的xml文件造成的吗?
注意:1000条记录大约需要5秒
public virtual void UpdateBankAccountUsingParseXML_SP(System.Xml.Linq.XElement inputXML)
{
string connectionstring = "Data Source=.;Initial Catalog=LibraryReservationSystem;Integrated Security=True;Connect Timeout=600";
var myDataContext = new DBML_Project.MyDataClassesDataContext(connectionstring);
myDataContext.ParseXML(inputXML);
}
public void FreezeAllAccountsForUser(int userId)
{
List<DTOLayer.BankAccountDTOForStatus> bankAccountDTOList = new List<DTOLayer.BankAccountDTOForStatus>();
IEnumerable<DBML_Project.BankAccount> accounts = AccountRepository.GetAllAccountsForUser(userId);
foreach (DBML_Project.BankAccount acc in accounts)
{
string typeResult = Convert.ToString(acc.GetType());
string baseValue = Convert.ToString(typeof(DBML_Project.BankAccount));
if (String.Equals(typeResult, baseValue))
{
throw new Exception("Not correct derived type");
}
acc.Freeze();
DTOLayer.BankAccountDTOForStatus presentAccount = new DTOLayer.BankAccountDTOForStatus();
presentAccount.BankAccountID = acc.BankAccountID;
presentAccount.Status = acc.Status;
bankAccountDTOList.Add(presentAccount);
}
IEnumerable<System.Xml.Linq.XElement> el = bankAccountDTOList.Select(x =>
new System.Xml.Linq.XElement("BankAccountDTOForStatus",
new System.Xml.Linq.XElement("BankAccountID", x.BankAccountID),
new System.Xml.Linq.XElement("Status", x.Status)
));
System.Xml.Linq.XElement root = new System.Xml.Linq.XElement("root", el);
AccountRepository.UpdateBankAccountUsingParseXML_SP(root);
//AccountRepository.Update();
}
存储过程
ALTER PROCEDURE [dbo].[ParseXML] (@InputXML xml)
AS
BEGIN
DECLARE @MyTable TABLE (RowNumber int, BankAccountID int, StatusVal varchar(max))
INSERT INTO @MyTable(RowNumber, BankAccountID,StatusVal)
SELECT ROW_NUMBER() OVER(ORDER BY c.value('BankAccountID[1]','int') ASC) AS Row,
c.value('BankAccountID[1]','int'),
c.value('Status[1]','varchar(32)')
FROM
@inputXML.nodes('//BankAccountDTOForStatus') T(c);
DECLARE @Count INT
SET @Count = 0
DECLARE @NumberOfRows INT
SELECT @NumberOfRows = COUNT(*) FROM @MyTable
WHILE @Count < @NumberOfRows
BEGIN
SET @Count = @Count + 1
DECLARE @BankAccID INT
DECLARE @Status VARCHAR(MAX)
SELECT @BankAccID = BankAccountID
FROM @MyTable
WHERE RowNumber = @Count
SELECT @Status = StatusVal
FROM @MyTable
WHERE RowNumber = @Count
UPDATE BankAccount
SET Status= @Status
WHERE BankAccountID = @BankAccID
END
END
GO