自定义Domain Service时遇到实体不能更新的问题及其解决方案
这是在项目中的一个小问题。我们用到了自定义的Domain Service和自定义Entity,如果仅仅是读取数据,没有任何问题。但如果需要通过双向绑定,实现更新,则可能会遇到一个错误。类似下面这样
本文将重现这个问题,并分析原因和提供解决方案。
【备注】Domain Service看起来很不错,尤其是结合LINQ to Entity的话。但在使用自定义Domain Service时会遇到各种各样的问题,很多问题是让人莫名其妙的。我个人觉得这个设计模型还需要进一步完善的
1. 自定义业务实体
下面这个类型会被用到服务中返回文件类型映射的信息
using System.ComponentModel.DataAnnotations; namespace SilverlightApplication3.Web { public class Employee { [Key] public int ID { get; set; } [Editable(true)] public string FirstName { get; set; } [Editable(true)] public string LastName { get; set; } } }
2. 自定义Domain Service
namespace SilverlightApplication3.Web { 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 { public IQueryable<Employee> GetEmployees() { return new[]{ new Employee(){ID=1,FirstName="ares",LastName="chen"}, new Employee(){ID=2,FirstName="bill",LastName="gate"}}.AsQueryable(); } } }
3. 客户端的ViewModel
using System.Collections.ObjectModel; using Microsoft.Practices.Prism.ViewModel; using SilverlightApplication3.Web; using System.ComponentModel; namespace SilverlightApplication3.ViewModels { public class MainPageViewModel:NotificationObject { public MainPageViewModel() { if(DesignerProperties.IsInDesignTool) return; var ctx = new SampleDomainContext(); ctx.Load<Employee>(ctx.GetEmployeesQuery(), (op) => { Employees = new ObservableCollection<Employee>(op.Entities); }, true); } private ObservableCollection<Employee> _Employees; public ObservableCollection<Employee> Employees { get { return _Employees; } set { if(_Employees != value) { _Employees = value; RaisePropertyChanged("Employees"); } } } } }
4. 页面定义
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="SilverlightApplication3.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:vm="clr-namespace:SilverlightApplication3.ViewModels"> <UserControl.DataContext> <vm:MainPageViewModel></vm:MainPageViewModel> </UserControl.DataContext> <Grid x:Name="LayoutRoot" Background="White" Margin="20"> <StackPanel> <TextBlock Text="Employee List" FontSize="20"></TextBlock> <sdk:DataGrid Margin="10" ItemsSource="{Binding Employees}" AutoGenerateColumns="False"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn Binding="{Binding ID}" Header="ID"></sdk:DataGridTextColumn> <sdk:DataGridTextColumn Binding="{Binding FirstName,Mode=TwoWay}" Header="FirstName"></sdk:DataGridTextColumn> <sdk:DataGridTextColumn Binding="{Binding LastName,Mode=TwoWay}" Header="LastName"></sdk:DataGridTextColumn> </sdk:DataGrid.Columns> </sdk:DataGrid> </StackPanel> </Grid> </UserControl>
5. 测试运行
读取数据是没有问题的,但是如果我们去编辑FirstName或者LastName,就会发生如下的错误
这个错误确实让人看得云里雾里的。不是吗?你绝不会想到是要像下面这样解决
6. 添加一个Updatexxxx方法,使得Employee这个EntitySet可以被编辑
解决方案居然是要为Employee这个类型,添加一个特殊的方法,用Update做为前缀。看下面的例子
namespace SilverlightApplication3.Web { 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 { public IQueryable<Employee> GetEmployees() { return new[]{ new Employee(){ID=1,FirstName="ares",LastName="chen"}, new Employee(){ID=2,FirstName="bill",LastName="gate"}}.AsQueryable(); } /// <summary> /// 这个方法用来更新员工 /// </summary> /// <param name="e"></param> public void UpdateEmployee(Employee e) { } } }
当然,也可以不叫UpdateEmployee的名字,但参数必须必须是Employee类型
例如下面这样
namespace SilverlightApplication3.Web { 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 { public IQueryable<Employee> GetEmployees() { return new[]{ new Employee(){ID=1,FirstName="ares",LastName="chen"}, new Employee(){ID=2,FirstName="bill",LastName="gate"}}.AsQueryable(); } /// <summary> /// 这个方法用来更新员工 /// </summary> /// <param name="e"></param> [Update] public void ModifyEmployee(Employee e) { } } }
7.再次运行测试(客户端代码无需任何修改)
请注意,第一行的FirstName已经被修改了(由ares改成了mike)
8. 分析问题的根源
那么,到底发生了什么呢?为什么添加了那个方法就又可以编辑了呢
我们可以打开客户端自动生成的那个文件来看一下,里面有一段代码很特殊
internal sealed class SampleDomainContextEntityContainer : EntityContainer { public SampleDomainContextEntityContainer() { this.CreateEntitySet<Employee>(EntitySetOperations.Edit); } }
是在这里,在这个内部密封的类型里面,它设置了Employee这个EntitySet是可以编辑的,而其实这个EntitySetOperation是一个枚举,具有下面可用的值
#region Assembly System.ServiceModel.DomainServices.Client.dll, v2.0.50727 // C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Silverlight\System.ServiceModel.DomainServices.Client.dll #endregion using System; namespace System.ServiceModel.DomainServices.Client { // Summary: // Enumeration of the types of operations permissable on an System.ServiceModel.DomainServices.Client.EntitySet. [Flags] public enum EntitySetOperations { // Summary: // Only read operations are permitted, no update operations are allowed. None = 0, // // Summary: // New entities may be added Add = 1, // // Summary: // Entities may be updated Edit = 2, // // Summary: // Entities may be removed Remove = 4, // // Summary: // Entities may be added, updated and removed All = 7, } }
9.题外话:
我们在使用LINQ to Entity作为Domain Service的数据模型的时候,可能就意识不到这个问题,是因为我们可以在创建Domain Service的时候设置是否允许编辑
然后自动生成的Domain Service里面就自动具有了Update,Delete,Insert之类的方法,这样就允许这些业务实体支持更新了。