MVCC データベース設計を最もよく処理できる ORM フレームワークはどれですか?

StackOverflow https://stackoverflow.com/questions/46709

  •  09-06-2019
  •  | 
  •  

質問

MVCC (Multi-Version Concurrency Control) を使用するデータベースを設計する場合は、「Islatest」などのブール値フィールドまたは整数「VersionId」のいずれかを使用してテーブルを作成します。更新は一切行わず、変更があった場合にのみ新しいレコードを挿入します。

MVCC は、詳細な履歴を必要とするアプリケーションの自動監査を提供し、更新ロックに関するデータベースへの負担も軽減します。欠点は、最新バージョンを取得するために必要な追加の句により、データ サイズが大幅に大きくなり、選択が遅くなることです。また、外部キーも複雑になります。

(注意してください、私は ない SQL Server のスナップショット分離レベルなどの RDBMS でのネイティブ MVCC サポートについて話します)

これについては、Stack Overflow の他の投稿で説明されています。[todo - リンク]

普及しているエンティティ/ORM フレームワーク (Linq to Sql、ADO.NET EF、Hibernate など) の中で、このタイプの設計を適切にサポートできるものはどれでしょうか?これは典型的な ActiveRecord 設計パターンに対する大きな変更であるため、世に出ているツールの大部分が、データ モデルでこの方法を選択する人を助けることができるかどうかはわかりません。私は外部キーがどのように処理されるかに特に興味があります。MVCC をサポートするために外部キーをデータ モデル化する最適な方法さえもわからないからです。

役に立ちましたか?

解決

MVCC 層を純粋に DB に実装し、ストアド プロシージャとビューを使用してデータ操作を処理することを検討するかもしれません。そうすれば、ストアド プロシージャとの間でマッピングできる適切な API を任意の ORM に提示でき、DB にデータ整合性の問題を処理させることができます (DB はそのためにほぼ構築されているため)。この方法を使用した場合は、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

そして、私の挿入(および更新と削除)はすべてストアドプロシージャ(テーブルごとに1つ)によって処理されました。

ストアド プロシージャは次のようになります…

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 並べ替え関数が含まれています。膨大な量のコードでした。

DB テーブルから VB オブジェクトへの 1 対 1...

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 がそのようなデータベース設計をうまく処理できるかどうかに興味があります。

Jeff とその Stack Overflow チームの残りのメンバーは、Stack Overflow の開発中に同様の問題に対処する必要があったに違いありません。編集された質問と回答の過去の改訂版は保存され、取得できます。

Jeff は、彼のチームが Linq to SQL と MS SQL Server を使用していたと述べたと思います。

彼らはこれらの問題にどのように対処したのだろうか。

私の知る限り、ORM フレームワークは CRUD コードを生成したいと考えているため、MVCC オプションを実装するように明示的に設計する必要があります。箱から出してすぐにそうできるものを私は知りません。

Entity Framework の観点から見ると、CSLA は永続性をまったく実装しません。必要な永続性を実装するために使用する「データ アダプター」インターフェイスを定義するだけです。したがって、コード生成 (CodeSmith など) テンプレートを設定して、MVCC データベース アーキテクチャに準拠した CSLA エンティティの CRUD ロジックを自動生成できます。

このアプローチは、おそらく CSLA だけでなく、あらゆるエンティティ フレームワークで機能しますが、CSLA では非常に「クリーン」な実装になります。

Envers プロジェクトをチェックしてください - JPA/Hibernate アプリケーションとうまく連携し、基本的にそれを実行します - 別のテーブルで各エンティティのさまざまなバージョンを追跡し、SVN のような可能性を提供します (「使用されている person のバージョンを教えてください 2008-11 -05...")

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

/イェンス

私はいつも、更新と削除時に db トリガーを使用して、これらの行を TableName_Audit テーブルにプッシュするだろうと思っていました。

これは ORM で動作し、履歴を提供し、そのテーブルでの選択のパフォーマンスを低下させることはありません。それは良い考えでしょうか、それとも私が何かを見逃しているのでしょうか?

私たちが行うことは、通常の ORM ( hibernate ) を使用し、トリガーの代わりにビュー + を使用して MVCC を処理することです。

したがって、通常のテーブルのように見える v_emp ビューがあり、問題なく挿入および更新できますが、これを行うと、トリガーが実際に正しいデータをベーステーブルに挿入します。

ない..私はこの方法が嫌いです :) Tim が提案したストアド プロシージャ API を使用します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top