2008-09-05 5 views
8

Al diseñar una base de datos para usar MVCC (Control de Concurrencia de varias versiones), crea tablas con un campo booleano como "IsLatest" o un entero "VersionId", y nunca hace ninguna actualización, solo inserta registros nuevos cuando las cosas cambio.¿Qué marco ORM puede manejar mejor un diseño de base de datos MVCC?

MVCC le ofrece una auditoría automática para las aplicaciones que requieren un historial detallado, y también alivia la presión sobre la base de datos con respecto a los bloqueos de actualización. Las desventajas son que hace que su tamaño de datos sea mucho más grande y ralentiza las selecciones, debido a la cláusula adicional necesaria para obtener la última versión. También hace que las claves foráneas sean más complicadas.

(Tenga en cuenta que no estoy hablar sobre el apoyo MVCC nativo en los RDBMS como el nivel de aislamiento de instantánea de SQL Server)

Esto ha sido discutido en otros puestos aquí en desbordamiento de pila. [todo - enlaces]

Me pregunto, ¿cuál de los marcos entidad/ORM prevalentes (Linq a Sql, ADO.NET EF, Hibernate, etc.) puede apoyar este tipo de diseño? Este es un cambio importante en el patrón de diseño de ActiveRecord típico, por lo que no estoy seguro de si la mayoría de las herramientas que existen podrían ayudar a alguien que decida ir por esta ruta con su modelo de datos. Estoy particularmente interesado en cómo se manejarán las claves externas, porque ni siquiera estoy seguro de la mejor manera de modelar los datos para admitir MVCC.

Respuesta

3

Podría considerar la implementación del nivel de MVCC puramente en la base de datos, utilizando procesos y vistas almacenados para manejar mis operaciones de datos. Luego, podría presentar una API razonable para cualquier ORM que fuera capaz de mapear desde y hacia procs almacenados, y podría dejar que la DB se ocupe de los problemas de integridad de los datos (ya que se trata de una compilación para eso). Si fue por este camino, es posible que desee ver una solución de mapas más pura como IBatis o IBatis.net.

1

Según mi leal saber y entender, los marcos ORM van a querer generar el código CRUD para usted, por lo que deberían estar explícitamente diseñados para implementar una opción MVCC; No conozco ninguno que lo haga de la caja.

Desde el punto de vista del marco de entidad, CSLA no implementa la persistencia para usted en absoluto; solo define una interfaz de "Adaptador de datos" que utiliza para implementar cualquier persistencia que necesite. De modo que podría configurar plantillas de generación de código (CodeSmith, etc.) para generar automáticamente la lógica CRUD para sus entidades CSLA que vayan junto con una arquitectura de base de datos MVCC.

Este enfoque funcionaría con cualquier marco de entidad, muy probablemente, no solo CSLA, sino que sería una implementación muy "limpia" en CSLA.

3

Diseñé una base de datos de manera similar (solo INSERTs — no hay ACTUALIZACIONES, no hay SUPRIMIR).

Casi todas mis consultas SELECT estaban en contra de las vistas de solo las filas actuales para cada tabla (número de revisión más alto).

Las vistas se veían así & 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 

Y mis inserciones (y las actualizaciones y eliminaciones) fueron manejadas por procedimientos almacenados (uno por tabla).

Los procedimientos almacenados se parecían a esto & 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 
     ) 

Los números de revisión se manejaron por transacción en el código de 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 

creé un objeto para cada mesa, cada uno con constructores, propiedades de instancia y métodos, crear-update-eliminar comandos, un montón de funciones del buscador, y funciones de ordenación IComparable. Fue una gran cantidad de código.

uno-a-uno de la tabla db al objeto de 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 

Tal sistema conserva todas las versiones anteriores de cada fila, pero puede ser un verdadero dolor de manejar.

PROS:

  • historia total conservados
  • menos procedimientos almacenados

Contras:

  • se basa en la aplicación de base de datos no para la integridad de los datos
  • gran cantidad de código que se ha escrito
  • No hay claves externas gestionadas dentro de la base de datos (generación automática de despedida objeto LINQ al estilo SQL)
  • todavía no he llegado con una buena interfaz de usuario para recuperar todos que conserva el control de versiones pasado.

CONCLUSIÓN:

  • yo no iría a tales problemas en un nuevo proyecto sin alguna solución ORM-fácil de usar fuera de la caja.

Tengo curiosidad por saber si Microsoft Entity Framework puede manejar bien estos diseños de bases de datos.

Jeff y el resto del equipo de Stack Overflow debe haber tenido que lidiar con problemas similares al desarrollar Stack Overflow: las revisiones anteriores de las preguntas y respuestas editadas se guardan y recuperan.

Creo que Jeff ha declarado que su equipo usó Linq para SQL y MS SQL Server.

Me pregunto cómo manejaron estos problemas.

0

Siempre pensé que usaría un disparador db en la actualización y eliminaría esas filas en una tabla TableName_Audit.

Eso funcionaría con ORM, le daría su historial y no reduciría el rendimiento de selección en esa tabla. ¿Es una buena idea o me estoy perdiendo algo?

1

Eche un vistazo al proyecto Envers - funciona bien con aplicaciones JPA/Hibernate y básicamente lo hace por usted - realiza un seguimiento de las diferentes versiones de cada Entidad en otra tabla y le da posibilidades SVN ("Dame la versión de Person siendo 2008-11-05 utilizado ... ")

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

/Jens

+0

Actualmente, envers se integra con hibernate – Sam

0

lo que hacemos, es simplemente utilizar un ORM normal (hibernación) y manejar la MVCC con vistas + en lugar de los factores desencadenantes.

Por lo tanto, hay una vista v_emp, que simplemente se ve como una tabla normal, puede insertar y actualizar bien, sin embargo, al hacer esto, los desencadenantes se encargan de insertar los datos correctos en la tabla base.

No ... Odio este método :) Iría con una API de procedimiento almacenado como lo sugiere Tim.

Cuestiones relacionadas