代码改变世界

LINQ to SQL活学活用(4):监视你的一举一动

2008-11-12 22:40 李永京 阅读(...) 评论(...) 编辑 收藏

引入

我们看看数据访问层有什么地方可以扩展呢?想到这个实现了吗?我需要时刻监视所有数据的一举一动,是谁创建它的?何时创建的?是谁修改它的?何时修改的?你想到这个问题怎么实现呢?使用开源日志记录框架例如log4net,或者自己写一份单独的实现利用IoC动态切入到你的程序中?没有必要!看看用LINQ to SQL如何实现这个功能吧!

注意:这是我第一次分析设计,我只想通过这个系列来讨论学习设计方面的东西,当然很多思想还不成熟或者有错误,只是希望通过这个系列来学习构架设计方面的东西,希望大牛们指点,大家拍砖头!

系列文章导航

LINQ to SQL活学活用(1):这要打破旧观念

LINQ to SQL活学活用(2):躲起来别让我看见

LINQ to SQL活学活用(3):嗅出“臭味”烟消云散

LINQ to SQL活学活用(4):监视你的一举一动

系列参考代码下载:LINQ to SQL

改进

这也可以运用GoF23中的观察者模式,让多个观察者对象(对象创建、修改)同时监听某一个主题对象(这里就是数据访问对象Customer)。这个主题对象Customer在状态发生变化时,会通知所有观察者对象(对象创建、修改),使它们能够自动更新自己,就可以实现监视Customer的一举一动了。

我们来实现这个功能,先看看这篇完成的整体架构。

程序架构

数据访问层

1.数据访问基类

我们为整个数据访问对象定义一个公共基类,首先定义定义数据访问基类接口,让抽象数据访问基类实现这个接口,这个基类用于存放数据访问对象共有的属性,设置为抽象的。在我们的系统中,并非只有Customer这个数据访问对象,将来肯定要增加另外很多的了,这样如果将来增加另外的数据访问对象,只需继承这个基类重写属性即可。

Step1:定义数据访问基类接口:

public interface IDataAccessBase
{
    int Id { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime ModifiedDate { get; set; }
    string CreatedBy { get; set; }
    string ModifiedBy { get; set; }
}

Step2:定义抽象的数据访问基类实现这个接口:针对抽象编程,减少了与具体类的耦合。

public abstract class DataAccessBase : IDataAccessBase
{
    public abstract int Id { get; set; }
    public abstract DateTime CreatedDate { get; set; }
    public abstract DateTime ModifiedDate { get; set; }
    public abstract string CreatedBy { get; set; }
    public abstract string ModifiedBy { get; set; }
}

2.数据访问对象Customer

既然在数据访问基类中定义了抽象的Id、CreatedBy、CreatedDate、ModifiedBy、ModifiedDate等属性,那么我们改修改数据访问对象Customer的属性了,看看截图

Customer重写属性

修改Customer类,增加了CreatedBy、CreatedDate、ModifiedBy、ModifiedDate属性,并把CustomerId换为Id,需要特别声明的是,这些字段属性的修饰符必须修改为重写“override"。

修改数据访问对象Customer接口,继承数据访问基类接口IDataAccessBase。

public interface ICustomer : IDataAccessBase
{
    int Id { get; set; }
    string FirstName { get; set; }
    string LastName { get; set; }
}

修改数据访问对象Customer分部类,继承数据访问基类接口IDataAccessBase和数据访问对象Customer接口,这时数据访问对象Customer一共七个属性了。

public partial class Customer : DataAccessBase, ICustomer
{

}

数据访问对象Customer修改完成,如果我们要添加另外的数据访问对象,很容易的去扩展,也很容易的重写自己的属性。

3.数据访问对象DataContext

定义数据访问对象DataContext分部类DataAccessEntitiesDataContext,用于存放一些操作对象的公共方法,例如是谁创建数据的?何时创建这个数据的?是谁修改数据的?何时修改的?我们可以重写LINQ to SQL内置的方法实现。

DataContext为每个实体都定义了以下三个分部方法:InsertEntityName()、UpdateEntityName()、DeleteEntityName()。

我们在OR设计器中添加了Customer类,内置定义了三个分部方法,就是:

partial void InsertCustomer(Customer instance);
partial void UpdateCustomer(Customer instance);
partial void DeleteCustomer(Customer instance);

我们就重写上面三个方法实现观察的作用,在重写上面的方法的时需要调用ExecuteDynamicInsert()、ExecuteDynamicUpdate()、ExecuteDynamicDelete()方法。

