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

WCF 示例 – 第一章 – 基线

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (35投票s)

2010 年 6 月 28 日

CPOL

5分钟阅读

viewsIcon

130900

模型、存储库和服务的初稿版本。

Previous Next
引言 第二章

系列文章

WCF 示例系列文章描述了如何使用 WCF 进行通信和 NHibernate 进行持久化来设计和开发 WPF 客户端。系列介绍描述了文章和项目的范围。

章节概述

这是本系列的第一章。本章为 eDirectory 解决方案奠定了基础;我们将介绍一些核心解决方案概念,例如 Customer 实体、存储库的定义、客户服务以及我们的最初几个测试。在后面的章节中,我们将讨论如何演进本文中描述的原始设计,以及如何简化持久化组件,以便在需要额外的实体时,在使解决方案的这一部分正常工作方面付出很少的努力。

eDirectory 解决方案的最新代码可在 CodePlex 上找到。

模型 - 客户实体

正如我们在介绍中提到的,我们将使模型非常简单,目前只需要一个实体。customer 类声明如下:

public class Customer
{
    protected Customer() { }

    public long Id { get; private set; }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Telephone { get; private set; }
}

需要注意的几点:属性是 protected,因此只能通过操作方法更改它们;该类不提供 public 构造函数,通过这种方式,我们努力设置类,使其状态只能通过实体内声明的众所周知的方法进行更改。我们不应该假设我们的类将以正确的方式使用,因此将构造函数设置为 protected 提供了一种很好的方式来控制新实例的创建。当我们在 第二章 - 存储库 中讨论持久化时,我们将证明这种方法是合理的。

此外,实体在 eDirectory.Domain 程序集中定义,该程序集将仅由我们的服务器端组件使用;但是,客户端不使用域类,而是向客户端提供 DTO。

客户服务

eDirectory 提供以下客户服务:

函数 描述
GetById 通过提供唯一 ID 检索客户
ListAll 返回所有客户实例
CreateNewCustomer 创建新的客户实例
UpdateCustomer 修改现有客户实例

服务使用以下约定定义:

  • 输入参数将是基元或数据传输对象
  • 所有服务方法都是函数,也就是说,它们都向客户端返回一些最基本的信息。
我们必须保持服务签名简单,以避免 WCF 序列化问题。我们还希望在客户端和服务器之间提供一种通信模式,而不依赖于特定的 WCF 机制,我们将在 第三章 - 响应 中看到如何解决这个问题。

此外,服务契约(接口)在公共程序集中声明,因为服务器和客户端组件都需要它们,我们不会在客户端使用 Web References 向导功能在客户端生成 WCF 代理。对于 DTO 也是如此,它们在服务器和客户端组件之间共享。

在此阶段,我们需要我们的第一个 DTO,它与我们的 Customer 实体非常相似:

public class CustomerDto
{
    public long CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Telephone { get; set; }
}

现在,我们可以定义我们的第一个服务契约:

[ServiceContract(Namespace = "http://wcfbyexample/customerservices/")]
public interface ICustomerService
{
    [OperationContract]
    CustomerDto CreateNewCustomer(CustomerDto customer);

    [OperationContract]
    CustomerDto GetById(long id);

    [OperationContract]
    CustomerDto UpdateCustomer(CustomerDto customer);

    [OperationContract]
    List<customerdto> FindAll();
}

让我们尝试创建一个创建客户实例的机制。

存储库

领域实体应该与持久化后端机制解耦。存储库模式为我们的领域实体提供所需的持久化功能,从而实现解耦。

此时,以下存储库定义就足够了:

public interface IRepository<TEntity>
{
    #region CRUD operations
        
    TEntity Save(TEntity instance);
    void Update(TEntity instance);
    void Remove(TEntity instance);
        
    #endregion

    #region Retrieval Operations

    TEntity GetById(long id);
    IQueryable<TEntity> FindAll();
        
    #endregion
}  

最重要的一点是,我们定义了一个泛型存储库,并且 FindAll 方法返回一个 IQueryable 实例。

创建工厂方法

此时,我们可以回到我们的领域类,并提供一种机制来促进客户实例的创建:

public static Customer Create(IRepository<customer> repository, CustomerDto operation)
{
    var instance = new Customer
                            {
                                FirstName = operation.FirstName,
                                LastName = operation.LastName,
                                Telephone = operation.Telephone
                            };

    repository.Save(instance);
    return instance;
} 

