问题描述
首先:我们有一个应用程序主要围绕旧的 DataTable
类型构建。因此,我们无法切换到 e。 G。现在EF。这将是一个未来的项目。与此同时,我们需要构建一个新的基于服务器端 REST 的解决方案来替代旧的服务器逻辑。
问题: sqlDataAdapter.Update(DataTable)
不更新数据库中的数据:
- 新记录:成功插入数据库
- 修改记录:上面的
Update()
方法返回正确的计数,但更改不在 DB 中 - 已删除的记录:上面的
Update()
方法返回 0 计数,因此引发并发异常(这是数据适配器的设计,此处不正确)
推测原因:因为 DataTable
是由服务器应用程序根据客户端的请求获取的,但在它被写入数据库之前被传输到客户端并返回到服务器,sqlDataAdapter
似乎没有正确检测到它们的变化:
- 客户端请求数据
- 服务器从数据库中获取数据
- 数据通过 REST 序列化传输到客户端
- 客户处理数据
- 更改的数据通过 REST 序列化传输到服务器
- 服务器实例化一个新的
sqlDataAdapter
实例,并在收到的数据上生成sqlDataAdapter.Update()
数据完整性:
- 每条记录的正确
RowState
出现在服务器端,当它生成sqlDataAdapter.Update()
- 出于效率原因,客户端仅将更改的记录传输到服务器
- 所有的表都有一个
PK
- 所有表都没有
FK
关系(这是/曾经是传统设计规则)
是否有可能以某种方式在“外部”数据上实现(服务器端)sqlDataAdapter.Update()
,还是这种方法仅用于直接(客户端)更新原始数据的数据库?
常见错误:当然,我已经对这个问题进行了大量搜索,并注意正确填充 sql 命令属性。
服务端代码部分:
public override int[] SaveDataTable(IEnumerable<DataTable> dataTables)
{
var counts = new Queue<int>();
using (_connection = new sqlConnection(ConnectionString))
{
_connection.open();
var transaction = _connection.BeginTransaction();
try
{
foreach (var table in dataTables)
{
//var command = new sqlCommand();
var command = _connection.CreateCommand();
using (command)
{
command.Connection = _connection;
command.Transaction = transaction;
command.CommandText = Global.GetSelectStatement(table);
var dataAdapter = new sqlDataAdapter(command);
var cmdBuilder = new sqlCommandBuilder(dataAdapter);
dataAdapter.UpdateCommand = cmdBuilder.GetUpdateCommand();
dataAdapter.InsertCommand = cmdBuilder.GetInsertCommand();
dataAdapter.DeleteCommand = cmdBuilder.GetDeleteCommand();
//dataAdapter.SelectCommand = command;
//var dSet = new DataSet();
//dataAdapter.Fill(dSet);
//dataAdapter.Fill(table);
//dataAdapter.Fill(new DataTable());
//var clone = table.copy();
//clone.AcceptChanges();
//dataAdapter.Fill(clone);
counts.Enqueue(dataAdapter.Update(table));
}
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback(); //this may throw also
throw;
}
}
return counts.ToArray();
}
解决方法
好的,所以任务解决了。 SqlDataAdapter
的实现没有任何问题(当然评论中的改进建议除外)。
问题在于客户端应用程序代码总是调用 AcceptChanges()
以减少数据量。在将更改的数据发送到数据访问层之前,每行的 RowState
都用 DataRow.SetModified()
等“恢复”了
这导致了SqlDataAdapter.Update()
的问题。
当然这是合乎逻辑的,因为原来的 DataRowVersion
丢失了。但这并不容易识别。