RIA Service中对于递归实体类型处理的问题及解决方案

故事是这样开始的:

 

我们在开发一个Silverlight应用程序的时候使用到了RIA Service,我们需要通过该服务公开一个对文件夹的查询操作。

为此,我们建立了如下的一个实体类型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;

using System.ServiceModel.DomainServices.Server;

namespace DomainServiceSample.Web
{
    [DataContract]//必须声明类别为DataContract
    public class Folder
    {
        [DataMember]//必须声明属性为DataMember
        [Key]//一个用于DomainService的Entity必须有一个Key
        public Guid ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]         
        public Folder[] SubFolder { get; set; }
        
    }
}

【注意】上面其实是有一个递归的类型,也就是Folder里面又包含Folder

然后,我们创建了一个DomainService

namespace DomainServiceSample.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;


    // TODO: Create methods containing your application logic.
    [EnableClientAccess()]
    public class SampleDomainService : DomainService
    {
        [Query]
        public IQueryable<Folder> GetFolder()
        {
            var folder = new Folder() { ID = Guid.NewGuid(), Name = "Level 1 Folder" };
            var subFolders = new[]{
                new Folder(){ID=Guid.NewGuid(),Name="Level 2 Folder"},
                new Folder(){ID=Guid.NewGuid(),Name="Level 2 Folder 2"}
            };
            folder.SubFolder = subFolders;
            return new[] { folder }.AsQueryable();
        }
    }
}


这个代码没有什么特别的,我们计划向客户端发送的结果是一个Folder,但同时它包含了两个子Folder。

编写上面两个类型很顺利,然后我们生成项目,因为使用了Domain Service,所以在Silverlight应用程序中会得到一个自动生成的类型

