Какая платформа ORM лучше всего подходит для проектирования базы данных MVCC?
-
09-06-2019 - |
Вопрос
При разработке базы данных для использования MVCC (управление многоверсионным параллелизмом) вы создаете таблицы либо с логическим полем, например «IsLatest», либо с целым числом «VersionId», и никогда не выполняете никаких обновлений, а вставляете новые записи только тогда, когда что-то меняется.
MVCC обеспечивает автоматический аудит приложений, которым требуется подробная история, а также снижает нагрузку на базу данных в отношении блокировок обновлений.Минусы в том, что это увеличивает размер ваших данных и замедляет выбор из-за дополнительного предложения, необходимого для получения последней версии.Это также усложняет внешние ключи.
(Обратите внимание, что я нет речь идет о встроенной поддержке MVCC в СУБД, такой как уровень изоляции снимков SQL Server)
Это обсуждалось в других сообщениях здесь, на Stack Overflow.[задача - ссылки]
Мне интересно, какая из распространенных инфраструктур сущностей/ORM (Linq to Sql, ADO.NET EF, Hibernate и т. д.) может полностью поддерживать этот тип дизайна?Это серьезное изменение типичного шаблона проектирования ActiveRecord, поэтому я не уверен, смогут ли большинство существующих инструментов помочь кому-то, кто решит пойти по этому пути со своей моделью данных.Меня особенно интересует, как будут обрабатываться внешние ключи, потому что я даже не уверен, как лучше всего смоделировать их данные для поддержки MVCC.
Решение
Я мог бы рассмотреть возможность реализации уровня MVCC исключительно в БД, используя хранимые процедуры и представления для обработки моих операций с данными.Затем вы могли бы предоставить разумный API для любого ORM, который был способен отображать хранимые процедуры и обратно, и вы могли бы позволить БД решать проблемы целостности данных (поскольку она в значительной степени создана для этого).Если вы пошли по этому пути, возможно, вам захочется взглянуть на более чистое решение для картографии, такое как IBatis или IBatis.net.
Другие советы
Я спроектировал базу данных аналогичным образом (только INSERT — без UPDATE и без DELETE).
Почти все мои запросы SELECT касались просмотра только текущих строк каждой таблицы (самый высокий номер версии).
Виды выглядели так…
SELECT
dbo.tblBook.BookId,
dbo.tblBook.RevisionId,
dbo.tblBook.Title,
dbo.tblBook.AuthorId,
dbo.tblBook.Price,
dbo.tblBook.Deleted
FROM
dbo.tblBook INNER JOIN
(
SELECT
BookId,
MAX(RevisionId) AS RevisionId
FROM
dbo.tblBook
GROUP BY
BookId
) AS CurrentBookRevision ON
dbo.tblBook.BookId = CurrentBookRevision.BookId AND
dbo.tblBook.RevisionId = CurrentBookRevision.RevisionId
WHERE
dbo.tblBook.Deleted = 0
И все мои вставки (а также обновления и удаления) обрабатывались хранимыми процедурами (по одной на таблицу).
Хранимые процедуры выглядели так…
ALTER procedure [dbo].[sp_Book_CreateUpdateDelete]
@BookId uniqueidentifier,
@RevisionId bigint,
@Title varchar(256),
@AuthorId uniqueidentifier,
@Price smallmoney,
@Deleted bit
as
insert into tblBook
(
BookId,
RevisionId,
Title,
AuthorId,
Price,
Deleted
)
values
(
@BookId,
@RevisionId,
@Title,
@AuthorId,
@Price,
@Deleted
)
Номера редакций обрабатывались для каждой транзакции в коде Visual Basic…
Shared Sub Save(ByVal UserId As Guid, ByVal Explanation As String, ByVal Commands As Collections.Generic.Queue(Of SqlCommand))
Dim Connection As SqlConnection = New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("Connection").ConnectionString)
Connection.Open()
Dim Transaction As SqlTransaction = Connection.BeginTransaction
Try
Dim RevisionId As Integer = Nothing
Dim RevisionCommand As SqlCommand = New SqlCommand("sp_Revision_Create", Connection)
RevisionCommand.CommandType = CommandType.StoredProcedure
RevisionCommand.Parameters.AddWithValue("@RevisionId", 0)
RevisionCommand.Parameters(0).SqlDbType = SqlDbType.BigInt
RevisionCommand.Parameters(0).Direction = ParameterDirection.Output
RevisionCommand.Parameters.AddWithValue("@UserId", UserId)
RevisionCommand.Parameters.AddWithValue("@Explanation", Explanation)
RevisionCommand.Transaction = Transaction
LogDatabaseActivity(RevisionCommand)
If RevisionCommand.ExecuteNonQuery() = 1 Then 'rows inserted
RevisionId = CInt(RevisionCommand.Parameters(0).Value) 'generated key
Else
Throw New Exception("Zero rows affected.")
End If
For Each Command As SqlCommand In Commands
Command.Connection = Connection
Command.Transaction = Transaction
Command.CommandType = CommandType.StoredProcedure
Command.Parameters.AddWithValue("@RevisionId", RevisionId)
LogDatabaseActivity(Command)
If Command.ExecuteNonQuery() < 1 Then 'rows inserted
Throw New Exception("Zero rows affected.")
End If
Next
Transaction.Commit()
Catch ex As Exception
Transaction.Rollback()
Throw New Exception("Rolled back transaction", ex)
Finally
Connection.Close()
End Try
End Sub
Я создал объект для каждой таблицы, каждый с конструкторами, свойствами и методами экземпляра, командами создания-обновления-удаления, набором функций поиска и функциями сортировки IComparable.Это был огромный объем кода.
Таблица БД «один к одному» для объекта VB...
Public Class Book
Implements iComparable
#Region " Constructors "
Private _BookId As Guid
Private _RevisionId As Integer
Private _Title As String
Private _AuthorId As Guid
Private _Price As Decimal
Private _Deleted As Boolean
...
Sub New(ByVal BookRow As DataRow)
Try
_BookId = New Guid(BookRow("BookId").ToString)
_RevisionId = CInt(BookRow("RevisionId"))
_Title = CStr(BookRow("Title"))
_AuthorId = New Guid(BookRow("AuthorId").ToString)
_Price = CDec(BookRow("Price"))
Catch ex As Exception
'TO DO: log exception
Throw New Exception("DataRow does not contain valid Book data.", ex)
End Try
End Sub
#End Region
...
#Region " Create, Update & Delete "
Function Save() As SqlCommand
If _BookId = Guid.Empty Then
_BookId = Guid.NewGuid()
End If
Dim Command As SqlCommand = New SqlCommand("sp_Book_CreateUpdateDelete")
Command.Parameters.AddWithValue("@BookId", _BookId)
Command.Parameters.AddWithValue("@Title", _Title)
Command.Parameters.AddWithValue("@AuthorId", _AuthorId)
Command.Parameters.AddWithValue("@Price", _Price)
Command.Parameters.AddWithValue("@Deleted", _Deleted)
Return Command
End Function
Shared Function Delete(ByVal BookId As Guid) As SqlCommand
Dim Doomed As Book = FindByBookId(BookId)
Doomed.Deleted = True
Return Doomed.Save()
End Function
...
#End Region
...
#Region " Finders "
Shared Function FindByBookId(ByVal BookId As Guid, Optional ByVal TryDeleted As Boolean = False) As Book
Dim Command As SqlCommand
If TryDeleted Then
Command = New SqlCommand("sp_Book_FindByBookIdTryDeleted")
Else
Command = New SqlCommand("sp_Book_FindByBookId")
End If
Command.Parameters.AddWithValue("@BookId", BookId)
If Database.Find(Command).Rows.Count > 0 Then
Return New Book(Database.Find(Command).Rows(0))
Else
Return Nothing
End If
End Function
Такая система сохраняет все предыдущие версии каждой строки, но управлять ею может быть очень сложно.
ПЛЮСЫ:
- Общая история сохранена
- Меньше хранимых процедур
МИНУСЫ:
- полагается на приложение, не связанное с базой данных, для обеспечения целостности данных
- огромное количество кода, которое нужно написать
- Никакие внешние ключи не управляются внутри базы данных (прощай, автоматическое создание объектов в стиле Linq-to-SQL)
- Я до сих пор не придумал хороший пользовательский интерфейс, позволяющий получить все, что сохранилось от прошлых версий.
ЗАКЛЮЧЕНИЕ:
- Я бы не стал заниматься такими проблемами в новом проекте без какого-нибудь простого в использовании готового решения ORM.
Мне любопытно, сможет ли Microsoft Entity Framework хорошо справиться с такими проектами баз данных.
Джеффу и остальным членам команды Stack Overflow, должно быть, приходилось сталкиваться с аналогичными проблемами при разработке Stack Overflow:Предыдущие версии отредактированных вопросов и ответов сохраняются и могут быть восстановлены.
Я думаю, Джефф заявил, что его команда использовала Linq to SQL и MS SQL Server.
Интересно, как они справились с этими проблемами?
Насколько мне известно, платформы ORM захотят сгенерировать для вас код CRUD, поэтому они должны быть явно разработаны для реализации опции MVCC;Я не знаю никого, кто делал бы это «из коробки».
С точки зрения платформы Entity, CSLA вообще не реализует постоянство — он просто определяет интерфейс «адаптера данных», который вы используете для реализации любого необходимого постоянства.Таким образом, вы можете настроить шаблоны генерации кода (CodeSmith и т. д.) для автоматического создания логики CRUD для ваших объектов CSLA, которые соответствуют архитектуре базы данных MVCC.
Этот подход будет работать с любой структурой сущностей, скорее всего, не только с CSLA, но это будет очень «чистая» реализация в CSLA.
Ознакомьтесь с проектом Envers - хорошо работает с приложениями JPA/Hibernate и в основном делает это за вас - отслеживает различные версии каждого объекта в другой таблице и дает вам возможности, подобные SVN («Дайте мне версию Person, используемую 2008-11 -05...")
/Йенс
Я всегда полагал, что вы будете использовать триггер базы данных при обновлении и удалении, чтобы вытолкнуть эти строки в таблицу TableName_Audit.
Это будет работать с ORM, даст вам вашу историю и не ухудшит производительность некоторых операций в этой таблице.Это хорошая идея или я что-то упускаю?
Что мы делаем, так это просто используем обычный ORM (спящий режим) и обрабатываем MVCC с помощью представлений + вместо триггеров.
Итак, есть представление v_emp, которое выглядит как обычная таблица, вы можете прекрасно вставлять и обновлять его, однако когда вы это делаете, триггеры фактически обрабатывают вставку правильных данных в базовую таблицу.
Нет..Я ненавижу этот метод :) Я бы выбрал API хранимых процедур, как предложил Тим.