风景:在Windows Server 2012上具有主表和两个子表(Sales,SalesDetail,Payments),使用SQL Server 2008 r2的数据集。此数据集调用不同的存储过程对于每个表上的每个插入,更新和删除操作。
错误:此例程工作正常,但偶尔会有:a)插入不在原始数据表中的子记录或b)插入重复的子记录。
我注意到当我将生产数据库恢复到我的开发机器中时发生了这个错误,然后我运行了应用程序并且第一个事务获得了几个不是我键入的详细记录。第二次恢复该数据库时,错误不会发生。
SQL Server日志在保存这些事务时会显示一些死锁,但这些很少见。
更新程序是:
(感谢Bonnie关于交易的建议: geek-goddess-bonnie.blogspot.mx)
namespace SalesWinCS
{
class Class1
{
SqlConnection conn;
public int UpdateSales(dsetSales ds)
{
Int32 _newId = 0;
SqlDataAdapter da = null;
SqlDataAdapter daDetail = null;
using (TransactionScope scope = Utils.GetTransactionScope())
{
try
{
conn.Open();
// preparing adapters
da = getDA2updateSales(conn);
daDetail = getDA2updateSalesDetail(conn);
// prepare deleted, changed or added tables
DataTable DeletedDetail = ds.SalesDetail.GetChanges(DataRowState.Deleted);
DataTable AddedDetail = ds.SalesDetail.GetChanges(DataRowState.Added);
DataTable ModifiedDetail = ds.SalesDetail.GetChanges(DataRowState.Modified);
// delete rows
if ((DeletedDetail != null))
daDetail.Update(DeletedDetail);
// main row
da.Update(ds, "Sales");
// get new ID
_newId = ds.Sales[0].IdSale;
// updates Detail rows
if ((ModifiedDetalle != null))
daDetail.Update(ModifiedDetail);
// add new rows
if ((AddedDetail != null))
{
foreach (dsetSales.SalesDetailRow d in AddedDetail.Rows)
{
d.IdSale = _newId;
}
daDetail.Update(AddedDetail);
}
// payment table processed as above
scope.Complete();
ds.AcceptChanges();
}
catch (Exception ex)
{
_newId = 0;
}
finally
{
if (conn.State != ConnectionState.Closed)
conn.Close();
}
return _newId;
}
}
private SqlDataAdapter getDA2updateSales(SqlConnection conn)
{
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Sales WHERE IdSale = @IdSale ", conn);
// prepare commands
da.DeleteCommand = new SqlCommand("spd_Sales", conn);
da.InsertCommand = new SqlCommand("spi_Sales", conn);
da.UpdateCommand = new SqlCommand("spu_Sales", conn);
da.SelectCommand.CommandType = CommandType.Text;
da.SelectCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.InsertCommand.CommandType = CommandType.StoredProcedure;
//
da.InsertCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.InsertCommand.Parameters.Add("@IdClient", SqlDbType.Int, 0, "IdClient");
// other fields...
da.InsertCommand.Parameters.Add("@new_id", SqlDbType.Int, 0, "IdVenta").Direction = ParameterDirection.Output;
//
da.UpdateCommand.CommandType = CommandType.StoredProcedure;
da.UpdateCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.UpdateCommand.Parameters.Add("@IdClient", SqlDbType.Int, 0, "IdClient");
// other fields...
da.DeleteCommand.CommandType = CommandType.StoredProcedure;
da.DeleteCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.InsertCommand.UpdatedRowSource = UpdateRowSource.Both;
da.InsertCommand.CommandTimeout = 900;
da.UpdateCommand.CommandTimeout = 900;
return da;
}
private SqlDataAdapter getDA2updateSalesDetail(SqlConnection conn)
{
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM SalesDetail WHERE IdSale = @IdSale", conn);
// prepare commands
da.DeleteCommand = new SqlCommand("spd_SaleDetail", conn);
da.InsertCommand = new SqlCommand("spi_SaleDetail", conn);
da.UpdateCommand = new SqlCommand("spu_SaleDetail", conn);
da.SelectCommand.CommandType = CommandType.Text;
da.SelectCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.InsertCommand.CommandType = CommandType.StoredProcedure;
//
da.InsertCommand.Parameters.Add("@IdSaleDetail", SqlDbType.Int, 0, "IdSaleDetail");
da.InsertCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.InsertCommand.Parameters.Add("@IdProducto", SqlDbType.Int, 0, "IdProduct");
da.InsertCommand.Parameters.Add("@Qty", SqlDbType.Money, 0, "Qty");
da.InsertCommand.Parameters.Add("@Cost", SqlDbType.Money, 0, "Cost");
// other fields
da.InsertCommand.Parameters.Add("@new_id", SqlDbType.Int, 0, "IdSaleDetail").Direction = ParameterDirection.Output;
//
da.UpdateCommand.CommandType = CommandType.StoredProcedure;
da.UpdateCommand.Parameters.Add("@IdSaleDetail", SqlDbType.Int, 0, "IdSaleDetail");
da.UpdateCommand.Parameters.Add("@IdSale", SqlDbType.Int, 0, "IdSale");
da.UpdateCommand.Parameters.Add("@IdProducto", SqlDbType.Int, 0, "IdProduct");
da.UpdateCommand.Parameters.Add("@Qty", SqlDbType.Money, 0, "Qty");
da.UpdateCommand.Parameters.Add("@Cost", SqlDbType.Money, 0, "Cost");
// ...
//
da.DeleteCommand.CommandType = CommandType.StoredProcedure;
da.DeleteCommand.Parameters.Add("@IdSaleDetail", SqlDbType.Int, 0, "IdSaleDetail");
da.InsertCommand.UpdatedRowSource = UpdateRowSource.Both;
da.InsertCommand.CommandTimeout = 900;
da.UpdateCommand.CommandTimeout = 900;
return da;
}
public class Utils
{
/// <summary>
/// The TransactionScoope that gets returned here is Required and IsolationLevel.ReadCommitted.
/// </summary>
/// <returns></returns>
public static TransactionScope GetTransactionScope()
{
return new TransactionScope(TransactionScopeOption.Required, new TransactionOptions(){IsolationLevel = IsolationLevel.ReadCommitted});
}
}
}