【问题标题】:Converting C# code to SQL Server stored procedure将 C# 代码转换为 SQL Server 存储过程
【发布时间】:2015-08-01 20:01:24
【问题描述】:

我正在编写一个小型 web 应用程序,用于为客户计算和创建帐单。为此,我有以下 SQL Server 表:

每个月,Billing 表中将添加多达 2600 条新记录,BillingPriceLine 表中的每个帐单约有 3 条记录,最后,BillingPriceLineSpecification 表中每条记录最多可添加 750 条记录在BillingPriceLine 表中。所以这意味着总共有 很多 条记录 :-)

因此,每个月插入到表中的初始数据可能如下所示(但这只是我的测试数据)

Billing 表(开始完全为空):

BillingPriceLine表:

BillingPriceLineSpecification表:

添加这些记录后(我为此使用SqlBulkCopy),然后我必须进行以下计算才能完成单次计费:

  1. 根据BillingPriceLineSpecification.EstimatedProduction - BillingPriceLineSpecification.RealisedProduction计算BillingPriceLineSpecification.Production

  2. 将每个BillingPriceLineSpecification.Production * BillingPriceLineSpecification.Price 的总和存储在一个临时变量中以备后用

  3. 根据BillingPriceLineSpecification.Production的总和计算BillingPriceLine.Production

  4. 根据步骤 2 的总和除以 BillingPriceLine.Production 计算 BillingPriceLine.Price

  5. 像这样计算BillingPriceLine.TotalPrice

    ((BillingPriceLine.Production * BillingPriceLine.Price) * BillingPriceLine.Share) / 100)
    
  6. 根据BillingPriceLine.TotalPrice的总和计算Billing.SubTotal

  7. 根据Billing.SubTotal / 4 计算Billing.VAT(丹麦增值税为25%)

  8. 计算Billing.Total,这将是Billing.SubTotal + Billing.VAT

我已经使用 Entity Framework 编写了一些 C# 代码来执行此操作,但是当我使用 Billing 表中的 10 条记录而不是 2600 条记录对其进行测试时,它基本上停滞不前(因为缺少更好的词)

我写的 C# 代码:

using(var ctx = new MyEntities())
{
    foreach (Billing billing in ctx.Billings)
    {
        // Calculate billing price lines from billing price line specifications         
        try
        {
            foreach (BillingPriceLine priceLine in billing.BillingPriceLines)
            {
                // Declare a local variable for holding the specification total sum
                decimal specificationsSum = 0;

                // Loop through billing price line specifications on this price line
                foreach (BillingPriceLineSpecification specification in priceLine.BillingPriceLineSpecifications)
                {
                    // First, check if the estimated production and realised production has a value
                    if (specification.EstimatedProduction.HasValue &&  specification.RealisedProduction.HasValue)
                    {
                        // Calculate production for a price line specification
                        specification.Production = specification.EstimatedProduction.Value - specification.RealisedProduction.Value;

                        // Add to total specification sum
                        specificationsSum += specification.Production*specification.Price;
                    }
                }

                // Set total production on price line
                priceLine.Production = priceLine.BillingPriceLineSpecifications.Sum(x => x.Production);

                // Set price on price line
                priceLine.Price = specificationsSum/priceLine.Production;

                // Set total price on price line
                priceLine.TotalPrice = ((priceLine.Production*priceLine.Price)*priceLine.Share)/100;
            }

            // Set subtotal, VAT and total sum on billing
            billing.Subtotal = billing.BillingPriceLines.Sum(x => x.TotalPrice);
            billing.VAT = billing.Subtotal/4;
            billing.Total = billing.Subtotal + billing.VAT;
        }
        catch
        {
            // Handle error logging here ..
        }
    }

    ctx.SaveChanges();
}

我希望使用存储过程将此计算过程转移到 SQL Server,希望性能会好很多,因为它不必在 Web 应用程序上运行。但是,我在编写 T-SQL 时缺乏技能,无法编写这样的程序。

有没有人可以给我一个编写这个程序的起点? :-) 和/或用更好的方法来解决这个问题。如果能得到一些反馈,我们将不胜感激。

提前致谢。

