数据门户:实现移动对象与数据访问的核心机制
在软件开发中,实现移动对象和高效的数据访问是构建复杂应用的关键。数据门户作为一种核心机制,在这方面发挥着重要作用。它结合了多种设计模式和技术,为开发者提供了强大而灵活的功能。
数据门户的设计理念
数据门户的设计旨在解决面向对象编程中的一些挑战。一方面,业务逻辑需要为用户提供丰富的交互体验,这要求对象在客户端运行;另一方面,后端处理通常是非交互性的,需要在应用服务器上执行。移动对象的概念应运而生,它允许对象在客户端和服务器之间移动,从而兼顾了用户交互和后端处理的需求。
数据门户的设计还强调业务逻辑和数据访问的逻辑分离。这可以通过多种方式实现,例如将数据访问代码放入预定义的方法、单独的数据访问类或对象工厂类中。这种分离有助于提高代码的可维护性和可扩展性。
一致的编码模型
数据门户为根对象和子对象提供了一致的编码模型。根对象通常实现
DataPortal_XYZ
方法,如
DataPortal_Create()
和
DataPortal_Fetch()
,这些方法由数据门户在业务对象的工厂方法调用
DataPortal.Create()
和
DataPortal.Fetch()
时触发。子对象则实现
Child_XYZ
方法,如
Child_Create()
和
Child_Fetch()
,由数据门户在子对象的工厂方法调用
DataPortal.CreateChild()
和
DataPortal.FetchChild()
时调用。
此外,字段管理器在更新子对象时发挥了重要作用。在父业务对象的
DataPortal_Insert()
或
DataPortal_Update()
方法中,可以使用
FieldManager.UpdateChildren()
方法来更新所有子对象。
BusinessListBase
类也提供了预建的
Child_Update()
实现,用于更新集合中的已删除项和活动项。
通道适配器和消息路由器模式
数据门户结合了通道适配器和消息路由器两种常见的设计模式。通道适配器模式为 n 层应用提供了极大的灵活性,允许应用在 2 层和 3 层模型之间切换,以及在各种网络协议之间选择。消息路由器模式则通过提供一个明确的、单一的入口点,将客户端调用路由到服务器端的适当对象,从而解耦了客户端和服务器。
以下是实现通道适配器所需的类型:
| 类型 | 命名空间 | 描述 |
| — | — | — |
| MethodCaller | Csla.Reflection | 封装反射和动态方法调用的实用类,用于查找方法信息并调用方法 |
| CallMethodException | Csla.Reflection | 数据门户在调用数据访问方法时发生异常时抛出的异常 |
| RunLocalAttribute | Csla | 应用于业务对象的数据访问方法的属性,用于强制数据门户始终在客户端运行该方法,绕过配置设置 |
| DataPortalEventArgs | Csla | 作为
Csla.DataPortal
引发的事件的参数传递的
EventArgs
子类 |
| DataPortal | Csla | 数据门户基础设施的主要入口点,供业务开发人员使用 |
| DataPortal(Of T) | Csla | 用于异步行为的数据门户的主要入口点,供业务开发人员使用 |
| DataPortal | Csla.Server | 服务器上消息路由器功能的门户,作为所有服务器通信的单一入口点 |
| IDataPortalServer | Csla.Server | 定义数据门户主机对象所需方法的接口 |
| IDataPortalProxy | Csla.DataPortalClient | 定义客户端数据门户代理对象所需方法的接口 |
| WcfProxy | Csla.DataPortalClient | 使用 WCF 与在 IIS、WAS 或自定义主机(通常是 Windows 服务)中运行的 WCF 服务器进行通信 |
| WcfPortal | Csla.Server.Hosts | 由 IIS、WAS 或自定义主机在服务器上公开,由
WcfProxy
调用 |
| LocalProxy | Csla.DataPortalClient | 将服务器端数据门户组件直接加载到客户端内存中,并在客户端进程中运行所有“服务器端”操作 |
| RemotingProxy | Csla.DataPortalClient | 使用 .NET Remoting 与在 IIS 或自定义主机(通常是 Windows 服务)中运行的远程服务器进行通信 |
| RemotingPortal | Csla.Server.Hosts | 由 IIS 或自定义主机在服务器上公开,由
RemotingProxy
调用 |
| EnterpriseServicesProxy | Csla.DataPortalClient | 使用 Enterprise Services (DCOM) 与在 COM+ 中运行的服务器进行通信 |
| EnterpriseServicesPortal | Csla.Server.Hosts | 由 Enterprise Services 在服务器上公开,由
EnterpriseServicesProxy
调用 |
| WebServicesProxy | Csla.DataPortalClient | 使用 Web 服务与在 IIS 中托管的服务进行通信 |
| WebServicePortal | Csla.Server.Hosts | 由 IIS 作为 Web 服务在服务器上公开,由
WebServicesProxy
调用 |
分布式事务支持
在处理数据库事务时,数据门户允许开发者为业务对象的数据访问方法选择不同的事务技术,包括手动处理事务、使用 Enterprise Services (COM+) 事务或使用
System.Transactions
。开发者可以通过
Csla.TransactionalAttribute
来指定偏好。
Csla.Server.DataPortal
对象根据数据访问方法上的
Transactional
属性值来决定如何路由客户端调用。调用可以通过以下三种方式之一进行路由:
-
手动选项
:直接路由到
DataPortalSelector
。
-
EnterpriseServices 选项
:通过
ServicedDataPortal
进行路由。
-
TransactionScope 选项
:通过
TransactionalDataPortal
进行路由。
消息路由器的实现
消息路由器功能在通道适配器之后发挥作用。客户端调用通过通道适配器到达服务器后,最终由
Csla.Server.DataPortal
处理。该对象将调用路由到
DataPortalSelector
对象,可能会先建立分布式事务上下文。
DataPortalSelector
类根据业务对象是否具有
ObjectFactory
属性来决定将调用路由到
SimpleDataPortal
还是
FactoryDataPortal
。如果没有
ObjectFactory
属性,所有客户端调用最终由
SimpleDataPortal
处理;否则,由
FactoryDataPortal
处理。
以下是
DataPortalSelector
的
Fetch
方法示例:
Public Function Fetch(ByVal objectType As Type, ByVal criteria As Object, _
ByVal context As DataPortalContext) As DataPortalResult
Try
context.FactoryInfo = ObjectFactoryAttribute.GetObjectFactoryAttribute( _
objectType)
If context.FactoryInfo Is Nothing Then
Dim dp = New SimpleDataPortal()
Return dp.Fetch(objectType, criteria, context)
Else
Dim dp = New FactoryDataPortal()
Return dp.Fetch(objectType, criteria, context)
End If
Catch generatedExceptionName As DataPortalException
Throw
Catch ex As Exception
Throw New DataPortalException("DataPortal.Fetch " & _
My.Resources.FailedOnServer, ex, New DataPortalResult())
End Try
End Function
上下文和位置透明性
数据门户的另一个重要功能是管理上下文信息,以提供客户端和服务器之间的位置透明性。它允许业务应用在每次数据门户调用时在客户端和服务器之间传递数据,包括安全和文化信息。
DataPortalContext
对象用于在客户端和服务器之间传递上下文数据,包括
GlobalContext
、
ClientContext
、
Principal
、
IsRemotePortal
、
ClientCulture
和
ClientUICulture
。
Csla.Server.DataPortal
使用该对象中的数据来设置服务器的上下文,使其与客户端匹配。
Csla.Server.DataPortalResult
对象在成功调用时将服务器上创建、检索或更新的业务对象和全局上下文集合返回给客户端。在服务器端发生异常时,
Csla.Server.DataPortalException
对象将异常细节和相关上下文数据返回给客户端。
数据门户的详细实现
通道适配器的实现
数据门户通过
Csla.DataPortal
模块向业务开发者暴露。该模块实现了一组共享方法,用于创建、检索、更新或删除对象。所有通道适配器行为都隐藏在
Csla.DataPortal
类后面。
RunLocal
属性允许业务开发者标记数据访问方法,以强制数据门户在客户端运行该方法,而不管应用的总体配置如何。例如:
<RunLocal()> _
Private Sub DataPortal_Create(ByVal criteria As Criteria)
' set default values here
End Sub
Csla.DataPortal
模块定义了五个主要方法,用于处理根对象的操作:
| 方法 | 描述 |
| — | — |
| Create() | 调用
Csla.Server.DataPortal
,然后调用
DataPortal_Create()
方法(或对象工厂中的
Create()
方法) |
| Fetch() | 调用
Csla.Server.DataPortal
,然后调用
DataPortal_Fetch()
方法(或对象工厂中的
Fetch()
方法) |
| Update() | 调用
Csla.Server.DataPortal
,然后调用
DataPortal_Insert()
、
DataPortal_Update()
或
DataPortal_DeleteSelf()
方法(或对象工厂中的
Update()
方法) |
| Delete() | 调用
Csla.Server.DataPortal
,然后调用
DataPortal_Delete()
方法(或对象工厂中的
Delete()
方法) |
| Execute() | 调用
Csla.Server.DataPortal
,然后调用
DataPortal_Execute()
方法(或对象工厂中的
Update()
方法) |
此外,该模块还定义了用于处理子对象的方法:
| 方法 | 描述 |
| — | — |
| CreateChild() | 调用
ChildDataPortal
,然后调用子对象的
Child_Create()
方法 |
| FetchChild() | 调用
ChildDataPortal
,然后调用子对象的
Child_Fetch()
方法 |
| UpdateChild() | 调用
ChildDataPortal
,然后调用子对象的
Child_Insert()
、
Child_Update()
或
Child_DeleteSelf()
方法 |
Csla.DataPortal
模块还定义了两个事件:
DataPortalInvoke
和
DataPortalInvokeComplete
,分别在调用服务器之前和服务器调用返回之后触发。
创建代理对象是
Csla.DataPortal
的重要功能之一。代理对象用于确定与
Csla.Server.DataPortal
交互时使用的网络协议。代理对象的类型在应用的配置文件中定义,例如:
<appSettings>
<add key="CslaDataPortalProxy"
value="Csla.DataPortalClient.WcfProxy, Csla"/>
</appSettings>
以下是创建代理对象的代码:
Private _proxyType As Type
Private Function GetDataPortalProxy( _
ByVal forceLocal As Boolean) As DataPortalClient.IDataPortalProxy
If forceLocal Then
Return New DataPortalClient.LocalProxy()
Else
Dim portal As Csla.DataPortalClient.IDataPortalProxy
Dim proxyTypeName As String = ApplicationContext.DataPortalProxy
If proxyTypeName = "Local" Then
portal = New DataPortalClient.LocalProxy()
Else
If _proxyType Is Nothing Then
_proxyType = Type.GetType(proxyTypeName, True, True)
End If
portal = DirectCast(Activator.CreateInstance(_proxyType), _
DataPortalClient.IDataPortalProxy)
End If
Return portal
End If
End Function
根对象数据访问方法的实现
根对象的数据访问方法(如
Create()
、
Fetch()
、
Update()
、
Delete()
和
Execute()
)遵循相似的基本流程:
1. 确保用户有权执行该操作。
2. 获取最终要调用的业务方法的元数据。
3. 获取数据门户代理对象。
4. 创建
DataPortalContext
对象。
5. 触发
DataPortalInvoke
事件。
6. 将调用委托给代理对象(从而委托给服务器)。
7. 处理并抛出任何异常。
8. 恢复从服务器返回的
GlobalContext
。
9. 触发
DataPortalInvokeComplete
事件。
10. 返回结果业务对象(如果适用)。
以
Fetch
方法为例:
Private Function Fetch(ByVal objectType As Type, ByVal criteria As Object) As Object
Dim result As Server.DataPortalResult = Nothing
Dim dpContext As Server.DataPortalContext = Nothing
Try
OnDataPortalInitInvoke(Nothing)
If Not Csla.Security.AuthorizationRules.CanGetObject(objectType) Then
Throw New System.Security.SecurityException(String.Format( _
My.Resources.UserNotAuthorizedException, "get", objectType.Name))
End If
Dim method = Server.DataPortalMethodCache.GetFetchMethod(objectType, criteria)
Dim proxy As DataPortalClient.IDataPortalProxy
proxy = GetDataPortalProxy(method.RunLocal)
dpContext = New Server.DataPortalContext(GetPrincipal(), proxy.IsServerRemote)
OnDataPortalInvoke(New DataPortalEventArgs(dpContext, objectType, _
DataPortalOperations.Fetch))
Try
result = proxy.Fetch(objectType, criteria, dpContext)
Catch ex As Server.DataPortalException
result = ex.Result
If proxy.IsServerRemote Then
ApplicationContext.SetGlobalContext(result.GlobalContext)
End If
Dim innerMessage As String = String.Empty
If TypeOf ex.InnerException Is Csla.Reflection.CallMethodException Then
If ex.InnerException.InnerException IsNot Nothing Then
innerMessage = ex.InnerException.InnerException.Message
End If
Else
innerMessage = ex.InnerException.Message
End If
Throw New DataPortalException(String.Format( _
"DataPortal.Fetch {0} ({1})", Resources.Failed, innerMessage), _
ex.InnerException, result.ReturnObject)
End Try
If proxy.IsServerRemote Then
ApplicationContext.SetGlobalContext(result.GlobalContext)
End If
OnDataPortalInvokeComplete(New DataPortalEventArgs( _
dpContext, objectType, DataPortalOperations.Fetch))
Catch ex As Exception
OnDataPortalInvokeComplete(New DataPortalEventArgs( _
dpContext, objectType, DataPortalOperations.Fetch, ex))
Throw
End Try
Return result.ReturnObject
End Function
子对象数据访问方法的实现
子对象的数据访问方法与根对象的方法有所不同。当处理子对象时,数据门户假设已经在处理根对象的过程中,因此不需要考虑网络协议和事务上下文等细节。子对象的数据访问方法主要是将调用委托给
ChildDataPortal
类,该类将调用子对象的
Child_XYZ
方法。
例如,
FetchChild
方法的实现如下:
Public Shared Function FetchChild(Of T)( _
ByVal ParamArray parameters As Object()) As T
Dim portal As New Server.ChildDataPortal()
Return DirectCast((portal.Fetch(GetType(T), parameters)), T)
End Function
异步行为的支持
数据门户通过
DataPortal(Of T)
类支持异步操作。该类提供了异步版本的共享方法,使用
System.ComponentModel.BackgroundWorker
组件处理线程细节。
以下是
BeginFetch
方法的实现:
Public Sub BeginFetch(ByVal criteria As Object, ByVal userState As Object)
Dim bw As New System.ComponentModel.BackgroundWorker()
AddHandler bw.RunWorkerCompleted, AddressOf Fetch_RunWorkerCompleted
AddHandler bw.DoWork, AddressOf Fetch_DoWork
bw.RunWorkerAsync(New DataPortalAsyncRequest(criteria, userState))
End Sub
上下文和位置透明性的实现
上下文和位置透明性是数据门户的重要特性。
DataPortalContext
对象用于在客户端和服务器之间传递上下文数据,包括安全和文化信息。
Csla.Server.DataPortal
使用该对象中的数据来设置服务器的上下文,使其与客户端匹配。
设置服务器上下文的代码如下:
Private Shared Sub SetContext(ByVal context As DataPortalContext)
If Not context.IsRemotePortal Then Exit Sub
ApplicationContext.SetContext( _
context.ClientContext, context.GlobalContext)
ApplicationContext.SetExecutionLocation( _
ApplicationContext.ExecutionLocations.Server)
System.Threading.Thread.CurrentThread.CurrentCulture = _
New System.Globalization.CultureInfo(context.ClientCulture)
System.Threading.Thread.CurrentThread.CurrentUICulture = _
New System.Globalization.CultureInfo(context.ClientUICulture)
If ApplicationContext.AuthenticationType = "Windows" Then
If context.Principal IsNot Nothing Then
Dim ex As New System.Security.SecurityException( _
My.Resources.NoPrincipalAllowedException)
ex.Action = System.Security.Permissions.SecurityAction.Deny
Throw ex
End If
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal)
Else
If context.Principal Is Nothing Then
Dim ex As New System.Security.SecurityException( _
My.Resources.BusinessPrincipalException & " Nothing")
ex.Action = System.Security.Permissions.SecurityAction.Deny
Throw ex
End If
ApplicationContext.User = context.Principal
End If
End Sub
在服务器端处理完成后,需要清除服务器的上下文,以防止其他代码意外访问客户端的上下文或安全信息。清除服务器上下文的代码如下:
Private Shared Sub ClearContext(ByVal context As DataPortalContext)
If Not context.IsRemotePortal Then Exit Sub
ApplicationContext.Clear()
If ApplicationContext.AuthenticationType <> "Windows" Then
ApplicationContext.User = Nothing
End If
End Sub
总结
数据门户作为实现移动对象和数据访问的核心机制,结合了多种设计模式和技术,为开发者提供了强大而灵活的功能。它通过通道适配器和消息路由器模式,实现了客户端和服务器之间的高效通信;通过分布式事务支持,确保了数据的一致性;通过上下文和位置透明性,提供了一致的开发环境。掌握数据门户的原理和实现细节,将有助于开发者构建更加高效、可维护的应用程序。
数据门户的高级特性与对象工厂的运用
数据门户的高级特性
数据门户除了前面提到的基本功能外,还有一些高级特性值得探讨。
授权服务器调用
数据门户在使用自定义身份验证时,能确保应用服务器使用与客户端相同的主体对象。使用 Windows AD 身份验证时,也可配置应用服务器进行模拟,使其在与客户端代码相同的 Windows 身份下运行。
为了对客户端请求进行更高级别的授权检查,数据门户允许开发者创建实现
IAuthorizeDataPortal
接口的对象。要使用此功能,应用服务器的配置文件需在
appSettings
元素中添加一项,指定该类的程序集限定名称,例如:
<add key="CslaAuthorizationProvider"
value="NamespaceName.TypeName, AssemblyName" />
实现
IAuthorizeDataPortal
接口的类需实现
Authorize()
方法,若不允许客户端调用,该方法应抛出异常,否则客户端请求将正常处理。示例代码如下:
Public Class CustomAuthorizer
Implements Csla.Server.IAuthorizeDataPortal
Public Sub Authorize(ByVal clientRequest As AuthorizationRequest) Implements _
IAuthorizeDataPortal.Authorize
' perform authorization here
' throw exception to stop processing
End Sub
End Class
异步行为的深入理解
前面提到数据门户通过
DataPortal(Of T)
类支持异步操作,这里进一步深入了解其工作原理。
DataPortal(Of T)
类的异步方法使用
System.ComponentModel.BackgroundWorker
组件处理线程细节。以
BeginFetch
方法为例,它创建一个
BackgroundWorker
对象,设置
RunWorkerCompleted
和
DoWork
事件的处理程序,然后启动后台任务。
Public Sub BeginFetch(ByVal criteria As Object, ByVal userState As Object)
Dim bw As New System.ComponentModel.BackgroundWorker()
AddHandler bw.RunWorkerCompleted, AddressOf Fetch_RunWorkerCompleted
AddHandler bw.DoWork, AddressOf Fetch_DoWork
bw.RunWorkerAsync(New DataPortalAsyncRequest(criteria, userState))
End Sub
DoWork
处理程序在后台线程上执行实际的数据门户操作,并将结果存储在
DataPortalAsyncResult
对象中。
Private Sub Fetch_DoWork( _
ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim request = TryCast(e.Argument, DataPortalAsyncRequest)
SetThreadContext(request)
Dim state As Object = request.Argument
Dim result As T = Nothing
If TypeOf state Is Integer Then
result = DirectCast(Csla.dataportal.fetch(Of T)(), T)
Else
result = DirectCast(Csla.DataPortal.fetch(Of T)(state), T)
End If
e.Result = New DataPortalAsyncResult( _
result, ApplicationContext.GlobalContext, request.UserState)
End Sub
RunWorkerCompleted
事件处理程序在操作完成后触发,它会引发
FetchCompleted
事件,通知调用代码异步操作已完成。
Private Sub Fetch_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
Dim result = TryCast(e.Result, DataPortalAsyncResult)
If result IsNot Nothing Then
_globalContext = result.GlobalContext
If result.Result IsNot Nothing Then
OnFetchCompleted(New DataPortalResult(Of T)( _
DirectCast(result.Result, T), e.Error, result.UserState))
Else
OnFetchCompleted(New DataPortalResult(Of T)( _
Nothing, e.Error, result.UserState))
End If
Else
OnFetchCompleted(New DataPortalResult(Of T)(Nothing, e.Error, Nothing))
End If
End Sub
对象工厂的引入
在某些情况下,开发者可以使用对象工厂来替代传统的数据门户行为。通过在业务类上添加
ObjectFactory
属性,数据门户将使用指定的对象工厂处理与该业务对象类型相关的所有持久化操作。
<ObjectFactory("MyProject.CustomerFactory, MyProject")> _
Public Class CustomerEdit
Inherits BusinessBase(Of CustomerEdit)
End Class
对象工厂类需实现创建、获取、更新和删除业务对象的方法,默认方法名为
Create()
、
Fetch()
、
Update()
和
Delete()
。工厂对象可继承自
ObjectFactory
类,该类包含一些受保护的方法,便于实现典型的对象工厂行为。
Public Class CustomerFactory
Inherits ObjectFactory
Public Function Create() As Object
' create object and load with defaults
MarkNew(result)
Return result
End Function
Public Function Fetch(ByVal criteria As SingleCriteria(Of CustomerEdit, _
Integer)) As Object
' create object and load with data
MarkOld(result)
Return result
End Function
Public Function Update(ByVal obj As Object) As Object
' insert/update/delete object and its child objects
MarkOld(obj)
' make sure to mark all child objects as old too
Return obj
End Function
Public Sub Delete(ByVal criteria As SingleCriteria(Of CustomerEdit, Integer))
' delete object data based on criteria
End Sub
End Class
数据门户的性能优化与实际应用
反射与动态方法调用的优化
数据门户在业务和工厂对象上动态调用方法时,需要使用反射,频繁使用反射会导致性能问题。为了优化性能,.NET 框架支持动态方法调用的概念,即仅使用一次反射来创建对方法的动态委托引用,然后使用该委托来实际调用方法,性能接近强类型方法调用。
Csla.Reflection
命名空间包含用于动态调用方法的类,如
MethodCaller
和
LateBoundObject
。
MethodCaller
模块负责缓存动态方法委托,以提高性能。
' MethodCaller 模块的部分代码示例
Public Function CallMethodIfImplemented(ByVal target As Object, ByVal methodName As String, ByVal ParamArray parameters As Object()) As Object
Dim delegateObj = GetMethodDelegate(target.GetType(), methodName, parameters)
If delegateObj IsNot Nothing Then
Return delegateObj.DynamicInvoke(parameters)
End If
Return Nothing
End Function
实际应用中的注意事项
在实际应用中,使用数据门户时需要注意以下几点:
事务处理
使用
TransactionScope
对象时要小心,因为如果打开多个数据库连接,即使是连接到同一个数据库,它也会加入 DTC。可以使用
ConnectionManager
、
ObjectContextManager
和
ContextManager
类来避免此问题。
上下文管理
在处理上下文数据时,要确保数据在客户端和服务器之间正确传递和更新。特别是在异步操作中,要注意上下文数据的处理,避免数据覆盖问题。
授权与安全
要合理设置授权规则,确保只有授权用户可以访问和操作业务对象。同时,要注意 Windows 集成安全的限制,如模拟通常只能跨一个网络跃点。
总结与展望
数据门户作为一种强大的机制,在实现移动对象和数据访问方面发挥了重要作用。它结合了多种设计模式和技术,提供了灵活的分布式事务支持、高效的消息路由和上下文透明性。
通过通道适配器和消息路由器模式,数据门户实现了客户端和服务器之间的高效通信;分布式事务支持确保了数据的一致性;上下文和位置透明性提供了一致的开发环境。对象工厂的引入为开发者提供了更多的选择,可根据实际需求灵活处理业务对象的持久化操作。
在未来的开发中,随着技术的不断发展,数据门户可能会进一步优化性能、增强安全性,并支持更多的分布式场景。开发者可以充分利用数据门户的特性,构建更加高效、可维护的应用程序。同时,要不断关注数据门户的最新发展,及时应用新的技术和方法,以适应不断变化的业务需求。
超级会员免费看

被折叠的 条评论
为什么被折叠?



