在设计数据库以使用MVCC(多版本并发控制)时,您可以使用类似“IsLatest”的布尔字段或整数“VersionId”创建表,并且您永远不会进行任何更新,您只需在插入新记录时事情会改变的。
MVCC为您提供需要详细历史记录的应用程序的自动审核,并且还减轻了数据库对更新锁的压力。缺点是,由于获得最新版本所需的额外条款,它会使您的数据大小更大并减慢选择速度。它还使外键更复杂。
(请注意,我不在讨论SQL Server的快照隔离级别等RDBMS中的本机MVCC支持)
这已在Stack Overflow上的其他帖子中讨论过。 [todo - links]
我想知道,哪些流行的实体/ ORM框架(Linq to Sql,ADO.NET EF,Hibernate等)可以干净地支持这种类型的设计?这是对典型ActiveRecord设计模式的重大改变,因此我不确定那里的大多数工具是否可以帮助那些决定使用他们的数据模型走这条路线的人。我对如何处理外键特别感兴趣,因为我甚至不确定将数据建模为支持MVCC的最佳方式。
答案 0 :(得分:3)
我可能会考虑纯粹在数据库中实现MVCC层,使用存储过程和视图来处理我的数据操作。然后,您可以向任何能够映射到存储过程和从存储过程映射的ORM提供合理的API,并且您可以让DB处理数据完整性问题(因为它几乎是为此构建的)。如果你这样做,你可能想看一个更纯粹的映射解决方案,如IBatis或IBatis.net。
答案 1 :(得分:3)
我设计了一个类似的数据库(只有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
这样的系统会保留每行的所有过去版本,但管理起来真的很痛苦。
<强>优点:强>
<强> CONS:强>
<强>结论:强>
我很好奇微软实体框架能否很好地处理这样的数据库设计。
Jeff和Stack Overflow团队的其他成员必须在开发Stack Overflow时处理类似的问题:已编辑的问题和答案的过去版本已保存并可检索。
我相信Jeff已经声明他的团队使用Linq来SQL和MS SQL Server。
我想知道他们是如何处理这些问题的。
答案 2 :(得分:1)
据我所知,ORM框架将要为您生成CRUD代码,因此必须明确设计它们以实现MVCC选项;我不知道任何这样做是开箱即用的。
从实体框架的角度来看,CSLA根本没有为您实现持久性 - 它只是定义了一个“数据适配器”接口,您可以使用它来实现所需的持久性。因此,您可以设置代码生成(CodeSmith等)模板,以便为您的CSLA实体自动生成CRUD逻辑,并与MVCC数据库体系结构一起使用。
这种方法适用于任何实体框架,很可能不仅仅是CSLA,但它在CSLA中将是一个非常“干净”的实现。
答案 3 :(得分:1)
查看Envers项目 - 适用于JPA / Hibernate应用程序并且基本上可以帮助您 - 在另一个表中跟踪每个实体的不同版本并为您提供类似SVN的可能性(“Gimme正在使用的人的版本2008-11-05 ...“)
/延
答案 4 :(得分:0)
我总是认为你在更新和删除时使用数据库触发器将这些行推送到TableName_Audit表中。
这可以与ORM一起使用,为您提供历史记录,并且不会在该表上选择性能。这是个好主意还是我错过了什么?
答案 5 :(得分:0)
我们所做的只是使用普通的ORM(休眠)并使用views +而不是触发器来处理MVCC。
所以,有一个v_emp视图,它看起来像一个普通的表,你可以很好地插入和更新它,但是当你这样做时,触发器会处理实际上将正确的数据插入到基表中。
不..我讨厌这个方法:)我会按照Tim的建议使用存储过程API。