请注意 DTO 实例如何用于填充新客户实例的属性。然后针对传入的存储库执行 save 方法。我们将在实体方法和工厂中传递存储库实例,而不是在实体中使用对存储库实现的引用。随着我们涵盖应用程序的未来方面,这种方法将不断演进。这里需要注意的另一个方面是,工厂方法创建持久化的客户实例,因此当客户实例返回时,持久化上下文完全了解新实例,实际上在调用 save 方法后 ID 就可用了。

内存存储库实现

此时,我们可以开始进行一些 TDD,我们可以模拟存储库等等,但是提供一个内存存储库实现相对来说成本不高,并且可以促进测试和业务探索。由于内存持久化器永远不会在生产环境中使用,我们希望在不同的程序集 eDirectory.Naive 中声明此实现。我们为内存持久化器定义一个具有通用功能的抽象类:

public abstract class RepositoryEntityStore<TEntity>
        :IRepository<TEntity>
{
    protected readonly IDictionary<long, TEntity> RepositoryMap = new Dictionary<long, TEntity>();

    #region IRepository<TEntity> Members

    public abstract TEntity Save(TEntity instance);

    public abstract void Update(TEntity instance);

    public abstract void Remove(TEntity instance);

    public TEntity GetById(long id)
    {
        return RepositoryMap[id];
    }

    public IQueryable<TEntity> FindAll()
    {
        return RepositoryMap.Values.AsQueryable();
    }

    #endregion
}

然后我们创建 Customer 实现:

public class RepositoryCustomer
        : RepositoryEntityStore<customer>
{
    public override Customer Save(Customer instance)
    {
        ...
    }

    public override void Update(Customer instance)
    {
        ...
    }

    public override void Remove(Customer instance)
    {
        ...
    }

    private void GetNewId(Customer instance)
    {
        ...
    }

    private readonly IDictionary<type,> Setters = new Dictionary<type,>();

    private MethodInfo GetSetter(Type type)
    {
        ...
    }
}

上述代码还有很大的改进空间,但值得注意的是实体如何使用实体 ID 存储在哈希映射中。正如我在系列介绍中提到的,我们对数据库设计拥有完全控制权,因此我们选择在所有表中都有一个数字主键,这简化了领域实体的定义。在下一章:第二章 - 存储库 中,我们将看到上述代码如何演变,并且只需要一个适用于所有实体类型的实现。

我们的第一个测试

让我们使用工厂创建一个客户,然后检查 ID 和名字是否正确:

[TestClass]
public class CustomerTests
{
    [TestMethod]
    public void CreateCustomer()
    {
        // need to create an instance of the repository
        var repository = new RepositoryCustomer();
        var dto = new CustomerDto
                                    { 
                                        FirstName = "Joe",
                                        LastName = "Bloggs",
                                        Telephone = "9999-8888"
                                    };

        var customer = Customer.Create(repository, dto);
        Assert.IsFalse(customer.Id == 0, "Customer Id should have been updated");
        Assert.AreSame(customer.FirstName, dto.FirstName, "First Name are different");
    }
}

客户服务实现

我们现在准备创建我们的第一个服务实现:

public class CustomerService
        :ICustomerService
{
    public IRepository<customer> Repository { get; set; }
        
    #region ICustomerService Members

    public Common.Dto.Customer.CustomerDto CreateNewCustomer(CustomerDto dto)
    {            
        var customer = Customer.Create(Repository, dto);
        return new CustomerDto
                            {
                                CustomerId = customer.Id,
                                FirstName = customer.FirstName,
                                LastName = customer.LastName,
                                Telephone = customer.Telephone
                            };
    }
    ...
}

让我们创建一个执行此服务的测试:

[TestClass]
public class CustomerServiceTests
{
    [TestMethod]
    public void CreateCustomer()
    {
        // need to create an instance of the repository
        var service = new CustomerService { Repository = new RepositoryCustomer() };
        var dto = new CustomerDto
                            {
                                FirstName = "Joe",
                                LastName = "Bloggs",
                                Telephone = "9999-8888"
                            };

        var customer = service.CreateNewCustomer(dto);
        Assert.IsFalse(customer.CustomerId == 0, 
            "Customer Id should have been updated");
        Assert.AreSame(customer.FirstName, dto.FirstName, 
                       "First Name are different");
    }
}

章节总结

我们在这篇文章中涵盖了许多方面。首先,我们设置了几个项目:Common、Domain 和 Naive。我们描述了设计领域实体及其与存储库关系的基本知识。我们通过使用服务创建客户实例并编写最初几个测试来结束本章。

在下一章中,我们将讨论到目前为止使用的一些建议模式,我们将识别一些可以改进的方面,并将引入存储库定位器概念。

© . All rights reserved.