• 简单分析一下 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) 方法.

  • 相关阅读:
    1509 加长棒
    51Nod 1158 全是1的最大子矩阵
    P2953 [USACO09OPEN]牛的数字游戏Cow Digit Game
    P3384 【模板】树链剖分
    北京集训DAY3
    北京集训DAY2
    北京集训DAY1
    51Nod 1422 沙拉酱前缀 二分查找
    51Nod 1109 01组成的N的倍数
    51Nod 1043 幸运号码 数位DP
  • 原文地址:https://www.cnblogs.com/RChen/p/ria_services.html
Copyright © 2020-2023  润新知