65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2010年12月9日

CPOL

4分钟阅读

viewsIcon

40171

downloadIcon

634

本文演示了如何创建领域服务工厂来托管纯粹的类对象(POCO)实体,并通过 Silverlight 4 中的 RIA 服务使用它们。

引言

如果您还没有,请参阅相关文章:通过 RIA 服务使用 POCO 实体,因为本文是关于通过富互联网应用程序 (RIA) 服务使用纯粹的类对象 (POCO) 实体的配套文章。您实际上可以使用此示例通过 OData(开放数据协议)托管任何内容,而不仅仅是 POCO 实体。对于此示例,您必须安装以下软件:

背景

使用 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。

XAML
<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>
.xaml.cs
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# 业务应用程序开发人员都有帮助,因为这项技术相对较新,而且很难找到好的资源。

© . All rights reserved.