Frage

Wenn Sie eine Datenbank für die Verwendung von MVCC (Multi-Version Concurrency Control) entwerfen, erstellen Sie Tabellen entweder mit einem booleschen Feld wie „IsLatest“ oder einer Ganzzahl „VersionId“ und führen nie Aktualisierungen durch, sondern fügen neue Datensätze nur ein, wenn sich etwas ändert.

MVCC bietet Ihnen eine automatische Prüfung für Anwendungen, die einen detaillierten Verlauf erfordern, und entlastet außerdem die Datenbank im Hinblick auf Update-Sperren.Die Nachteile bestehen darin, dass die Datenmenge viel größer wird und die Auswahl verlangsamt wird, da eine zusätzliche Klausel erforderlich ist, um die neueste Version zu erhalten.Außerdem werden Fremdschlüssel dadurch komplizierter.

(Beachten Sie, dass ich nicht wir reden über die native MVCC-Unterstützung in RDBMS wie der Snapshot-Isolationsstufe von SQL Server)

Dies wurde in anderen Beiträgen hier auf Stack Overflow besprochen.[Alles – Links]

Ich frage mich, welche der vorherrschenden Entitäts-/ORM-Frameworks (Linq to Sql, ADO.NET EF, Hibernate usw.) diese Art von Design sauber unterstützen können?Dies ist eine wesentliche Änderung des typischen ActiveRecord-Entwurfsmusters, daher bin ich mir nicht sicher, ob die meisten verfügbaren Tools jemandem helfen könnten, der sich für diesen Weg mit seinem Datenmodell entscheidet.Mich interessiert vor allem, wie mit Fremdschlüsseln umgegangen wird, da ich nicht einmal sicher bin, wie ich sie am besten datenmodellieren kann, um MVCC zu unterstützen.

War es hilfreich?

Lösung

Ich könnte darüber nachdenken, die MVCC-Ebene rein in der Datenbank zu implementieren und gespeicherte Prozesse und Ansichten für die Verarbeitung meiner Datenoperationen zu verwenden.Dann könnten Sie jedem ORM, der in der Lage ist, auf und von gespeicherten Prozessen zuzuordnen, eine vernünftige API bereitstellen und die Datenbank mit den Datenintegritätsproblemen befassen (da sie eigentlich dafür gebaut ist).Wenn Sie diesen Weg gegangen sind, sollten Sie sich vielleicht eine reinere Mapping-Lösung wie IBatis oder IBatis.net ansehen.

Andere Tipps

Ich habe eine Datenbank auf ähnliche Weise entworfen (nur INSERTs – keine UPDATEs, keine DELETEs).

Fast alle meiner SELECT-Abfragen betrafen Ansichten nur der aktuellen Zeilen für jede Tabelle (höchste Revisionsnummer).

Die Ansichten sahen so aus ...

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

Und meine Einfügungen (sowie Aktualisierungen und Löschungen) wurden alle von gespeicherten Prozeduren (eine pro Tabelle) verarbeitet.

Die gespeicherten Prozeduren sahen so aus ...

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
        )

Revisionsnummern wurden im Visual Basic-Code pro Transaktion verarbeitet …

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

Ich habe für jede Tabelle ein Objekt erstellt, jedes mit Konstruktoren, Instanzeigenschaften und -methoden, Befehlen zum Erstellen, Aktualisieren und Löschen, einer Reihe von Suchfunktionen und IComparable-Sortierfunktionen.Es war eine riesige Menge Code.

Eins-zu-eins-DB-Tabelle zu VB-Objekt ...

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

Ein solches System behält alle früheren Versionen jeder Zeile bei, kann jedoch sehr mühsam zu verwalten sein.

VORTEILE:

  • Gesamte Geschichte erhalten
  • Weniger gespeicherte Prozeduren

NACHTEILE:

  • verlässt sich für die Datenintegrität auf datenbankunabhängige Anwendungen
  • riesige Menge an Code, der geschrieben werden muss
  • Keine Fremdschlüssel in der Datenbank verwaltet (Auf Wiedersehen, automatische Objektgenerierung im Linq-to-SQL-Stil)
  • Ich habe immer noch keine gute Benutzeroberfläche entwickelt, um die gesamte alte Versionierung abzurufen.

ABSCHLUSS:

  • Ohne eine benutzerfreundliche, sofort einsatzbereite ORM-Lösung würde ich mir bei einem neuen Projekt nicht so viel Mühe machen.

Ich bin gespannt, ob das Microsoft Entity Framework mit solchen Datenbankdesigns gut zurechtkommt.

Jeff und der Rest des Stack Overflow-Teams mussten sich bei der Entwicklung von Stack Overflow mit ähnlichen Problemen auseinandersetzen:Frühere Überarbeitungen bearbeiteter Fragen und Antworten werden gespeichert und sind abrufbar.

Ich glaube, Jeff hat angegeben, dass sein Team Linq to SQL und MS SQL Server verwendet hat.

Ich frage mich, wie sie mit diesen Problemen umgegangen sind.

Soweit ich weiß, möchten ORM-Frameworks den CRUD-Code für Sie generieren, daher müssten sie explizit für die Implementierung einer MVCC-Option konzipiert sein;Ich kenne keinen, der das sofort macht.

Aus Sicht des Entity-Frameworks implementiert CSLA überhaupt keine Persistenz für Sie – es definiert lediglich eine „Datenadapter“-Schnittstelle, die Sie verwenden, um die benötigte Persistenz zu implementieren.So können Sie Codegenerierungsvorlagen (CodeSmith usw.) einrichten, um automatisch CRUD-Logik für Ihre CSLA-Entitäten zu generieren, die mit einer MVCC-Datenbankarchitektur einhergehen.

Dieser Ansatz würde höchstwahrscheinlich mit jedem Entity-Framework funktionieren, nicht nur mit CSLA, aber es wäre eine sehr „saubere“ Implementierung in CSLA.

Schauen Sie sich das Envers-Projekt an – funktioniert gut mit JPA/Hibernate-Anwendungen und erledigt das im Grunde genommen für Sie – verfolgt verschiedene Versionen jeder Entität in einer anderen Tabelle und bietet Ihnen SVN-ähnliche Möglichkeiten („Gib mir die Version von Person, die 2008–11 verwendet wird“) -05...")

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

/Jens

Ich dachte immer, Sie würden beim Aktualisieren und Löschen einen Datenbanktrigger verwenden, um diese Zeilen in eine TableName_Audit-Tabelle zu verschieben.

Das würde mit ORMs funktionieren, Ihnen Ihren Verlauf liefern und die ausgewählte Leistung in dieser Tabelle nicht beeinträchtigen.Ist das eine gute Idee oder übersehe ich etwas?

Wir verwenden einfach ein normales ORM (Ruhezustand) und verarbeiten den MVCC mit Ansichten + anstelle von Triggern.

Es gibt also eine v_emp-Ansicht, die einfach wie eine normale Tabelle aussieht. Sie können sie problemlos einfügen und aktualisieren. Wenn Sie dies tun, übernehmen die Trigger jedoch das tatsächliche Einfügen der richtigen Daten in die Basistabelle.

Nicht..Ich hasse diese Methode :) Ich würde eine API für gespeicherte Prozeduren verwenden, wie von Tim vorgeschlagen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top