【问题讨论】:

  • 你能把输入表示例数据和预期输出数据放在这里吗
  • 您的代码似乎读取了计费中的每一行,遍历 BillingPriceLineSpecification 中的每一行,然后得到总数。但是你每次都重新计算每一行的总数,当没有过滤器时它不会改变.....
  • @BugFinder 是的,这是预期的行为 :-) 每个账单 SubTotalVATTotal 应该分别计算。
  • 但总数不会改变,该规格和没有过滤器......所以。我想我不明白
  • @BugFinder 不需要过滤器 :-) 这三个表每个月都会填充 一些 数据,然后我必须根据这些初始数据用于“完成”每个计费设置的 SubTotal、VAT 和 Total。有意义吗?

标签: c# sql-server stored-procedures


【解决方案1】:

尝试仅查询所需数据并一次获取所有数据(包括子集合),然后根据内存中的数据进行计算。


这里是一些示例代码,说明我如何使用 SQL 和 SqlBulkCopy 在没有 SP 的情况下从 C# 批量更新数据。

首先,您创建一个自定义对象(每个表)来保存将要更新的数据。

// custom object
public sealed class CustomObjectWithUpdatedValues
{
    public int BillingID { get; set; }
    public int Field1 { get; set; }
    public int Field2 { get; set; }
    public decimal Field3 { get; set; }
    public decimal Field4 { get; set; }
    public decimal Field5 { get; set; }
}

然后我使用下面的代码首先通过SqlBulkCopy 插入到一个新表中。然后我生成一条 SQL 语句来批量更新实时数据,然后删除新表。

// do all your calculations and write the data to be updated to a custom object
List<CustomObjectWithUpdatedValues> DataToUpdateList = GetDataToUpdate();

// create a transaction scope incase something fails we can rollback
using (TransactionScope tranScope = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection("connection_string"))
    {
        // open the connnection
        conn.Open();

        // create a table to temporarily hold the data to update
        string strTableName = "temp_BulkUpdateBillings";
        SqlCommand cmdCreateTable = new SqlCommand
            (
                "CREATE TABLE " + strTableName + " " +
                    "(" +
                        "BillingID int NOT NULL, " +
                        "Field1 int NOT NULL, " +
                        "Field2 int NOT NULL, " +
                        "Field3 decimal(19, 4) NOT NULL, " +
                        "Field4 decimal(19, 4) NOT NULL, " +
                        "Field5 decimal(19, 4) NOT NULL " +
                    ")"
            , conn);
        cmdCreateTable.ExecuteNonQuery();

        // do sql bulk copy to insert data into new table
        using (SqlBulkCopy bcp = new SqlBulkCopy(conn))
        {
            using (var reader = ObjectReader.Create(DataToUpdateList, "BillingID", "Field1", "Field2", "Field3", "Field4", "Field5"))
            {
                bcp.DestinationTableName = strTableName;
                bcp.BatchSize = 1000;
                bcp.BulkCopyTimeout = 300;
                bcp.WriteToServer(reader);
            }
        }

        // update the live records
        SqlCommand cmdBulkUpdate = new SqlCommand
            (
                "UPDATE Billings SET " +
                    "Field1 = temp.Field1, " +
                    "Field2 = temp.Field2, " +
                    "Field3 = temp.Field3, " +
                    "Field4 = temp.Field4, " +
                    "Field5 = temp.Field5 " +
                "FROM " + strTableName + " as temp " +
                "WHERE Billings.ID = temp.BillingID"
            , conn);
        cmdBulkUpdate.ExecuteNonQuery();

        // drop the table
        SqlCommand cmdDropTable = new SqlCommand("DROP TABLE " + strTableName, conn);
        cmdDropTable.ExecuteNonQuery();
    }

    // complete the transaction scope
    tranScope.Complete();
}

ObjectReader 类来自FastMember 库。

希望可以为您指明正确的方向。

【讨论】:

  • 感谢您的回答 :-) 这是有道理的,但即便如此,我也必须更新每个 BillingPriceLineSpecifications Production 字段,每个 BillingPriceLines ProductionPrice , 和TotalPrice 等,所以我想这会导致对数据库的大量调用?我认为造成压力的不是实际计算,而是在计算和更新每个Billing 之后使用ctx.SaveChanges() 一次性更新大量记录。
  • 实体框架对于批量操作来说太可怕了。如果您坚持使用 EF,那么您需要使用 SqlBlulkCopy 将插入临时表并使用带有连接的 sql 查询/sp 进行更新。
  • 不支持使用 EF :-) 这就是为什么我要为此创建一个存储过程。我认为,我将不得不深入研究使用 SqlBulkCopy 进一步更新一点点。
【解决方案2】:

希望对你有帮助

Set Nocount On;

