简单分析一下 RIA Services 的数据绑定原理

简单分析一下 RIA Services 的数据绑定原理.

Neil Chen, 11/25/2009
==================================================================
利用 RIA Services 的项目模板创建了一个 solution,其中包含一个 Silverlight App 和一个 ASP.NET Web App.
名称分别是 BusinessApplication1 和 BusinessApplication1.Web

在 Silverlight 项目的代码里,

首先定义了一个 DomainDataSource 对象,

 

<riacontrols:DomainDataSource x:Name="MyData" LoadSize="20" QueryName="GetSalariedEmployee" AutoLoad="True">
 
<riacontrols:DomainDataSource.DomainContext>
  
<ds:AdvDomainContext />
 
</riacontrols:DomainDataSource.DomainContext>
</riacontrols:DomainDataSource>

 

这个东西定义在 System.Windows.Ria.Controls.dll 中的 System.Windows.Controls 名称空间下。

下面是一个简单的 DataGrid 的例子,通过 ItemsSource 绑定到上述数据源对象。

 

<datagrid:DataGrid x:Name="EmployeeGrid" ItemsSource="{Binding ElementName=MyData, Path=Data}" />

 

可以看到,其绑定 Path 是 "Data".

另一个复杂一点的柱形图绑定例子:

 

<chartingToolkit:Chart x:Name="MyChartBarSeries" Title="Job Title/SickLeaveHours Chart (BarSeries)" Height="400" Margin="0,20,0,0">
 
<chartingToolkit:Chart.Series>
  
<chartingToolkit:BarSeries
   
Title="SickLeave Hours"
   ItemsSource
="{Binding ElementName=MyData, Path=Data}"
   IndependentValueBinding
="{Binding Title}"
   DependentValueBinding
="{Binding SickLeaveHours}"/>
 
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>

 

那么我们用 Reflector 看一看 DomainDataSource 中 Data 属性的实现是怎样的.

 

public IEnumerable Data
{
    
get
    {
        
return (IEnumerable) base.GetValue(DataProperty);
    }
    
private set
    {
        
if (this.Data != value)
        {
            
this.SetValueNoCallback(DataProperty, value);
        }
    }
}

 
这里可以看到表示可枚举数据的 IEnumerable 对象其实是存在一个依赖属性 DataProperty 里面. 然后我们跟踪 private set 方法是何时被调用的,
可以发现下列代码:

 

private void InitializeEntityCollectionView()
{
    
this._internalEntityCollection = new DomainDataSourceEntityCollection(this);
    EntityCollectionView view 
= new EntityCollectionView(this._internalEntityCollection, delegate {
        
this.EndDeferRefresh();
    });
    
this._internalEntityCollection.EntityCollectionView = view;

 
// 这里设定了 Data 属性.
    this.Data = view;
 
//
}

再进一步可以看到这个方法是被 DomainDataSource 类的构造器调用的。
 
我们可以看到这里的 view 实际上是一个 EntityCollectionView 类的实例。而这个类的签名比较复杂,它继承了一堆接口:

 

internal class EntityCollectionView : ICollectionView, IEnumerable, INotifyCollectionChanged, 
 IPagedCollectionView, IEditableCollectionView, INotifyPropertyChanged

 

可以从接口名称大致看到这个集合类具备可枚举,变动通知,分页,编辑等功能。

而该对象的数据源又是一个 DomainDataSourceEntityCollection 的实例。见下列语句:
this._internalEntityCollection = new DomainDataSourceEntityCollection(this);

另一个用到的类 EntityCollectionView 的一些关键代码如下:

 

internal class EntityCollectionView : ICollectionView, IEnumerable, INotifyCollectionChanged, 
 IPagedCollectionView, IEditableCollectionView, INotifyPropertyChanged
{
 
public EntityCollectionView(DomainDataSourceEntityCollection source, Action deferRefreshDisposedCallback)
 {
  
//
  this._sourceCollection = source;
  
//
  this.CopySourceToInternalList();
  
//  
 }

 
public IEnumerator GetEnumerator()
 {
  
//
  
// Call InternalList here.
  enumerator = this.InternalList.GetEnumerator();
  
//
 }

 
private void CopySourceToInternalList()
 {
  
this._internalList = new List<object>();
  
// 这里获取数据源的 enumerator.
  IEnumerator enumerator = this.SourceCollection.GetEnumerator();
  
while (enumerator.MoveNext())
  {
   
this._internalList.Add(enumerator.Current);
  }
 }

 
public IEnumerable SourceCollection
 {
  
get
  {
   
return this._sourceCollection;
  }
 } 

 
private IList InternalList
 {
  
get
  {
   
return this._internalList;
  }
 }
}


