哪个ORM框架可以最好地处理MVCC数据库设计?
在设计数据库以使用MVCC(多版本并发控制)时,我们创建的表具有布尔字段(例如" IsLatest"或者整数" VersionId"),并且我们从不进行任何更新,仅在发生变化时才插入新记录。
MVCC为我们提供了需要详细历史记录的应用程序的自动审核,并且还减轻了数据库在更新锁方面的压力。缺点是,由于要获取最新版本,必须使用额外的子句,这会使数据量大得多并减慢选择速度。这也使外键更加复杂。
(请注意,我并不是在谈论RDBMS中的本地MVCC支持,例如SQL Server的快照隔离级别)
在堆栈溢出的其他文章中对此进行了讨论。 [待办事项链接]
我想知道,哪个流行的实体/ ORM框架(Linq to Sql,ADO.NET EF,Hibernate等)可以干净地支持这种类型的设计?这是对典型ActiveRecord设计模式的重大更改,因此我不确定那里提供的大多数工具是否可以帮助决定使用其数据模型走这条路的人。我对如何处理外键特别感兴趣,因为我什至不知道对它们进行数据建模以支持MVCC的最佳方法。
解决方案
回答
据我所知,ORM框架将要为我们生成CRUD代码,因此必须明确设计它们以实现MVCC选项。我不知道有什么方法可以立即使用。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
从实体框架的角度来看,CSLA根本不会为我们实现持久性-它只是定义了一个"数据适配器"接口,我们可以使用该接口来实现所需的任何持久性。因此,我们可以设置代码生成(CodeSmith等)模板,以为与MVCC数据库体系结构一起使用的CSLA实体自动生成CRUD逻辑。
这种方法适用于任何实体框架,很可能不仅适用于CSLA,而且在CSLA中将是非常"干净"的实现。
回答
我可能会考虑使用存储的proc和视图来处理我的数据操作,从而完全在DB中实现MVCC层。然后,我们可以向任何能够与存储的proc进行映射的ORM提供一个合理的API,并且可以让DB处理数据完整性问题(因为它是为此而构建的)。如果我们采用这种方式,则可能需要查看更纯的Mapping解决方案,例如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
我为每个表创建了一个对象,每个对象都有构造函数,实例属性和方法,create-update-delete命令,一堆查找程序函数和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是否可以很好地处理此类数据库设计。
Jeff和Stack Overflow团队的其余成员必须在开发Stack Overflow时必须处理类似的问题:已编辑的问题和答案的先前修订版本已保存并可以检索。
我相信Jeff已经说过,他的团队将Linq用于SQL和MS SQL Server。
我不知道他们如何处理这些问题。
回答
我一直认为我们会在更新和删除时使用db触发器,以将这些行推送到TableName_Audit表中。
这将与ORM一起使用,为我们提供历史记录,并且不会降低该表的选择性能。这是个好主意还是我错过了什么?
回答
看看Envers项目与JPA / Hibernate应用程序配合得很好,基本上可以做到这一点,因为我们可以在另一个表中跟踪每个实体的不同版本,并为我们提供类似SVN的可能性("给我使用的Person版本2008-11-05 ...")
http://www.jboss.org/envers/
/詹斯
回答
我们所做的只是使用普通的ORM(hibernate)并使用views +而不是触发器来处理MVCC。
因此,有一个v_emp视图,它看起来像一个普通表,我们可以对其进行精细的插入和更新,但是当执行此操作时,触发器将实际将正确的数据插入基表。
不。我讨厌这种方法:)我会按照Tim的建议使用存储过程API。