我们打开那个文件,确实里面是有一个Folder的类型

    /// <summary>
    /// The 'Folder' entity class.
    /// </summary>
    [DataContract(Namespace="http://schemas.datacontract.org/2004/07/DomainServiceSample.Web")]
    public sealed partial class Folder : Entity
    {
        
        private Guid _id;
        
        private string _name;
        
        #region Extensibility Method Definitions

        /// <summary>
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// </summary>
        partial void OnCreated();
        partial void OnIDChanging(Guid value);
        partial void OnIDChanged();
        partial void OnNameChanging(string value);
        partial void OnNameChanged();

        #endregion
        
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Folder"/> class.
        /// </summary>
        public Folder()
        {
            this.OnCreated();
        }
        
        /// <summary>
        /// Gets or sets the 'ID' value.
        /// </summary>
        [DataMember()]
        [Editable(false, AllowInitialValue=true)]
        [Key()]
        [RoundtripOriginal()]
        public Guid ID
        {
            get
            {
                return this._id;
            }
            set
            {
                if ((this._id != value))
                {
                    this.OnIDChanging(value);
                    this.ValidateProperty("ID", value);
                    this._id = value;
                    this.RaisePropertyChanged("ID");
                    this.OnIDChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Name' value.
        /// </summary>
        [DataMember()]
        public string Name
        {
            get
            {
                return this._name;
            }
            set
            {
                if ((this._name != value))
                {
                    this.OnNameChanging(value);
                    this.RaiseDataMemberChanging("Name");
                    this.ValidateProperty("Name", value);
                    this._name = value;
                    this.RaiseDataMemberChanged("Name");
                    this.OnNameChanged();
                }
            }
        }
        
        /// <summary>
        /// Computes a value from the key fields that uniquely identifies this entity instance.
        /// </summary>
        /// <returns>An object instance that uniquely identifies this entity instance.</returns>
        public override object GetIdentity()
        {
            return this._id;
        }
    }

但是,让人疑惑的是,这个类型里面并没有包含SubFolder这个属性

这是什么情况呢?难道RIA Service不允许传递这种包含递归类型引用的实体?确实如此。

 

我目前的解决方法是:

1. 为Folder类型添加一个ParentID属性

2. 为SubFolder设置关联,即子Folder的ParentID设置到父Folder的ID。并且定义他们的关联

3. 使用Include属性标记SubFolder是要包含进来的

 

所以,这个类型修改为下面这样

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;

using System.ServiceModel.DomainServices.Server;

namespace DomainServiceSample.Web
{
    [DataContract]//必须声明类别为DataContract
    public class Folder
    {
        [DataMember]//必须声明属性为DataMember
        [Key]//一个用于DomainService的Entity必须有一个Key
        public Guid ID { get; set; }

        [DataMember]
        public string Name { get; set; }


        [DataMember]
        [Association("Test","ID","ParentID")]
        [Include]
        public Folder[] SubFolder { get; set; }


        [DataMember]
        public Guid ParentID { get; set; }
        
    }
}

image

然后,我们再来看在Silverlight中生成的那个类型

   /// <summary>
    /// The 'Folder' entity class.
    /// </summary>
    [DataContract(Namespace="http://schemas.datacontract.org/2004/07/DomainServiceSample.Web")]
    public sealed partial class Folder : Entity
    {
        
        private Guid _id;
        
        private string _name;
        
        private Guid _parentID;
        
        private EntityCollection<Folder> _subFolder;
        
        #region Extensibility Method Definitions

        /// <summary>
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// </summary>
        partial void OnCreated();
        partial void OnIDChanging(Guid value);
        partial void OnIDChanged();
        partial void OnNameChanging(string value);
        partial void OnNameChanged();
        partial void OnParentIDChanging(Guid value);
        partial void OnParentIDChanged();

        #endregion
        
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Folder"/> class.
        /// </summary>
        public Folder()
        {
            this.OnCreated();
        }
        
        /// <summary>
        /// Gets or sets the 'ID' value.
        /// </summary>
        [DataMember()]
        [Editable(false, AllowInitialValue=true)]
        [Key()]
        [RoundtripOriginal()]
        public Guid ID
        {
            get
            {
                return this._id;
            }
            set
            {
                if ((this._id != value))
                {
                    this.OnIDChanging(value);
                    this.ValidateProperty("ID", value);
                    this._id = value;
                    this.RaisePropertyChanged("ID");
                    this.OnIDChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Name' value.
        /// </summary>
        [DataMember()]
        public string Name
        {
            get
            {
                return this._name;
            }
            set
            {
                if ((this._name != value))
                {
                    this.OnNameChanging(value);
                    this.RaiseDataMemberChanging("Name");
                    this.ValidateProperty("Name", value);
                    this._name = value;
                    this.RaiseDataMemberChanged("Name");
                    this.OnNameChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'ParentID' value.
        /// </summary>
        [DataMember()]
        public Guid ParentID
        {
            get
            {
                return this._parentID;
            }
            set
            {
                if ((this._parentID != value))
                {
                    this.OnParentIDChanging(value);
                    this.RaiseDataMemberChanging("ParentID");
                    this.ValidateProperty("ParentID", value);
                    this._parentID = value;
                    this.RaiseDataMemberChanged("ParentID");
                    this.OnParentIDChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets the collection of associated <see cref="Folder"/> entity instances.
        /// </summary>
        [Association("Test", "ID", "ParentID")]
        public EntityCollection<Folder> SubFolder
        {
            get
            {
                if ((this._subFolder == null))
                {
                    this._subFolder = new EntityCollection<Folder>(this, "SubFolder", this.FilterSubFolder);
                }
                return this._subFolder;
            }
        }
        
        private bool FilterSubFolder(Folder entity)
        {
            return (entity.ParentID == this.ID);
        }
        
        /// <summary>
        /// Computes a value from the key fields that uniquely identifies this entity instance.
        /// </summary>
        /// <returns>An object instance that uniquely identifies this entity instance.</returns>
        public override object GetIdentity()
        {
            return this._id;
        }
    }

这时就看到SubFolder了,而且还包含了很多其他的属性。

 

最后,我做了一个界面来显示给大家看看效果

MainPage.xaml的内容如下

<UserControl x:Class="DomainServiceSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid AutoGenerateColumns="True" Margin="16,13,12,12" Name="dataGrid1" ItemsSource="{Binding}" RowDetailsVisibilityMode="Visible">
            <sdk:DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <sdk:DataGrid AutoGenerateColumns="True" Margin="20,20,20,20" Height="300" ItemsSource="{Binding SubFolder}" />
                </DataTemplate>
            </sdk:DataGrid.RowDetailsTemplate>
        </sdk:DataGrid>
    </Grid>
</UserControl>

 

MainPage.xaml.cs的内容如下

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using DomainServiceSample.Web;

namespace DomainServiceSample
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            var ctx = new SampleDomainContext();
            var op = ctx.Load<Folder>(ctx.GetFolderQuery());
            dataGrid1.DataContext = op.Entities;
        }
    }
}

 

同时,服务端的代码我也稍作了修改

namespace DomainServiceSample.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;


    // TODO: Create methods containing your application logic.
    [EnableClientAccess()]
    public class SampleDomainService : DomainService
    {
        [Query]
        public IQueryable<Folder> GetFolder()
        {
            var folder = new Folder() { ID = Guid.NewGuid(), Name = "Level 1 Folder" };
            var subFolders = new[]{
                new Folder(){ID=Guid.NewGuid(),Name="Level 2 Folder",ParentID=folder.ID},
                new Folder(){ID=Guid.NewGuid(),Name="Level 2 Folder 2",ParentID=folder.ID}
            };
            folder.SubFolder = subFolders;
            return new[] { folder }.AsQueryable();
        }
    }
}


 

调试起来看到的效果如下

image

 

虽然解决了问题,但个人感觉Domain Service这个设计值得商榷。如果各位有更好的见解和解决方案,请不吝赐教

posted @ 2011-06-10 11:32  陈希章  阅读(1670)  评论(9编辑  收藏  举报