Question

Lors de la conception d'une base de données à utiliser MVCC (Version Multi-Contrôle de la Simultanéité), vous créez des tables avec un champ booléen comme "IsLatest" ou un entier "VersionId", et vous ne jamais faire les mises à jour, vous n'insérer de nouveaux enregistrements lorsque les choses changent.

MVCC vous donne automatiques de vérification pour les applications qui nécessitent un historique détaillé, et il soulage aussi la pression sur la base de données en ce qui concerne la mise à jour des serrures.Les inconvénients sont qu'il rend vos données de taille beaucoup plus grande et ralentit sélectionne, en raison de la clause supplémentaire nécessaire pour obtenir la dernière version.Il rend également les clés étrangères plus compliqué.

(Notez que je suis pas parler de la maternelle MVCC soutien dans des Sgbdr tels que SQL Server, du niveau d'isolation snapshot)

Ce point a été discuté dans d'autres posts ici sur un Débordement de Pile.[todo - liens]

Je me demande qui de la forte prévalence de l'entité/ORM cadres (Linq to Sql, ADO.NET EF, Hibernate, etc) peut proprement en charge ce type de conception?C'est un changement majeur pour le typique ActiveRecord modèle de conception, donc je ne sais pas si la majorité des outils qui sont là-bas pourrait aider quelqu'un qui décide d'aller dans cette voie avec leur modèle de données.Je suis particulièrement intéressé par la façon dont les clés étrangères sont manipulés, parce que je ne suis même pas sûr de la meilleure façon de modèle de données à l'appui de MVCC.

Était-ce utile?

La solution

Je pourrais envisager de mettre en œuvre la MVCC niveau purement dans la base de données, stockées à l'aide de proc et des vues sur le traitement de mes données.Ensuite, vous pouvez présenter une raisonnable API pour toute ORM qui a été capable de cartographie et d'stockées procs, et vous pouvez laissez la DB traiter les problèmes d'intégrité des données (depuis qu'il est à peu près à construire pour que).Si vous êtes allé de cette façon, vous voudrez peut-être examiner d'une façon plus pure solution de Cartographie comme IBatis ou IBatis.net.

Autres conseils

J'ai conçu une base de données de la même façon (Insère uniquement — pas de Mises à jour, pas de Suppressions).

Presque toutes mes requêtes de sélection ont été à l'encontre de vues que les lignes en cours pour chaque table (plus haut numéro de révision).

Le point de vue ressemblait à ça...

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

Et mes inserts (et les mises à jour et suppressions) ont été traités par des procédures stockées (un par table).

Les procédures stockées ressemblait à ça...

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
        )

Les numéros de révision ont été traités par transaction dans le code 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

J'ai créé un objet pour chaque tableau, chaque avec des constructeurs, des propriétés d'instance et des méthodes, créez-mise à jour-la suppression des commandes, un tas de fonctions du finder, et IComparable des fonctions de tri.C'était une énorme quantité de code.

One-to-One de la table DB à VB objet...

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

Un tel système permet de conserver toutes les dernières versions de chaque ligne, mais peuvent être une vraie douleur à gérer.

POUR:

  • Total de l'histoire préservée
  • Moins de procédures stockées

INCONVÉNIENTS:

  • s'appuie sur la non-application de base de données pour l'intégrité des données
  • énorme quantité de code à écrire
  • Pas de clés étrangères gérées dans la base de données (au revoir automatique Linq-to-SQL-objet de style de génération)
  • Je n'ai toujours pas venir avec une bonne interface utilisateur pour récupérer tous qui a conservé le passé, la gestion des versions.

CONCLUSION:

  • Je ne voudrais pas aller à de tels problèmes sur un nouveau projet sans facile à utiliser, out-of-the-box ORM solution.

Je suis curieux de savoir si Entity Framework Microsoft peut gérer une telle base de données de modèles bien.

Jeff et le reste de Débordement de Pile de l'équipe doit avoir eu à traiter avec les mêmes questions, tout en développant de Débordement de Pile:Dernières révisions de révisions, les questions et les réponses sont enregistrées et consultables.

Je crois que Jeff a déclaré que son équipe a utilisé Linq to SQL et MS SQL Server.

Je me demande comment ils ont traité ces questions.

Au meilleur de ma connaissance, ORM cadres vont vouloir générer le CRUD code pour vous, de sorte qu'ils doivent être explicitement conçu pour mettre en œuvre un MVCC option;Je ne sais pas du tout que le faire hors de la boîte.

À partir d'un Entity framework point de vue, l'AAPC ne pas mettre en œuvre la persistance de vous à tous -- c'est juste définit un "Adaptateur de Données" de l'interface que vous utilisez pour mettre en œuvre la persistance dont vous avez besoin.Donc, vous pourriez mettre en place la génération de code (CodeSmith, etc.) modèles d'auto-générer CRUD logique de votre AAPC entités qui vont de pair avec une MVCC architecture de base de données.

Cette approche serait de travailler avec une entité cadre, le plus probable, et pas seulement l'AAPC, mais ce serait une très "propre" mise en œuvre dans l'AAPC.

Découvrez l'Envers du projet - les œuvres de nice avec JPA/Hibernate applications et ne fond que pour vous - assure le suivi des différentes versions de chaque Entité dans une autre table et vous donne SVN-comme possibilités ("donne Moi la version de la Personne utilisée 2008-11-05...")

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

/Jens

J'ai toujours pensé que vous souhaitez utiliser une db de déclenchement de la mise à jour et de suppression de pousser ces lignes dans un TableName_Audit table.

Que j'avais de travailler avec des Formulaires, vous donner à votre histoire et de ne pas décimer sélectionnez la performance sur la table.Est-ce une bonne idée ou alors j'ai loupé quelque chose?

Ce que nous faisons, c'est juste de l'utilisation normale de l'ORM ( hibernate ) et de gérer la MVCC avec vue sur + au lieu de déclencheurs.

Donc, il y a un v_emp point de vue, qui ressemble à un tableau normal, vous pouvez insérer et mettre à jour en bien, quand vous faites cela, les déclencheurs de la poignée fait de l'insertion de données dans la table de base.

Non..Je déteste cette méthode :) j'irais avec un API de procédure stockée comme suggéré par Tim.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top