Declare @Billing Table
(
     ID                     Int Identity(514789,1) Primary Key
    ,SubTotal               Numeric(38,10)
    ,Vat                    Numeric(38,10)
    ,Total                  Numeric(38,10)
    ,Status                 Varchar(100)
)

Declare @BillingPriceLine Table
(
     ID                     Int Identity(24527,1) Primary Key
    ,BillingID              Int
    ,Product                Varchar(100) Null
    ,FinancialID            Int
    ,Production             Numeric(38,10)
    ,Unit                   Varchar(100)
    ,Price                  Numeric(38,10)
    ,Share                  Numeric(38,10)
    ,TotalPrice             Numeric(38,10)
)

Declare @BillingPriceLineSpecification Table
(
     ID                     Int Identity(2447820,1) Primary Key
    ,BillingPriceLineID     Int
    ,Production             Numeric(38,10)
    ,EstimatedProduction    Numeric(38,10)
    ,RealisedProduction     Numeric(38,10)
    ,Unit                   Varchar(100)
    ,Price                  Numeric(38,10)
    ,Created                Datetime
    ,Time                   Datetime
)

Insert Into @Billing(SubTotal,Vat,Total) Values
 (Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)
,(Null,Null,Null)

Insert Into @BillingPriceLine(BillingID,FinancialID,Production,Unit,Price,Share,TotalPrice) Values
 (514789,1234,Null,'kWh',Null,50.00,Null)
,(514789,1234,Null,'kWh',Null,50.00,Null)
,(514789,1234,Null,'kWh',Null,50.00,Null)
,(514789,1234,Null,'kWh',Null,50.00,Null)
,(514789,1234,Null,'kWh',Null,50.00,Null)
,(514790,1234,Null,'kWh',Null,50.00,Null)
,(514790,1234,Null,'kWh',Null,50.00,Null)
,(514790,1234,Null,'kWh',Null,50.00,Null)
,(514790,1234,Null,'kWh',Null,50.00,Null)
,(514790,1234,Null,'kWh',Null,50.00,Null)
,(514791,1234,Null,'kWh',Null,50.00,Null)
,(514791,1234,Null,'kWh',Null,50.00,Null)
,(514791,1234,Null,'kWh',Null,50.00,Null)
,(514791,1234,Null,'kWh',Null,50.00,Null)
,(514791,1234,Null,'kWh',Null,50.00,Null)
,(514792,1234,Null,'kWh',Null,50.00,Null)
,(514792,1234,Null,'kWh',Null,50.00,Null)

Insert Into @BillingPriceLineSpecification(BillingPriceLineID,Production,EstimatedProduction,RealisedProduction,Unit,Price,Created,Time) Values
 (24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')
,(24527,0.00,2.00,4.00,'kWh',0.30,'2015-07-30 18:09:34.000','2015-07-30 18:09:34.000')

-------- above one only table preparation -----

Update  bpls
Set     bpls.Production = Abs(bpls.EstimatedProduction - bpls.RealisedProduction)
From    @BillingPriceLineSpecification As bpls

Update  bpl
Set      bpl.Production = bpls.Production
        ,bpl.Price = (bpls.SumProduction / bpls.Production)
        ,bpl.TotalPrice = (bpls.Production * (bpls.SumProduction / bpls.Production) * bpl.Share) / 100
From    @BillingPriceLine As bpl
        Join
        (
            Select   bpls.BillingPriceLineID
                    ,Sum(bpls.Production) As Production
                    ,Sum(bpls.Production * bpls.Price) As SumProduction
            From    @BillingPriceLineSpecification As bpls
            Group By bpls.BillingPriceLineID
        ) As bpls On bpl.ID = bpls.BillingPriceLineID



Update  b
Set      b.SubTotal = bpl.TotalPrice
        ,b.Vat = (bpl.TotalPrice / 4)
        ,b.Total = (bpl.TotalPrice + (bpl.TotalPrice / 4))
From    @Billing As b
        Join
        (
            Select   bpl.BillingID
                    ,Sum(Isnull(bpl.TotalPrice,0)) As TotalPrice
            From    @BillingPriceLine As bpl
            Group By bpl.BillingID  
        ) As bpl On b.ID = bpl.BillingID

---- final result
Select   b.ID
        ,b.SubTotal
        ,b.Vat
        ,b.Total
From    @Billing As b

输出:-

【讨论】:

    猜你喜欢
    • 2013-10-10
    • 2020-06-06
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多