Прежде всего то, что вы описываете в своем вопросе, на мой взгляд, лучший способ для приложения ASP.NET с MS SQL в качестве базы данных. В базе данных нет блокировки. Он идеально подходит для постоянно отключенных клиентов , таких как веб-клиенты.
Как можно прочитать из некоторых ответов, в терминологии есть недоразумение. Мы все подразумеваем использование Microsoft SQL Server 2008 или выше для хранения базы данных. Если вы откроете в документации по MS SQL Server 2008 тему «версия строки (Transact-SQL)», вы найдете следующее:
" отметка времени является синонимом
rowversion тип данных и зависит от
поведение синонима типа данных. "…
Msgstr "Синтаксис timestamp устарел.
Эта функция будет удалена в
будущая версия Microsoft SQL
Сервер. Избегайте использования этой функции в
новые разработки и планируют
изменить приложения, которые в настоящее время используют
эта функция. "
Итак, timestamp тип данных - это синоним типа данных rowversion для MS SQL. Он содержит 64-битный счетчик, который существует внутри каждой базы данных и может рассматриваться как @@ DBTS . После изменения одной строки в одной таблице базы данных счетчик будет увеличиваться.
Когда я читаю ваш вопрос, я читаю «TimeStamp» как имя столбца типа rowversion data. Я лично предпочитаю имя RowUpdateTimeStamp . В AzManDB (см. Microsoft Authorization Manager с Store as DB) я мог видеть такое имя. Иногда использовались также ChildUpdateTimeStamp для отслеживания иерархических RowUpdateTimeStamp (с учетом триггеров).
Я реализовал этот подход в своем последнем проекте и буду очень счастлив. Обычно вы делаете следующее:
- Добавьте RowUpdateTimeStam p столбца к каждой таблице вашей базы данных с типом rowversion (в Microsoft SQL Management Studio это будет выглядеть как timestamp , что то же самое).
- Вы должны построить все свои запросы SQL SELECT для отправки результатов клиенту так, чтобы вы отправляли дополнительное значение RowVersion вместе с основными данными. Если у вас есть SELECT с JOINT, вы должны отправить RowVersion максимального RowUpdateTimeStamp из обеих таблиц, например
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN s.RowUpdateTimeStamp
ELSE m.RowUpdateTimeStamp
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
Или выполните приведение данных следующим образом
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN CAST(s.RowUpdateTimeStamp AS bigint)
ELSE CAST(m.RowUpdateTimeStamp AS bigint)
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
для хранения RowUpdateTimeStamp как bigint , что соответствует ulong типу данных C #. Если вы делаете OUTER JOINTs или JOINTs из многих таблиц, конструкция MAX(RowUpdateTimeStamp)
из всех таблиц будет выглядеть немного более сложной. Поскольку MS SQL не поддерживает такие функции, как MAX (a, b, c, d, e), соответствующая конструкция может выглядеть следующим образом:
(SELECT MAX(rv)
FROM (SELECT table1.RowUpdateTimeStamp AS rv
UNION ALL SELECT table2.RowUpdateTimeStamp
UNION ALL SELECT table3.RowUpdateTimeStamp
UNION ALL SELECT table4.RowUpdateTimeStamp
UNION ALL SELECT table5.RowUpdateTimeStamp) AS maxrv) AS RowUpdateTimeStamp
- Все отключенные клиенты (веб-клиенты) получают и хранят не только некоторые строки данных, но и RowVersion (тип ulong ) строки данных.
- В одной попытке изменить данные от отключенного клиента ваш клиент должен отправить RowVersion , соответствующий исходным данным, на сервер. Хранимая процедура
spSoftwareUpdate
может выглядеть как
CREATE PROCEDURE dbo.spSoftwareUpdate
@Id int,
@SoftwareName varchar(100),
@originalRowUpdateTimeStamp bigint, -- used for optimistic concurrency mechanism
@NewRowUpdateTimeStamp bigint OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- ExecuteNonQuery() returns -1, but it is not an error
-- one should test @NewRowUpdateTimeStamp for DBNull
SET NOCOUNT ON;
UPDATE dbo.Software
SET Name = @SoftwareName
WHERE Id = @Id AND RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
SET @NewRowUpdateTimeStamp = (SELECT RowUpdateTimeStamp
FROM dbo.Software
WHERE (@@ROWCOUNT > 0) AND (Id = @Id));
END
Код хранимой процедуры dbo.spSoftwareDelete
выглядит так же. Если вы не включите NOCOUNT
, вы можете создать DBConcurrencyException , автоматически генерируемый во многих сценариях. Visual Studio дает вам возможность использовать оптимистичный параллелизм, такой как флажок «Использовать оптимистичный параллелизм» в дополнительных параметрах TableAdapter
или DataAdapter
.
.
Если вы посмотрите на dbo.spSoftwareUpdate
хранимую процедуру carful, вы обнаружите, что я использую RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
в WHERE вместо RowUpdateTimeStamp = @originalRowUpdateTimeStamp
. Я делаю это потому, что значение @originalRowUpdateTimeStamp
, которое имеет клиент, обычно составляется как MAX(RowUpdateTimeStamp)
из нескольких таблиц. Так может быть, что RowUpdateTimeStamp < @originalRowUpdateTimeStamp
. Либо вы должны использовать строгое равенство = и воспроизвести здесь тот же сложный оператор JOIN, который вы использовали в операторе SELECT, либо использовать конструкцию <= </strong>, подобную мне, и оставаться точно такой же безопасной, как и раньше.
Кстати, можно построить очень хорошее значение для ETag на основе RowUpdateTimeStamp, который может отправлять в заголовке HTTP клиенту вместе с данными. С ETag вы можете реализовать интеллектуальное кэширование данных на стороне клиента.
Я не могу написать здесь весь код, но вы можете найти много примеров в Интернете. Я хочу лишь еще раз повторить, что, по моему мнению, использование оптимистичного параллелизма на основе rowversion является лучшим способом для большинства сценариев ASP.NET .