internal class DomainDataSourceEntityCollection : Collection<Entity>, IIndexableCollection, IEnumerable, 
 INotifyPropertyChanged, INotifyCollectionChanged
{
 
public IEnumerator<Entity> GetEnumerator()
 {
  
return base.items.GetEnumerator();
 }
}

现在需要知道 base.items 如何初始化.

这个对象是通过 this._internalEntityCollection = new DomainDataSourceEntityCollection(this);
初始化的。其中 this 是 DomainDataSource 对象.
于是追踪下列构造器

 

public DomainDataSourceEntityCollection(DomainDataSource domainDataSource)
{
    
//
    this._domainDataSource = domainDataSource;
}

 

但其中没有涉及 base.items 何时赋值的语句。可以断定,这里数据是后来加载的。

加载流程:

DomainDataSource 中设定了 DomainContext 属性,<riacontrols:DomainDataSource.DomainContext>
导致 DomainContextPropertyChanged 被调用。

 

private static void DomainContextPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
    DomainDataSource source 
= (DomainDataSource) depObj;
    
if ((source != null&& !source.IsHandlerSuspended(e.Property))
    {
  
// 得到 QueryName 对应的 MethodInfo 等信息
        MethodInfo info;
        PropertyInfo info2;
        Type type;
        
if (e.OldValue != null)
        {
            source.SetValueNoCallback(e.Property, e.OldValue);
            
throw new InvalidOperationException(DomainDataSourceResources.DomainContextAlreadySet);
        }
        DomainContext newValue 
= e.NewValue as DomainContext;
        Type domainContextType 
= newValue.GetType();
  
// 关键点。。
        Exception exception = CheckEntityQueryInformation(GetEntityQueryInformation(domainContextType, source.QueryName, source.QueryParameters, out info, out info2, out type), domainContextType, source.QueryName, type);
        
if (exception != null)
        {
            source.SetValueNoCallback(e.Property, e.OldValue);
            
throw exception;
        }
        source._entityType 
= type;
        source._queryMethod 
= info;
        newValue.PropertyChanged 
+= new PropertyChangedEventHandler(source.DomainContext_PropertyChanged);
        source.GetEntityList(info2);
        source.HasChanges 
= newValue.HasChanges;

  
// 调用 CheckAutoLoad 方法来触发获取数据的操作.
        exception = source.CheckAutoLoad(truefalse);
        
if (exception != null)
        {
            
throw exception;
        }
    }
}

下面是获取 DomainContext 对象上定义的元数据的实现:

 

