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
  
  
   
    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的类型

    /// 
    /// The 'Folder' entity class.
    /// 
    [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

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

        #endregion
        
        
        /// 
        /// Initializes a new instance of the 
     
      class.
        /// 
        public Folder()
        {
            this.OnCreated();
        }
        
        /// 
        /// Gets or sets the 'ID' value.
        /// 
        [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();
                }
            }
        }
        
        /// 
        /// Gets or sets the 'Name' value.
        /// 
        [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();
                }
            }
        }
        
        /// 
        /// Computes a value from the key fields that uniquely identifies this entity instance.
        /// 
        /// 
   
   
    
    An object instance that uniquely identifies this entity instance.
   
   
        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中生成的那个类型

   /// 
    /// The 'Folder' entity class.
    /// 
    [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
  
  
   
    _subFolder;
        
        
   
   #region Extensibility Method Definitions

        
   
   /// 
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// 
        
   
   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
        
        
        
   
   /// 
        /// Initializes a new instance of the 
      
       class.
        /// 
        
   
   public Folder()
        {
            
   
   this.OnCreated();
        }
        
        
   
   /// 
        /// Gets or sets the 'ID' value.
        /// 
        [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();
                }
            }
        }
        
        
   
   /// 
        /// Gets or sets the 'Name' value.
        /// 
        [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();
                }
            }
        }
        
        
   
   /// 
        /// Gets or sets the 'ParentID' value.
        /// 
        [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();
                }
            }
        }
        
        
   
   /// 
        /// Gets the collection of associated 
      
       entity instances.
        /// 
        [Association(
   
   "Test", 
   
   "ID", 
   
   "ParentID")]
        
   
   public EntityCollection
   
   
    
     SubFolder
        {
            get
            {
                
    
    if ((
    
    this._subFolder == 
    
    null))
                {
                    
    
    this._subFolder = 
    
    new EntityCollection
    
    
     
     (
     
     this, 
     
     "SubFolder", 
     
     this.FilterSubFolder);
                }
                
     
     return 
     
     this._subFolder;
            }
        }
        
        
     
     private 
     
     bool FilterSubFolder(Folder entity)
        {
            
     
     return (entity.ParentID == 
     
     this.ID);
        }
        
        
     
     ///  /// Computes a value from the key fields that uniquely identifies this entity instance. /// 
        
     
     /// 
      
      
        An object instance that uniquely identifies this entity instance. 
      
        
     
     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
  
  
   
   (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
  
  
   
    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这个设计值得商榷。如果各位有更好的见解和解决方案,请不吝赐教

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值