0

我有一个如下所示的表格。它有固定和储蓄类型的账户。我需要更新用户 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后的CODE

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";
    }
}  
}

参考

  1. 将 List 作为 XElement 传递以用作 XML 数据类型参数
4

4 回答 4

6

您正在比较两种截然不同的场景:

1:在SQL server本地运行脚本,单集为主UPDATE

2:通过网络获取 10,000 条记录,更新每条记录,单独提交每条记录

您可以通过将 1 推迟到一批 10,000 而不是 10,000 批 1 来稍微改进 2(只是:直到最后才调用),但这仍然涉及在两个方向发送 10,000 条记录的详细信息,加上所有开销(例如,可能仍然选择通过 10,000 个单独的调用来实现)。SubmitChanges()SubmitChanges()SubmitChanges()

基本上,基于对象的工具不适用于针对记录的批量更新。如果 SP 有效,请使用 SP。也许通过数据上下文调用SP,只是为了方便它添加方法/参数/等。

于 2012-07-03T07:38:46.593 回答
1

您仍然可以从您的应用程序中执行您的存储过程/自定义 SQL 脚本。您甚至可以在 Linq-to-sql 模型中映射过程,这样您就不需要手动打开连接和创建命令。

我不确定 Linq-to-sql 是否总是在单独的往返数据库中执行每个修改命令,但我猜它确实(至少在大多数情况下)。EF总是这样做。NHibernate 对此类操作有更好的支持,因为它具有命令批处理功能。

您在此处显示的不是批量更新(单个命令更新大量记录)-大多数 ORM 将始终单独更新每条记录-这就是这些工具的工作方式。如果您加载记录并在循环中修改它们中的每一个,则与用于加载记录的原始查询的关系将丢失。现在,您的应用程序中有 10.000 条加载的记录必须更新。无法进行批量更新,因为您必须将 10.000 项更改从应用程序移动到数据库。

如果您想进行批量更新,您应该使用直接 SQL 或实现一些逻辑,从 Linq-to-sql 进行更新,而不是加载记录并在应用程序中更新它们。查看这篇文章或简单地在 Linq-to-sql 中搜索 Bulk / Batch 更新。

于 2012-07-03T08:00:10.593 回答
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
于 2012-07-04T14:52:14.337 回答
0

这是因为 Linq to SQL 首先从服务器加载数据,然后单独更新每条记录,包括数据查询/传输到客户端,更新每条记录的请求。而在 SP 情况下,只调用 SP 直接在服务器上执行更新查询,它不包括数据获取和每条记录的更新。它批量更新记录

于 2012-07-03T07:34:43.337 回答