private static MethodAccessStatus GetEntityQueryInformation(Type domainContextType, string queryName, ParameterCollection queryParameters, out MethodInfo entityQueryMethodInfo, out PropertyInfo domainContextEntityListPropertyInfo, out Type entityType)
{
    Func
<KeyValuePair<MethodInfo, Type>bool> predicate = null;
    entityQueryMethodInfo 
= null;
    domainContextEntityListPropertyInfo 
= null;
    entityType 
= null;
    
if ((domainContextType == null|| string.IsNullOrEmpty(queryName))
    {
        
return MethodAccessStatus.InsufficientInput;
    }
    
string suffixedQueryName = queryName + "Query";
    IEnumerable
<KeyValuePair<MethodInfo, Type>> source = domainContextType.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where<MethodInfo>(delegate (MethodInfo method) {
        
if (!string.Equals(method.Name, suffixedQueryName, StringComparison.Ordinal))
        {
            
return string.Equals(method.Name, queryName, StringComparison.Ordinal);
        }
        
return true;
    }).Select(
delegate (MethodInfo method) {
        
return new { method = method, entityQueryEntityType = GetEntityQueryEntityType(method.ReturnType) };
    }).Where(
delegate (<>f__AnonymousType1<MethodInfo, Type> <>h__TransparentIdentifier0) {
        
return (<>h__TransparentIdentifier0.entityQueryEntityType != null);
    }).Select(
delegate (<>f__AnonymousType1<MethodInfo, Type> <>h__TransparentIdentifier0) {
        
return new KeyValuePair<MethodInfo, Type>(<>h__TransparentIdentifier0.method, <>h__TransparentIdentifier0.entityQueryEntityType);
    });
    
if (!source.Any<KeyValuePair<MethodInfo, Type>>())
    {
        
return MethodAccessStatus.NameNotFound;
    }
    KeyValuePair
<MethodInfo, Type>[] pairArray = source.Where<KeyValuePair<MethodInfo, Type>>(delegate (KeyValuePair<MethodInfo, Type> methodType) {
        
return MethodParametersMatchQueryParameters(methodType.Key, queryParameters);
    }).ToArray
<KeyValuePair<MethodInfo, Type>>();
    
if (pairArray.Length > 1)
    {
        
if (predicate == null)
        {
            predicate 
= delegate (KeyValuePair<MethodInfo, Type> o) {
                
return o.Key.Name.Equals(suffixedQueryName, StringComparison.Ordinal);
            };
        }
        pairArray 
= pairArray.Where<KeyValuePair<MethodInfo, Type>>(predicate).ToArray<KeyValuePair<MethodInfo, Type>>();
    }
    
if (pairArray.Length != 1)
    {
        
return MethodAccessStatus.ArgumentMismatch;
    }
    KeyValuePair
<MethodInfo, Type> pair = pairArray[0];
    Type entityListType 
= typeof(EntityList<>).MakeGenericType(new Type[] { pair.Value });
    IEnumerable
<PropertyInfo> enumerable2 = domainContextType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where<PropertyInfo>(delegate (PropertyInfo property) {
        
return property.PropertyType == entityListType;
    });
    entityType 
= pair.Value;
    
int num = enumerable2.Count<PropertyInfo>();
    
if (num == 0)
    {
        
return MethodAccessStatus.EntityListNotFound;
    }
    
if (num > 1)
    {
        
return MethodAccessStatus.AmbiguousEntityList;
    }
    entityQueryMethodInfo 
= pair.Key;
    domainContextEntityListPropertyInfo 
= enumerable2.Single<PropertyInfo>();
    
return MethodAccessStatus.Success;
}

追踪调用堆栈:

 

DomainDataSource.CheckAutoLoad()
DomainDataSource.ExecuteLoad()
DomainDataSource.LoadData()
DomainDataSource.LoadData_Callback()
DomainDataSource.DomainContext_Loaded()
DomainDataSourceEntityCollection.AddLoadedEntity() 
// data items are added to internal base.items collection.

 

 

private Exception LoadData(LoadType loadType, bool allowThrow)
{
 
// 
 callback = delegate (LoadOperation loadOperation) {
  
this.LoadData_Callback(loadOperation);
 };
 
//
}

private void LoadData_Callback(LoadOperation loadOperation)
{
    LoadedDataEventArgs e 
= new LoadedDataEventArgs(loadOperation.Entities, loadOperation.AllEntities, loadOperation.TotalEntityCount, loadOperation.Error, loadOperation.IsCanceled, loadOperation.UserState);
    
this.DomainContext_Loaded(this, e);
 
//
}

private void DomainContext_Loaded(object sender, LoadedDataEventArgs e)
{
 
//
 foreach (Entity entity in e.Entities.Where<Entity>(delegate (Entity entity) {
  
return entity.EntityState != EntityState.Deleted;
 }))
 {
  
this._internalEntityCollection.AddLoadedEntity(entity);
 }
 
//
}

internal void AddLoadedEntity(Entity loadedEntity)
{
 
// 
    base.Add(loadedEntity);
}



到目前为止,可以看到全部的加载流程如下:
============================================
DomainDataSource 中设定了 DomainContext 属性,<riacontrols:DomainDataSource.DomainContext>
导致 DomainContextPropertyChanged 被调用。

DomainDataSource.CheckAutoLoad()
DomainDataSource.ExecuteLoad()
DomainDataSource.LoadData()
DomainDataSource.LoadData_Callback()
DomainDataSource.DomainContext_Loaded()
DomainDataSourceEntityCollection.AddLoadedEntity() // data items are added to internal base.items collection.

这时如果控件通过 Path=Data 绑定到 DomainDataSource 的 Data 属性,
对这个控件的 GetEnumerator() 操作会转发到 EntityCollectionView 类的一个实例,
EntityCollectionView 的构造函数中,会 copy 一个 DomainDataSourceEntityCollection 对象的数据到内部的数据列表。
而该 DomainDataSourceEntityCollection 中的数据,是在前面 DomainContext 属性变动时,由 AutoLoad 属性引发自动填充的。


现在来仔细看一下 DomainContext 对象是如何获取数据的。

在这段代码中:

<riacontrols:DomainDataSource x:Name="MyData" LoadSize="20" QueryName="GetSalariedEmployee" AutoLoad="True">
 
<riacontrols:DomainDataSource.DomainContext>
  
<ds:AdvDomainContext />
 
</riacontrols:DomainDataSource.DomainContext>
</riacontrols:DomainDataSource>

<ds:AdvDomainContext /> 是一个 DomainContext 对象,而名称空间 ds 的定义如下:
xmlns:ds="clr-namespace:BusinessApplication1.Web.Services"

我们看看 BusinessApplication1\Generated_Code\ 下的 BusinessApplication1.Web.g.cs,
这个自动生成的代码里有一个类,

 

 

public sealed partial class AdvDomainContext : DomainContext

 

正是 <ds:AdvDomainContext />.

其默认构造器如下:

 

 

public AdvDomainContext() : 
  
this(new HttpDomainClient(new Uri("DataService.axd/BusinessApplication1-Web-Services-AdvDomainService/", System.UriKind.Relative)))
{
}

注意到这里指向了一个服务的 Uri. "DataService.axd".
那我们到 \BusinessApplication1.Web 下面看一下 web.config 里面搜一下,发现有这么一段:

 

<httpHandlers>
 
<add path="DataService.axd" verb="GET,POST" 
  type
="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>

 

对了,这个就是动态注册的一个 http handler. 其定义可以去看 System.Web.Ria.dll 中 System.Web.Ria.DataServiceFactory 类的实现。

可以看到, AdvDomainContext 的代码非常简单,比如像下面的两个方法,

 

public EntityList<Employee> Employees
{
 
get
 {
  
return base.Entities.GetEntityList<Employee>();
 }
}

/// <summary>
/// Returns an EntityQuery for query operation 'GetEmployee'.
/// </summary>
public EntityQuery<Employee> GetEmployeeQuery()
{
 
return base.CreateQuery<Employee>("GetEmployee"nullfalsetrue);
}

主要是利用了 DomainContext 基类里面的一些方法。晚一点我们再来研究详细的内容,看来值得关注的至少有
base.Entities.GetEntityList<T>()

base.CreateQuery<T>(..)
这两个方法的实现。

回过头去看一下 System.Web.Ria.dll 里面 DataServiceFactory 的实现。
可以看到,
GetHandler() 方法调用了 GetDataService(),
而后者,根据 context.Request.PathInfo,也就是请求中的字符串信息,
按一定的规则拆分后,创建了符合要求的 DataService 类以处理请求。

回忆前面,路径的字符串其实就是这个东西:

DataService.axd/BusinessApplication1-Web-Services-AdvDomainService/
其中包含了要动态创建的类型的名字等信息.

这里 BusinessApplication1-Web-Services-AdvDomainService
首先将其中 - 替换为 .,变成

BusinessApplication1.Web.Services.AdvDomainService

而这个类就是 VS 项目模板在服务端自动生成的一个类,在 BusinessApplication1.Web\Services 下的
AdvDomainService.cs 文件中。

接下来,会创建一个 System.Web.Ria.DataService 对象,并利用以上 AdvDomainService 实例的信息将其初始化然后返回。

internal sealed class DataService : IHttpHandler, IServiceProvider, IDisposable

这里就进入了一个标准的 WCF Service 的处理流程。。。

值得一提的是,处理具体请求用的是 System.Web.Ria.DataServiceSubmitRequest 类的下列方法:

public override object Invoke(DomainService domainService)

然后,调用到 System.Web.DomainServices.DomainService 的

public virtual void Submit(ChangeSet changeSet) 方法.

 

posted on 2009-11-25 14:50  NeilChen  阅读(1846)  评论(0编辑  收藏  举报

导航