Какая платформа ORM лучше всего справляется с дизайном базы данных MVCC? - PullRequest
9 голосов
/ 05 сентября 2008

При разработке базы данных для использования MVCC (Multi-Version Concurrency Control) вы создаете таблицы либо с логическим полем, таким как «IsLatest», либо с целочисленным «VersionId», и вы никогда не делаете никаких обновлений, вы только вставляете новые записи, вещи меняются.

MVCC обеспечивает автоматический аудит приложений, для которых требуется подробная история, а также снижает нагрузку на базу данных в связи с обновлением блокировок. Недостатки в том, что это делает ваш размер данных намного больше и замедляет выбор, из-за дополнительного условия, необходимого для получения последней версии. Это также делает внешние ключи более сложными.

(Обратите внимание, что я не говорю о собственной поддержке MVCC в РСУБД, такой как уровень изоляции моментальных снимков SQL Server)

Это обсуждалось в других постах здесь, о переполнении стека. [todo - ссылки]

Мне интересно, какая из распространенных структур сущностей / ORM (Linq to Sql, ADO.NET EF, Hibernate и т. Д.) Может чисто поддерживать этот тип дизайна? Это серьезное изменение в типичном шаблоне проектирования ActiveRecord, поэтому я не уверен, что большинство существующих инструментов могут помочь кому-то, кто решит пойти по этому пути со своей моделью данных. Меня особенно интересует, как будут обрабатываться внешние ключи, потому что я даже не уверен в наилучшем способе их моделирования данных для поддержки MVCC.

Ответы [ 6 ]

3 голосов
/ 08 сентября 2008

Я спроектировал базу данных аналогичным образом (только INSERT & mdash; нет UPDATE, нет DELETE).

Почти все мои запросы SELECT были против просмотра только текущих строк для каждой таблицы (наибольшее число ревизий).

Вид был похож на это & ​​hellip;

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

И мои вставки (а также обновления и удаления) были обработаны хранимыми процедурами (по одной на таблицу).

Хранимые процедуры выглядят так: & hellip;

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 & hellip;

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

Я создал объект для каждой таблицы, каждый из которых имеет конструкторы, свойства и методы экземпляра, команды create-update-delete, набор функций поиска и функции сортировки 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-SQL)
  • У меня до сих пор нет хорошего пользовательского интерфейса для извлечения всего того, что сохранилось после создания версий.

ВЫВОД:

  • Я бы не стал сталкиваться с такими проблемами в новом проекте без какого-либо простого в использовании готового решения ORM.

Мне любопытно, может ли Microsoft Entity Framework хорошо обрабатывать такие конструкции баз данных.

Джефф и остальная часть этой команды переполнения стека должны были иметь дело с подобными проблемами при разработке переполнения стека: прошлые версии отредактированных вопросов и ответов сохраняются и могут быть извлечены.

Полагаю, Джефф заявил, что его команда использовала Linq для SQL и MS SQL Server.

Интересно, как они справились с этими проблемами.

3 голосов
/ 06 сентября 2008

Я мог бы рассмотреть возможность реализации уровня MVCC исключительно в БД, используя хранимые процедуры и представления для обработки моих операций с данными. Тогда вы могли бы представить разумный API для любого ORM, который был бы способен отображать и хранить хранимые процессы, и вы могли бы позволить БД справляться с проблемами целостности данных (так как она в значительной степени создана для этого). Если вы пошли по этому пути, возможно, вы захотите взглянуть на более чистое картографическое решение, такое как IBatis или IBatis.net.

1 голос
/ 06 ноября 2008

Ознакомьтесь с проектом Envers - хорошо работает с приложениями JPA / Hibernate и, в основном, делает это за вас - отслеживает различные версии каждого объекта в другой таблице и дает вам SVN-подобные возможности («Дай мне версию используемого человека 2008-11-05 ... ")

http://www.jboss.org/envers/

/ Jens

1 голос
/ 05 сентября 2008

Насколько я знаю, фреймворки ORM захотят сгенерировать для вас код CRUD, поэтому они должны быть явно разработаны для реализации опции MVCC; Я не знаю ни одного, кто делает это из коробки.

С точки зрения платформы Entity CSLA вообще не реализует постоянство для вас - оно просто определяет интерфейс «Адаптер данных», который вы используете для реализации любого необходимого вам постоянства. Таким образом, вы можете настроить шаблоны генерации кода (CodeSmith и т. Д.) Для автоматической генерации логики CRUD для ваших объектов CSLA, которые соответствуют архитектуре базы данных MVCC.

Этот подход будет работать с любой структурой сущностей, скорее всего, не только с CSLA, но это будет очень "чистая" реализация в CSLA.

0 голосов
/ 06 ноября 2008

Что мы делаем, так это просто используем обычный ORM (режим гибернации) и обрабатываем MVCC с представлениями + вместо триггеров.

Итак, есть представление v_emp, которое выглядит просто как обычная таблица, вы можете прекрасно вставлять и обновлять ее, хотя, когда вы делаете это, дескриптор триггера фактически вставляет правильные данные в базовую таблицу.

Не .. Я ненавижу этот метод :) Я бы пошел с API хранимых процедур, как предложено Тимом.

0 голосов
/ 08 сентября 2008

Я всегда думал, что вы будете использовать триггер db при обновлении и удалении, чтобы вытолкнуть эти строки в таблицу TableName_Audit.

Это работало бы с ORM, давало бы вам историю и не влияло бы на выбор производительности для этой таблицы. Это хорошая идея или я что-то упустил?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...