Какая структура ORM лучше всего подходит для проектирования базы данных MVCC?

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

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

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

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

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

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
9
0
1 954
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

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

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

Ответ принят как подходящий

Я мог бы рассмотреть возможность реализации уровня 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.

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

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

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

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

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

/ Йенс

В настоящее время envers интегрирован с гибернацией

Sam 29.05.2013 00:12

Мы просто используем обычный ORM (спящий режим) и обрабатываем MVCC с помощью view + вместо триггеров.

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

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

Другие вопросы по теме