C# SQL按顺序一次更新一个项目

C# SQL按顺序一次更新一个项目,c#,sql,sql-server,ado.net,C#,Sql,Sql Server,Ado.net,我有一个c代码,它执行SQL更新,一次可以执行多个更新。现在,我正在更新的表有一个名为SortOrder的列,所以当我进行这些多个更新时,我想按照SortOrder列的顺序进行更新……这可能吗 这是我的密码: public void PostScheduledTasks(List<CellModel> cells) { conn = new SqlConnection(connectionString); cmd = new SqlCo

我有一个c代码,它执行SQL更新,一次可以执行多个更新。现在,我正在更新的表有一个名为SortOrder的列,所以当我进行这些多个更新时,我想按照SortOrder列的顺序进行更新……这可能吗

这是我的密码:

public void PostScheduledTasks(List<CellModel> cells)
        {
conn = new SqlConnection(connectionString);
                cmd = new SqlCommand(
                    @"UPDATE ScheduleTasks_Copy  
                      SET 
                          ActualStart=@actualStart,
                          ActualFinish=@actualFinish,
                          ActualEndDate=@actualEndDate,
                          UserDate1=@userDateOne,
                          IsCompleted=@isCompleted
                      WHERE ScheduleTaskID = @scheduleTaskID");
                cmd.Parameters.Add("@isCompleted", System.Data.SqlDbType.Bit);
                cmd.Parameters.Add("@userDateOne", System.Data.SqlDbType.DateTime);
                cmd.Parameters.Add("@actualStart", System.Data.SqlDbType.DateTime);
                cmd.Parameters.Add("@actualFinish", System.Data.SqlDbType.DateTime);
                cmd.Parameters.Add("@actualEndDate", System.Data.SqlDbType.DateTime);
                cmd.Parameters.Add("@scheduleTaskID", System.Data.SqlDbType.Int);


                cmd.Connection = conn;

                conn.Open();
                for (int i = 0; i < cells.Count; i++)
                {
                    cmd.Parameters["@isCompleted"].Value = cmd.Parameters["@percentComplete"].Value = (cells[i].selected == true) ? 1 : 0;
                    cmd.Parameters["@userDateOne"].Value = !string.IsNullOrEmpty(cells[i].scheduledDate) ? cells[i].scheduledDate : (object)DBNull.Value;
                    cmd.Parameters["@actualStart"].Value = !string.IsNullOrEmpty(cells[i].actualDate) ? cells[i].actualDate : (object)DBNull.Value;
                    cmd.Parameters["@actualFinish"].Value = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
                    cmd.Parameters["@actualEndDate"].Value = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
                    cmd.Parameters["@scheduleTaskID"].Value = cells[i].scheduleTaskID;
                    cmd.ExecuteNonQuery();
                }

                conn.Close();
}

您必须首先获取SortOrder列的值,然后在WHERE子句中使用SortOrder=X对它们进行迭代更新:

var dt = new DataTable();
从查询中填写dt,如:从ScheduleTasks\u Copy中选择SortOrder

var conn = new SqlConnection(connectionString);

foreach (DataRow dr in dt.Rows)
{
    cmd = new SqlCommand(
        @"UPDATE ScheduleTasks_Copy  
        SET 
        ActualStart=@actualStart,
        ActualFinish=@actualFinish,
        ActualEndDate=@actualEndDate,
        UserDate1=@userDateOne,
        IsCompleted=@isCompleted
        WHERE ScheduleTaskID = @scheduleTaskID
        AND SortOrder = @sortOrder");

    cmd.Parameters.Add("@isCompleted", System.Data.SqlDbType.Bit);
    cmd.Parameters.Add("@userDateOne", System.Data.SqlDbType.DateTime);
    cmd.Parameters.Add("@actualStart", System.Data.SqlDbType.DateTime);
    cmd.Parameters.Add("@actualFinish", System.Data.SqlDbType.DateTime);
    cmd.Parameters.Add("@actualEndDate", System.Data.SqlDbType.DateTime);
    cmd.Parameters.Add("@scheduleTaskID", System.Data.SqlDbType.Int);
    cmd.Parameters.Add("@sortOrder", dr["SortOrder"].ToString());
}
我还没有测试过这段代码的语法,但它理解了一般的想法

虽然我想这取决于您希望这样做的原因,但在我看来,需要这样做的原因是有限的。

CellModel是否具有sortOrder属性?因为它应该。在这种情况下,您可以添加以下行:

cells = cells.OrderBy(o => o.sortOrder); // add this -> order your cells first
for (int i = 0; i < cells.Count; i++) { 
...
}
然后,您应该检查所有这些项目,如果在单元格中找到具有相同ScheduleTaskID的项目,则应进行更新。

如果可以从源对象单元格中确定排序规则,如其他属性: 然后,最有效的方法是在迭代之前按排序器对单元格进行排序。这样做的确切方法超出了问题的范围,因为您没有告诉我们什么是单元格,确切地说是列表?阵列?自定义对象?一套

如果只能通过查询数据库来确定排序规则: 然后,毫不奇怪,您需要查询数据库:

SELECT ScheduleTaskID, SortOrder FROM ScheduleTasks_Copy ORDER BY SortOrder
遍历该行集,每次都获取ScheduleTaskID。对于每个ScheduleTaskID,遍历单元格,直到找到匹配的任务单元格[i]。ScheduleTaskID==TaskID,然后使用源表中的匹配任务更新数据库

下面是非常粗略的代码,我已经有一段时间没有编写C了:

using (connection)
{
    SqlCommand command = new SqlCommand("SELECT ScheduleTaskID, SortOrder FROM ScheduleTasks_Copy ORDER BY SortOrder;", connection);
    connection.Open();
    SqlDataReader reader = command.ExecuteReader();

    if (reader.HasRows)
    {
        while (reader.Read())
        {
            int taskid = reader.GetInt32(0);
            for (int i = 0; i < cells.Count; i++)
            {
             if (cells[i].scheduleTaskID == taskid) {
                 cmd.Parameters["@isCompleted"].Value = cmd.Parameters["@percentComplete"].Value = (cells[i].selected == true) ? 1 : 0;
                 cmd.Parameters["@userDateOne"].Value = !string.IsNullOrEmpty(cells[i].scheduledDate) ? cells[i].scheduledDate : (object)DBNull.Value;
                 cmd.Parameters["@actualStart"].Value = !string.IsNullOrEmpty(cells[i].actualDate) ? cells[i].actualDate : (object)DBNull.Value;
                 cmd.Parameters["@actualFinish"].Value = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
                 cmd.Parameters["@actualEndDate"].Value = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
                 cmd.Parameters["@scheduleTaskID"].Value = cells[i].scheduleTaskID;
                 cmd.ExecuteNonQuery();
                 }
            }
        }
    }
    reader.Close();
 }

我相信您可以做的是将结果上传到临时表中,然后使用游标服务器端按指定顺序滚动并更新所有行。如果您能够使用sql用户定义的类型和存储过程,这可能会更容易

var conn = new SqlConnection(connectionString);

conn.Open();
SqlCommand cmd = new SqlCommand("create table ##Tasks(id int, isCompleted bit, userDateOne datetime, actualStart datetime, actualFinish datetime, actualEndDate datetime)", conn);
cmd.ExecuteNonQuery();

DataTable localTempTable = new DataTable("Tasks");
localTempTable.Columns.Add("id", typeof(int));
localTempTable.Columns.Add("isCompleted", typeof(bool));
localTempTable.Columns.Add("userDateOne", typeof(DateTime));
localTempTable.Columns.Add("actualStart", typeof(DateTime));
localTempTable.Columns.Add("actualFinish", typeof(DateTime));
localTempTable.Columns.Add("actualEndDate", typeof(DateTime));

for (int i = 0; i < cells.Count; i++)
{
    var row = localTempTable.NewRow();
    row["id"] = cells[i].scheduleTaskID;
    row["isCompleted"] = (cells[i].selected == true);
    row["userDateOne"] = !string.IsNullOrEmpty(cells[i].scheduledDate) ? cells[i].scheduledDate : (object)DBNull.Value;
    row["actualStart"] = !string.IsNullOrEmpty(cells[i].actualDate) ? cells[i].actualDate : (object)DBNull.Value;
    row["actualFinish"] = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
    row["actualEndDate"] = !string.IsNullOrEmpty(cells[i].finishedDate) ? cells[i].finishedDate : (object)DBNull.Value;
}

localTempTable.AcceptChanges();

using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
    bulkCopy.DestinationTableName = "##Tasks";
    bulkCopy.WriteToServer(localTempTable);
}

SqlCommand cmd = new SqlCommand(@"
    declare orderedUpdate cursor 
    for 
    select tempTasks.* from ##tasks tempTasks
    inner join ScheduleTasks tasks on tempTasks.id =  tasks.ScheduleTaskID
    order by tasks.sortOrder;

declare @id int, @isCompleted bit, @actualStart datetime, @actualFinish datetime, @actualEndDate datetime, @userDateOne datetime;

open orderedUpdate;
fetch next from orderedUpdate INTO @id, @isCompleted, @userDateOne, @actualStart, @actualFinish, @actualEndDate 
while @@fetch_status = 0
begin 
    UPDATE ScheduleTasks_Copy  
        SET 
            ActualStart=@actualStart,
            ActualFinish=@actualFinish,
            ActualEndDate=@actualEndDate,
            UserDate1=@userDateOne,
            IsCompleted=@isCompleted
        WHERE ScheduleTaskID = @id;
end
close orderedUpdate
deallocate orderedUpdate", conn);

cmd.ExecuteNonQuery();

退一步,忽略您关于同时运行更新的实际问题,让我们假设您的问题不是我可以同时运行一组单个更新吗?取而代之的是如何尽可能快/高效地更新列表单元格中的每个项目

显然,当cells.Count变大时,按顺序运行单个更新不起作用,这可能是因为执行更新的成本与跨网络执行多次往返所产生的开销相比相形见绌。因此,同时运行一系列单个更新可能也不是最有效的解决方案

相反,让我们一次性移动整个单元格列表。这意味着服务器拥有处理我们的请求所需的一切。如果cells.Count很大,则很难像建议的那样复制SqlBulkCopy。或者,a可以适用于较小的集合

一旦数据可供服务器使用,您所需要做的就是对temp表/变量执行一次更新联接,您已经完成了这项技术的一些很好的示例。但是,请注意,如果单元格包含重复的scheduleTaskID,您可能还需要执行一些额外的预处理,以确保最高排序器获胜,等等

例如:

create table #temp(scheduleTaskID int, isCompleted bit, userDateOne datetime, actualStart datetime, actualFinish datetime, actualEndDate datetime)

--populate #temp via SqlBulkCopy, etc

update st
set st.ActualStart = t.actualStart
  , st.ActualFinish = t.actualFinish
  , st.ActualEndDate = t.actualEndDate
  , st.UserDate1 = t.userDateOne
  , st.IsCompleted = t.isCompleted
from dbo.ScheduleTasks_Copy st
     inner join #temp t on st.ScheduleTaskID = t.scheduleTaskID

drop table #temp

更新的连接语法并不像它可能应该的那样广为人知-一旦你习惯了这种轻微的尴尬,它就会变得非常有用…

虽然还不清楚为什么人们希望以任何特定的顺序更新记录集,但是否有一个触发因素会导致某种下游效应,从而从中受益在按照sortOrder字段?的顺序进行处理时,应该指出,在单个UPDATE语句中执行所有更新会使排序变得不相关。其他一些人已经指出,采用基于集合的方法会快得多,而且肯定会快得多,但在一次更新中完成意味着在实际层面上事务上没有顺序

已经提到过通过SqlBulkCopy基于此集合,但我更喜欢使用表值参数TVPs,因为它们允许将单元格集合直接流式传输到存储过程,从技术上讲,通过[tempdb]可以很好地进行更新,但距离足够近。相反,使用SqlBulkCopy意味着需要以`数据表'的形式复制现有单元格集合

下面是将现有进程转换为使用TVP的T-SQL和C代码。应该注意的是,如果仍有某些原因需要按特定顺序执行此操作,那么唯一需要更改的是存储过程中表变量的处理方式

T-SQL代码 -第一:您需要一个用户定义的表类型 将类型dbo.ScheduleTasksImport创建为表 ScheduleTaskID INT不为空- (可选)将此字段标记为主键 IsCompleted位不为空, 实际开始日期时间为空, 实际完成日期时间为空, ActualLendDate日期时间为空, UserDate1日期时间为空 ; 去 将执行类型::[dbo].[ScheduleTasksImport]授予[user_或_角色]; 去 -第二:将UDTT用作导入过程的输入参数。 -因此,表值参数TVP 创建过程dbo.ImportData @可导入dbo.ScheduleTasksImport只读 像 不计数; 更新stc 设置stc.ActualStart=imp.ActualStart, stc.ActualFinish=imp.ActualFinish, stc.actualendate=imp.actualendate, stc.UserDate1=imp.UserDate1, stc.IsCompleted=imp.IsCompleted 从ScheduleTasks\u复制stc 内部联接@impattable imp 在imp.ScheduleTaskID=stc.ScheduleTaskID上 去 将对dbo.ImportData执行的权限授予[用户或角色]; C代码 第1部分:定义接受集合并将其作为IEnumerable返回的方法


请注意,两个C清单实际上放在同一个文件中,但为了可读性,在这里被分开了。如果将它们放在单独的.cs文件中,则需要将using语句复制到第二个列表中。

忽略关于为什么要这样做的明显问题,您可以尝试以下方法: 使用将数据插入临时表,然后在源中使用带有ORDER BY子句的MERGE。在本例中,我使用ScheduleTasks\u Copy作为临时表,ScheduleTasks作为目标

MERGE ScheduleTasks AS T
USING (SELECT TOP 99999999 C.* 
       FROM ScheduleTasks_Copy C
       JOIN ScheduleTasks S
         ON C.ID = S.ID
       ORDER BY S.SortOrder ASC) AS S
ON S.ID = T.ID
WHEN MATCHED THEN 
  UPDATE SET T.ActualStart = S.ActualStart,
      T.ActualFinish = S.ActualFinish,
      T.ActualEndDate = S.ActualEndDate,
      T.UserDate1 = S.UserDate1,
      T.IsCompleted = S.IsCompleted,
      T.UpdatedOn =  dbo.GetDateValue();
不要担心愚蠢的GetDateValue函数和UpdateOn列。我只是将它们包括在内,以便您可以查看更新的执行顺序。如果要以相反的顺序更新记录,只需将排序顺序更改为DESC


就我个人而言,我建议避免类似的事情,并重新设计您的解决方案,以便完全避免问题。

您是否遇到任何错误/异常,是否可以尝试在循环内部和循环开始时添加参数,并使用cmd.Paramaters.Clear;我没有收到任何错误,这段代码工作正常,我只是想添加代码,通过列sortorder更新它们。这种奇怪且通常不必要的情况的目的是什么?为什么您需要一次更新一个?那么,为什么按这个顺序?无论您试图做什么,在SQL中可能有一种更好的方法来实现它,而不是这种非常不面向集合的条件。。。在SQL Server中更新或插入行的顺序对后续SELECT查询没有影响。订单输入不影响订单输出。期望它可能会,或者通常会,是危险的。我必须同意@rbaryyoung。即使这里给出的最佳答案也很容易被基于集合的解决方案所超越,在这种解决方案中,您“简单地”将新数据推送到一个临时表中,并在一次操作中更新目标表。除非你有一些好的和奇异的理由一个接一个地去做,否则选择基于集合的解决方案才是真正的出路触发器不是一个很好的理由,因为它们从基于集合的方法中获益甚于逐行方法
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;

private static IEnumerable<SqlDataRecord> SendRows(List<CellModel> Cells)
{
   SqlMetaData[] _TvpSchema = new SqlMetaData[] {
      new SqlMetaData("ScheduleTaskID", SqlDbType.Int),
      new SqlMetaData("IsCompleted", SqlDbType.Bit),
      new SqlMetaData("ActualStart", SqlDbType.DateTime),
      new SqlMetaData("ActualFinish", SqlDbType.DateTime),
      new SqlMetaData("ActualEndDate", SqlDbType.DateTime),
      new SqlMetaData("UserDate1", SqlDbType.DateTime)
   };

   SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);

   // read a row, send a row
   for (int _Index = 0; _Index < Cells.Count; _Index++)
   {
      // Unlike BCP and BULK INSERT, you have the option here to create an
      // object, do manipulation(s) / validation(s) on the object, then pass
      // the object to the DB or discard via "continue" if invalid.
      _DataRecord.SetInt32(0, Cells[_Index].scheduleTaskID);
      _DataRecord.SetBoolean(1, Cells[_Index].selected); // IsCompleted
      _DataRecord.SetDatetime(2, Cells[_Index].actualDate); // ActualStart
      _DataRecord.SetDatetime(3, Cells[_Index].finishedDate); // ActualFinish
      _DataRecord.SetDatetime(4, Cells[_Index].finishedDate); // ActualEndDate
      _DataRecord.SetDatetime(5, Cells[_Index].scheduledDate); // UserDate1

      yield return _DataRecord;
   }
}
public static void PostScheduledTasks(List<CellModel> Cells)
{
   SqlConnection _Connection = new SqlConnection(connectionString);
   SqlCommand _Command = new SqlCommand("ImportData", _Connection);
   _Command.CommandType = CommandType.StoredProcedure;

   SqlParameter _TVParam = new SqlParameter();
   _TVParam.ParameterName = "@ImportTable";
   _TVParam.SqlDbType = SqlDbType.Structured;
   _TVParam.Value = SendRows(Cells); // method return value is streamed data
   _Command.Parameters.Add(_TVParam);

   try
   {
      _Connection.Open();

      // Send the data and process the UPDATE
      _Command.ExecuteNonQuery();
   }
   finally
   {
      _Connection.Close();
   }

   return;
}
MERGE ScheduleTasks AS T
USING (SELECT TOP 99999999 C.* 
       FROM ScheduleTasks_Copy C
       JOIN ScheduleTasks S
         ON C.ID = S.ID
       ORDER BY S.SortOrder ASC) AS S
ON S.ID = T.ID
WHEN MATCHED THEN 
  UPDATE SET T.ActualStart = S.ActualStart,
      T.ActualFinish = S.ActualFinish,
      T.ActualEndDate = S.ActualEndDate,
      T.UserDate1 = S.UserDate1,
      T.IsCompleted = S.IsCompleted,
      T.UpdatedOn =  dbo.GetDateValue();