故事是这样开始的:
我们在开发一个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; }
}
}
然后,我们再来看在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();
}
}
}
调试起来看到的效果如下
虽然解决了问题,但个人感觉Domain Service这个设计值得商榷。如果各位有更好的见解和解决方案,请不吝赐教