  • ExecuteDynamicInsert()方法:在插入重写方法中调用,以向LINQ to SQL重新委托生成和执行插入操作的动态SQL的任务。
  • ExecuteDynamicUpdate()方法:在更新重写方法中调用,以向LINQ to SQL重新委托生成和执行更新操作的动态SQL的任务。
  • ExecuteDynamicDelete()方法:在删除重写方法中调用,以向LINQ to SQL重新委托生成和执行删除操作的动态SQL的任务。

我们重写新建一个分部类DataAccessEntitiesDataContext.cs,在这个分部类中重写上面三个分部方法的具体实现。这篇我只需重写InsertCustomer方法(插入Customer时触发)和UpdateCustomer(更新Customer时触发)两个方法,为了程序可以复用,我们把插入数据访问对象时、修改数据访问对象时记录信息封装成两个方法实现。

Step1:创建数据访问对象实现记录创建者(这里获取机器当前用户名)和创建时间,由于修改字段不为空,这里默认使用创建时的信息。

private void InsertDataAccessBase(DataAccessBase instance)
{
    //创建对象时(记录创建者和时间)
    instance.CreatedBy = WindowsIdentity.GetCurrent().Name;
    instance.CreatedDate = DateTime.Now;
    //修改对象时(记录修改者和时间)
    instance.ModifiedBy = WindowsIdentity.GetCurrent().Name;
    instance.ModifiedDate = DateTime.Now;
    //在插入重写方法中调用
    //向LINQ to SQL重新委托生成和执行插入操作的动态SQL的任务
    ExecuteDynamicInsert(instance);
}

Step2:修改数据访问对象实现记录修改者(这里获取机器当前用户名)和修改时间。

private void UpdateDataAccessBase(DataAccessBase instance)
{
    //修改对象时(记录修改者和时间)
    instance.ModifiedBy = WindowsIdentity.GetCurrent().Name;
    instance.ModifiedDate = DateTime.Now;
    ExecuteDynamicUpdate(instance);
}

Step3:重写InsertCustomer()分部方法,调用InsertDataAccessBase()方法即可。

partial void InsertCustomer(Customer instance)
{
    InsertDataAccessBase(instance);
}

Step4:重写UpdateCustomer()分部方法,调用UpdateDataAccessBase()方法即可。

partial void UpdateCustomer(Customer instance)
{
    UpdateDataAccessBase(instance);
}

如果系统又要增加别的数据访问对象的话,如果想实现记录功能只需要调用InsertDataAccessBase()、UpdateDataAccessBase()方法即可,这样就提高了代码的复用性。

4.数据访问对象Customer外观Facade

这个同上一篇一样,无需任何改动,外观Facade依赖关系为Customer接口,这里Customer接口没有变化哦!

单元测试层

以上所有的工作离不开测试!只有通过测试,才可以验证我们程序的正确性!好了,我们开始,既然我们修改了数据访问对象,那么也要修改对应的测试了!

1.创建数据访问对象测试

测试创建数据访问对象,比较创建者是否同机器用户名一致!

[Test]
public void UpdateTest()
{
    ICustomer newCustomer = CreateAndSaveNewCustomer("YJing", "Lee");
    Assert.AreNotEqual(0, newCustomer.Id);
    Assert.AreEqual("YJing", newCustomer.FirstName);

    Assert.AreNotEqual(DateTime.MinValue, newCustomer.CreatedDate);
    Assert.AreNotEqual(DateTime.MinValue, newCustomer.ModifiedDate);
    Assert.IsNotNull(newCustomer.CreatedBy);
    Assert.IsNotNull(newCustomer.ModifiedBy);
    //获取机器当前用户名
    string currentUserName = WindowsIdentity.GetCurrent().Name;
    //比较创建者和修改者
    Assert.AreEqual(currentUserName, newCustomer.CreatedBy);
    Assert.AreEqual(currentUserName, newCustomer.ModifiedBy);
}

测试输出如下

测试结果

分析一下:先验证数据库是否存在,存在删除,然后重新创建一个新的数据库架构和Customer表,这个表有7个字段。我们向数据库中插入了一条数据,系统自动增加了创建者、创建时间、修改者、修改时间等信息。

2.修改数据访问对象测试

既然我们增加了这个功能,那么我们来测试验证一下:先插入一条数据,然后等待3秒,更新这个对象,看看修改时间是否是我们预期的变化值。

[Test]
public void ModifyCustomerUpdatesModifiedDateTest()
{
    ICustomer tempCustomer = CreateAndSaveNewCustomer("YJing", "Lee");
    DateTime originalLastModifiedDate = tempCustomer.ModifiedDate;

    Thread.Sleep(1500);
    tempCustomer.FirstName = "CnBlogs";
    Facade.UpdateCustomer(tempCustomer);
    Assert.AreNotEqual(originalLastModifiedDate, tempCustomer.ModifiedDate);
}

测试输出如下:

测试结果

这里省略了创建数据库的过程,第一句插入一个对象,第二句更新一个对象,仔细看看其修改时间是否变化了。

大家再测试下原先的几个测试吧!

结语

这篇通过重写分部方法的方法,来实现了非常酷的功能,时刻监视着数据。也从设计的角度知道针对抽象编程,减少了与具体类的耦合。在这里我们很少和Customer类打交道。