创建托管 POCO 实体的域服务工厂





4.00/5 (2投票s)
本文演示了如何创建领域服务工厂来托管纯粹的类对象(POCO)实体,并通过 Silverlight 4 中的 RIA 服务使用它们。
引言
如果您还没有,请参阅相关文章:通过 RIA 服务使用 POCO 实体,因为本文是关于通过富互联网应用程序 (RIA) 服务使用纯粹的类对象 (POCO) 实体的配套文章。您实际上可以使用此示例通过 OData(开放数据协议)托管任何内容,而不仅仅是 POCO 实体。对于此示例,您必须安装以下软件:
- Visual Studio 2010
- Entity Framework v4
- 适用于 Visual Studio 的 SilverLight 4 工具
- 适用于 C# 的 T4 POCO 实体生成器
- RIA Services SP1 Beta
背景
使用 POCO 时会出现一个问题,那就是您可能需要通过 LINQ 或其他数据上下文来包含关系。Microsoft 撰写了一篇关于使用 Include 语句共享实体的文章,但它不适用于 POCO,因为它们应该是与上下文无关的。此外,通常情况下,您可能希望连接两个 .edmx 文件,或在实体之间提供一些高度自定义的业务关系,或者您甚至可能希望在提供数据之前执行用户访问检查,或者您可能希望实现 Model View View Model (MVVM) 模式,就像本示例一样。
创建领域服务工厂
我之前文章(参见引言)中的 **WCF RIA Services 类库**项目名为 EntRiaServices,其中有一个名为 EntRiaServices.Web 的子项目。在这里,我们将创建领域服务类以及领域服务工厂。这两个类,因此右键单击 EntRiaServices.Web 子项目,选择“添加”、“新建项”、“类”。
领域服务工厂 (DomainServiceFactory.cs) 是一个类,它将生成 RIA 服务可以访问的可用领域服务。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.DomainServices.Server;
namespace EntRIAServices.Web
{
public class DomainServiceFactory : IDomainServiceFactory
{
private IDomainServiceFactory _defaultFactory;
private Entities.DBData _dataContext;
public DomainServiceFactory(IDomainServiceFactory defaultFactory)
{
_defaultFactory = defaultFactory;
// For this I copied the connection string from
// the web.config file of the BasicDataViewer file
// (i.e. it's where by .EDMX file is located
// and the .EDMX generated it for me because I selected
// Save your connection settings to your Web.config when I created it)
// I had to modify it to point to the physical
// location of the metadata files generated by my .EDMX
// after selecting the Metadata Artifact property to "Copy to Output Directory"
_dataContext =
new Entities.DBData("metadata=C:\\Users\\Owner\\Documents\\" +
"Visual Studio 2010\\Projects\\BasicDataViewer\\BasicDataViewer\\" +
"bin\\DBData.csdl|C:\\Users\\Owner\\Documents\\Visual Studio 2010\\" +
"Projects\\BasicDataViewer\\BasicDataViewer\\bin\\DBData.ssdl|C:\\" +
"Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\" +
"BasicDataViewer\\BasicDataViewer\\bin\\DBData.msl;provider=" +
"System.Data.SqlClient;provider connection string='Data Source=" +
"YOUR_PC_NAME\\YOUR_SQL_SERVER_NAME;Initial Catalog=YOUR_DB_NAME;" +
"User ID=DB_USER_NAME;Password=YOUR_DB_PASSWORD;" +
"MultipleActiveResultSets=True'");
}
public DomainService CreateDomainService(Type domainServiceType,
DomainServiceContext context)
{
// Here is where you would filter your entities based
// on user information and/or load multiple entities by
// passing in relationship objects
if ((domainServiceType == typeof(AccessableDomainService)))
{
DomainService ds = (DomainService)Activator.CreateInstance(
domainServiceType, new object[] { this._dataContext });
ds.Initialize(context);
return ds;
}
else
{
return _defaultFactory.CreateDomainService(domainServiceType, context);
}
}
public void ReleaseDomainService(DomainService domainService)
{
domainService.Dispose();
}
}
}
第二个类 (AccessableDomainService.cs),或者您想要的任何数量的类,是您的可访问领域服务。您所有的领域服务都应公开标记有 [Query]
、[Update]
、[Insert]
和 [Delete]
属性的子例程。
using System.Data.Objects;
namespace EntRIAServices.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 AccessableDomainService : DomainService
{
private Entities.DBData _context;
protected string EntitySetName { get; private set; }
public AccessableDomainService(Entities.DBData dataContext)
{
this._context = dataContext;
fFetchEntitySetName();
}
// This gets the entity set name "Zip"
private void fFetchEntitySetName()
{
var entitySetProperty =
this._context.GetType().GetProperties().Single(
p => p.PropertyType.IsGenericType &&
typeof(IQueryable<>).MakeGenericType(
typeof(Entities.Zip)).IsAssignableFrom(p.PropertyType));
this.EntitySetName = "DBData." + entitySetProperty.Name;
}
[Query(IsDefault = true)]
public IQueryable<entities.zip> GetByDallas()
{
return (IQueryable<entities.zip>) this._context.Zips.Where(
x => x.City == "Irving");
}
[Update]
public virtual void SaveCommand(Entities.Zip oZipToSave)
{
object oOriginalItem;
if (this._context.TryGetObjectByKey(new System.Data.EntityKey(
this.EntitySetName, "ZipCode", oZipToSave.ZipCode),
out oOriginalItem))
{
this._context.ApplyCurrentValues(this.EntitySetName, oZipToSave);
}
else
{
this._context.AddObject(this.EntitySetName, oZipToSave);
}
this._context.SaveChanges();
}
[Insert]
public virtual void Add(Entities.Zip oNewZip)
{
this._context.AddObject(this.EntitySetName, oNewZip);
this._context.SaveChanges();
}
[Delete]
public virtual void Delete(Entities.Zip oZip)
{
this._context.DeleteObject(oZip);
this._context.SaveChanges();
}
}
}
从这两个类中可以看到,工厂会重写特定类的 CreateInstance
方法,并传入数据上下文。此时您可以传入任何内容,包括关系对象。另一件您可以做的事情是,如果某个 POCO 的所有领域服务都具有相同的接口,那么您可以在将 Silverlight .xaml 与该集合中“UI 批准”的领域服务关联时使用多态性。
将 Silverlight 连接到您的领域服务工厂
我之前文章(参见引言)中的 **SilverLight 应用程序**项目名为 BusinessApplication1 或 BusinessApp。我不得不从另一个项目拖入一个 Global.asax 文件,但如果您知道如何创建它,那就去做吧。领域服务工厂可以从您的代码中的任何位置更改,但我喜欢放在这里。
using System;
using System.Web.DynamicData;
using System.Web.Routing;
using System.ServiceModel.DomainServices.Server;
namespace BusinessApp.Web
{
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
if (!(DomainService.Factory is
EntRIAServices.Web.DomainServiceFactory))
{
DomainService.Factory =
new EntRIAServices.Web.DomainServiceFactory(DomainService.Factory);
}
}
}
}
接下来,您将更新您的 .XAML 文件以包含一个按钮。如果您之前按照文章中的步骤操作,您的 .xaml 文件上应该已经有了数据,并且设置了 riaControls:DomainDataSource.DomainContext
引用。我示例中的数据上下文名为 zipDomainDataSource
。我有一个演示保存按钮,名为 Button1
。这里是代码隐藏中执行更新的快速测试;在将此代码输入到按钮的 .xaml.cs 文件后,您需要按 F5。
<navigation:Page x:Class="BusinessApp.Page1"
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"
xmlns:navigation="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="Page1 Page"
xmlns:riaControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.DomainServices"
xmlns:my="clr-namespace:EntRIAServices.Web;assembly=EntRIAServices"
xmlns:my1="clr-namespace:Entities;assembly=EntRIAServices"
xmlns:myApp="clr-namespace:BusinessApp"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<riaControls:DomainDataSource AutoLoad="True"
d:DesignData="{d:DesignInstance my1:Zip, CreateList=true}"
Height="0" LoadedData="zipDomainDataSource_LoadedData"
Name="zipDomainDataSource" QueryName="GetZipsQuery" Width="0">
<riaControls:DomainDataSource.DomainContext>
<my:EntityDomainContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<UserControl.Resources>
<myApp:ZipViewModel x:Key="zipDomainDataSource"></myApp:ZipViewModel>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid DataContext="{Binding ElementName=zipDomainDataSource, Path=Data}"
HorizontalAlignment="Left" Margin="319,0,0,311"
Name="grid1" VerticalAlignment="Bottom">
<Grid HorizontalAlignment="Left"
Margin="114,166,0,0" Name="grid3"
VerticalAlignment="Top">
<Grid.DataContext>
<Binding Source="{StaticResource zipDomainDataSource}" Path="Data" />
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:Label Content="Tax Rate:" Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Left" Margin="3"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" Height="23"
HorizontalAlignment="Left" Margin="3"
Name="taxRateTextBox"
Text="{Binding Path=TaxRate, Mode=TwoWay,
NotifyOnValidationError=true, ValidatesOnExceptions=true}"
VerticalAlignment="Center" Width="120" />
</Grid>
<Button Click="button1_Click" Content="Button"
Height="40" HorizontalAlignment="Left"
Margin="116,262,0,0" Name="button1"
VerticalAlignment="Top" Width="132" >
</Button>
<Button Command="{Binding SaveMe}"
Content="Button" Height="40"
HorizontalAlignment="Left" Margin="116,262,0,0"
Name="button1" VerticalAlignment="Top" Width="132" >
<Button.DataContext>
<Binding Source="{StaticResource zipDomainDataSource}" />
</Button.DataContext>
</Button>
</Grid>
</navigation:Page>
private void button1_Click(object sender, RoutedEventArgs e)
{
zipDomainDataSource.SubmitChanges();
}
抽象接口
这包含在可下载的项目中,所以如果您正在寻找使用 POCO 的 MVVM 模式,这里是我的视图模型和相关类。这些类位于我的 **Silverlight 应用程序**项目中。
视图模型类提供了对数据的访问,并公开了可以通过委托从 UI 调用(ZipViewModel.cs)的命令。
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.ServiceModel.DomainServices.Client;
using Entities;
namespace BusinessApp
{
public class ZipViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand SaveMe { get; set; }
private System.Collections.IEnumerable _myData;
private ICollectionView _myDataView;
private DomainDataSource _myDomainDataSource;
public ZipViewModel()
{
// Initialize the object and set the domain context
this._myDomainDataSource = new DomainDataSource();
this._myDomainDataSource.AutoLoad = true;
this._myDomainDataSource.Name = "zipDomainDataSource";
this._myDomainDataSource.LoadingData +=
new EventHandler<loadingdataeventargs>(_zipDomainDataSource_LoadingData);
this._myDomainDataSource.LoadedData +=
new EventHandler<loadeddataeventargs>(_zipDomainDataSource_LoadedData);
this._myDomainDataSource.DomainContext =
new EntRIAServices.Web.AccessableDomainContext();
// Load our data source
this._myDomainDataSource.QueryName = "GetDefaultQuery";
this._myDomainDataSource.Load();
// Link commands
this.SaveMe = new DelegateCommand(SaveCommand, SaveCommand_CanExecute);
// Set vars
this._myData = this._myDomainDataSource.Data;
this._myDataView = this._myDomainDataSource.DataView;
}
void _zipDomainDataSource_LoadingData(object sender, LoadingDataEventArgs e)
{
}
void _zipDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)
{
}
public System.Collections.IEnumerable Data
{
get { return this._myData; }
set { this._myData = value; }
}
public ICollectionView DataView
{
get { return this._myDataView; }
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void SaveCommand(object parameter) {
this._myDomainDataSource.DomainContext.SubmitChanges();
}
public bool SaveCommand_CanExecute(object parameter)
{
return true;
}
}
}
视图模型类调用此自定义处理程序类来包装通用命令接口。
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Input;
namespace BusinessApp
{
public class DelegateCommand : ICommand {
Func<object, bool> canExecute;
Action<object> executeAction;
bool canExecuteCache;
public DelegateCommand(Action<object>
executeAction, Func<object, bool> canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
bool temp = canExecute(parameter);
if (canExecuteCache != temp)
{
canExecuteCache = temp;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
return canExecuteCache;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction(parameter);
}
#endregion
}
}
最后,我想直接从我的 .xaml 文件访问视图模型类;您可以删除对 RIA 服务的引用,并进行延迟绑定,如下所示。
namespace BusinessApp
{
public partial class Page1 : Page
{
public Page1()
{
DataContext = new ZipViewModel();
InitializeComponent();
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
}
然而,我更倾向于直接从 .xaml 文件引用视图模型,因为这样我们可以在数据源中看到该对象。您只需在页面定义中创建对您的命名空间的引用:xmlns:myApp="clr-namespace:BusinessApp"。然后,只需引用该资源。
<UserControl.Resources>
<myApp:ZipViewModel x:Key="zipDomainDataSources"></myApp:ZipViewModel>
</UserControl.Resources>
通过将显式数据上下文设置为在控件中使用上下文。
<Button.DataContext>
<Bind Source="{StaticResource zipDomainDataSource}" Path="Data" />
</Button.DataContext>
然后可以通过调用视图模型上的 iCommand
来执行命令回调(请参阅上面 .xaml 文件中的划线部分)。
Command = "{Binding SaveMe}"
结论
这是本解决方案的一个示例;由于压缩率很高,您需要使用7Zip来解压这些文件。
如您所见,拥有领域服务工厂为提供领域服务的方式增加了极大的灵活性,并使我们的 POCO DLL 保持与上下文无关。至此,本文附带的解决方案演示了一个使用 Model View View Model (MVVM) 模式和 POCO 实体的 Silverlight 4 解决方案。我希望这对所有 C# 业务应用程序开发人员都有帮助,因为这项技术相对较新,而且很难找到好的资源。