Spiga

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

2008-11-04 21:52 by 李永京, 4867 visits, 网摘, 收藏, 编辑

引入

这个系列呢?我想去挖掘LINQ to SQL另外的一面,把LINQ to SQL发挥的淋漓尽致!按照原来.NET2.0时代比较传统的方法,先建立一个数据库,然后拖到DataSet中做一些读取方法啊,或者直接手写一些操作方法啦,最后在数据访问层中调用这些方法,业务逻辑层中业务判断,在页面表现层上就可以光明正大的使用了。呵呵!这个系列就由我带着大家利用LINQ to SQL全新的思维方式来看看另一面!这个系列的目标是构建一个N层程序,要求这个程序可测试、可维护、可复用、可扩展,最大的要求使用LINQ to SQL作为数据访问层。就是这么多了,别的就不要求了。

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

系列文章导航

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

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

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

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

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

程序架构

现在比较经典的架构,看看下面图片。

分层架构

如何实现

在一个N层应用程序中我们如何使用LINQ to SQL呢?这给刚刚入门的朋友的确是个难题,使用LINQ to SQL就是ORM技术,可以很轻松的实现对数据库记录增删查改操作,但是我们如何去“构建它”才更合理,更科学,更好用?这才是我们真正要学习的,使用面向对象的接口、抽象达到这个目的,面向接口编程就是更好的选择,可以更好的维护和测试。

下面一步一步完成这个程序吧,看到标题了吗?这篇是打破旧观念!看看接下来有什么神秘的地方。首先新建一个工程,有两个类库分别为:数据访问层DataAccess和单元测试UnitTest,看看截图:

程序架构

数据访问层

这篇先创建一个数据访问对象,由于使用LINQ to SQL作为ORM,我们新建一个LINQ to SQL类DataAccessEntities.dbml作为数据访问对象DataContext,一切可视化的操作,为了展示,在O/R设计器中新建一个Customer类(数据访问对象),添加CustomerId、FirstName、LastName三个成员属性,并在成员属性的属性窗口修改相应的属性。

为了展示,这里简单描述下我的操作,设置成员属性如下:

  • CustomerId:成员属性的类型Int,属性的名称CustomerId,数据库列的名称CustomerId,数据库列参与表的主键True,插入时在数据库中自动生成值True,指定插入时自动同步属性。
  • FirstName:属性的名称FirstName,数据库列的名称FirstName,其它默认
  • LastName:属性的名称LastName,数据库列的名称LastName,其它默认

好了,简单的数据访问对象创建好了,下面测试了~~

单元测试层

单元测试用于测试用例的成功与失败,在软件开发中起着尤为重要的地位。当然使用单元测试的要求非常严格,这个系列我将严格遵守,我整理的要求如下

1.尽量在断言中提供错误信息描述,这可以很容易的发现你的错误。

2.每个测试完全独立,体现面向对象中的单一职责原则。

3.不要假设数据库中有什么数据或者哪些数据不在数据库中,在每个测试方法前保证数据库为空的。

4.测试时需要的一些原始数据要作为测试的一部分在测试方法前加载到数据库中。

遵守上面的要求,就可能面临以下的麻烦:

  • 在测试前删除每张表的每行数据,PK关联需要另外写删除SQL语句脚本。
  • 业务逻辑层创建了你没有预料到的数据,你不好处理。
  • 如果你测试失败,在数据库中查看数据不在数据库中,没有任何提示信息你不知道系统做了什么。
  • 由于记录被锁定,插入数据不写入数据库,也不能在不同数据库连接中读取。

幸好LINQ to SQL做到了上面的一切,LINQ to SQL的DataContext可以用来管理数据架构,它提供了DatabaseExists()、DeleteDatabase()、CreateDatabase()方法可以很轻松的创建删除数据库。

注意,这个系列我使用NUnit.Framework测试,所以要引用nunit.framework.dll程序集,另外由于代码编写中涉及了这个类库引用System.configuration.dll、System.Data.Linq.dll程序集和Business项目。

1.创建一个测试基类

测试通常是很复杂的,我们创建一个测试基类用于编写一些通用的方法,然后所有测试类都继承这个测试基类,这个基类主要完成以下功能。

第一步:手动配置DataContext连接和日志

在我们的数据访问层仅仅创建了一个数据访问对象,没有和数据库打交道,测试时需要对DataContext连接到数据库。另外为了测试显示详细的信息,我们还要使用DataContext的日志功能。

public string ConnectionString
 {
     get
     {
         if (ConfigurationManager.ConnectionStrings["conn"] == null ||
             String.IsNullOrEmpty(ConfigurationManager.ConnectionStrings["conn"].ConnectionString) == true)
         {
             throw new InvalidOperationException("默认的连接字符串不存在或者为空");
         }
         return ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
     }
 }
 private DataAccessEntitiesDataContext m_dataContext;
 public DataAccessEntitiesDataContext DataContext
 {
     get
     {
         if (m_dataContext == null)
         {
             m_dataContext = new DataAccessEntitiesDataContext(ConnectionString);
             m_dataContext.Log = Console.Out;
         }
         return m_dataContext;
     }
 }

第二步:创建数据库

在测试之前,打开临时连接用于创建数据库,使用DataContext提供了DatabaseExists()、DeleteDatabase()、CreateDatabase()方法,先使用DatabaseExists()验证数据库是否存在,如果存在使用DeleteDatabase()方法删除,使用CreateDatabase()方法创建一个数据库架构,及时释放这个连接。

[TestFixtureSetUp]
public void Init()
{
    DataAccessEntitiesDataContext context = new DataAccessEntitiesDataContext(ConnectionString);
    context.Log = Console.Out;
    if (context.DatabaseExists() == true)
    {
        context.DeleteDatabase();
    }
    context.CreateDatabase();
    context.Connection.Close();
    context.Dispose();
    context = null;
}

第三步:关闭所有连接

在测试结束关闭所有的连接,这一步非常必要哦。

[TestFixtureTearDown]
public void Tear()
{
    DataContext.Connection.Close();
}

第四步:设置连接字符串

新建一App.config文件,设置连接字符串:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="conn" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=LINQ;Integrated Security=True"/>
  </connectionStrings>
</configuration>

2.测试类

我们新建一个测试类用于测试数据访问对象,这里简单测试创建一个Customer对象,新建CustomerFixture.cs类继承UnitTestBase

编写创建Customer对象方法如下,创建一个Customer对象,调用InsertOnSubmit()方法插入,调用DataContext.SubmitChanges()方法提交数据库。

[Test]
public void CreateCustomerTest()
{
    Customer customer = new Customer() { FirstName = "YJing", LastName = "Lee" };
    Assert.AreEqual(0, customer.CustomerId, "测试前CustomerId为0");
    DataContext.Customer.InsertOnSubmit(customer);
    DataContext.SubmitChanges();
    Assert.AreNotEqual(0, customer.CustomerId, "调用SubmitChanges()方法后CustomerId不为0");
}

测试成功,看看输出结果:

测试输出信息

OH!非常酷!首先创建了数据库架构,然后插入了一条数据。再次测试一下这个方法将是什么结果呢?这个问题就留给大家了。

结语

看到了吗?这就是全新的方式来完成非常酷的工作!从面向对象入手,利用LINQ to SQL生成其关系型数据库,一切就是这么容易!这篇仅仅在数据访问层上新建一数据访问对象,在测试中调用DataContext提供的方法完成数据操作!下篇更精彩!

2
0
(请您对文章做出评价)
« 上一篇:NHibernate之旅(15):探索NHibernate中使用存储过程(上)
» 下一篇:NHibernate之旅(16):探索NHibernate中使用存储过程(中)
Add your comment

26 条回复

  1. #1楼 Astar      2008-11-04 21:58
    学习!NH还没完的吧,就又开始新系列了。真强~
      回复  引用  查看    
  2. #2楼[楼主] 李永京      2008-11-04 22:00
    @Astar
    两边同时进行,这个提交参赛,可以扛得住!NHibernate明天发
      回复  引用  查看    
  3. #3楼 李彦      2008-11-04 22:14
    哎,还是早点开始转到Entity framework吧
      回复  引用  查看    
  4. #4楼 kunkun      2008-11-04 22:17
    真是辛苦永京兄了。
      回复  引用  查看    
  5. #5楼[楼主] 李永京      2008-11-04 22:20
    @李彦
    恩,关于LINQ to SQL就用这个系列的6篇文章来告终吧~~~不过这个思想在EF上直接可用,这个系列在于一些设计的思想
      回复  引用  查看    
  6. #6楼[楼主] 李永京      2008-11-04 22:21
    @kunkun
    呵呵,对了,这节连接字符串没有添上
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <connectionStrings>
    <add name="conn" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=LINQ;Integrated Security=True"/>
    </connectionStrings>
    </configuration>
      回复  引用  查看    
  7. #7楼 阿什地方[未注册用户]2008-11-04 22:26
    不是说LINQ to SQL微软不作为未来方向了嘛?
      回复  引用    
  8. #8楼[楼主] 李永京      2008-11-04 22:28
    @阿什地方
    这个系列在于一些现在设计的思想,至于在数据访问层上用什么技术,自己看着办,用NHibernate、LINQ to SQL、EF我看都差不多。注重思想的改变。
      回复  引用  查看    
  9. #9楼 Gray Zhang      2008-11-04 22:41
    @阿什地方
    不作为未来方向的技术有很多,.net remoting也已经开始退出舞台,但有用的始终有用,即便推动了支持他依旧是一个非常优秀的技术,用于生产环境也毫无问题
      回复  引用  查看    
  10. #10楼 您所在的用户已关机,无法侦测ID[未注册用户]2008-11-04 23:04
    行文流畅,思路清晰!
    不过,问一下,旧观念是什么?
      回复  引用    
  11. #11楼[楼主] 李永京      2008-11-04 23:14
    @您所在的用户已关机,无法侦测ID
    谢谢!按照原来.NET2.0/1.1时代比较传统的方法,先建立数据库表,然后写DataSet中一些方法。利用面向关系的数据库写面向对象的代码,现在我们直接写类来生成数据库。在引入中说明了哦。
      回复  引用  查看    
  12. #12楼[楼主] 李永京      2008-11-04 23:18
    @Gray Zhang
    思想才是自己的东西~~
      回复  引用  查看    
  13. #13楼 包建强      2008-11-05 00:42
    @Gray Zhang
    Remoting仍然有大部分概念保留在WCF中。WCF是所有这些通讯技术的U运算。

    我非议WCF的一个原因就是,我们在学习WCF的同时,也还要去学习WebService、Remoting、Socket编程。这就等于额外增加了要学习WCF的负担。
      回复  引用  查看    
  14. #14楼 TerryLee      2008-11-05 09:03
    这篇先创建一个业务逻辑对象,由于使用LINQ to SQL作为ORM,我们新建一个LINQ to SQL类BusinessEntities.dbml作为业务逻辑对象DataContext,一切可视化的操作,为了展示,在O/R设计器中新建一个Customer类(业务逻辑对象),添加CustomerId、FirstName、LastName三个成员属性,并在成员属性的属性窗口修改相应的属性
    ________________________________________
    这个放在业务逻辑中有些牵强了,呵呵~~~
      回复  引用  查看    
  15. #15楼 9029[未注册用户]2008-11-05 09:21
    不是说Entity framework才是王道,linq to sql是过渡,还发这过渡文章??
      回复  引用    
  16. #16楼 Tristan Guo      2008-11-05 09:23
    你的数据库访问层其实是不存在的吗?
      回复  引用  查看    
  17. #17楼[楼主] 李永京      2008-11-05 09:49
    @TerryLee
    这个是数据访问层的东西,我没有再分层了,就在一个Bussiness层中了,不过心中清楚就行了,在Bussiness层就一个LINQ to SQL Class。看着简便就没有放在DAL中了
      回复  引用  查看    
  18. #18楼[楼主] 李永京      2008-11-05 09:50
    @9029
    思想思想,在数据访问层只需换个你需要的技术就行了。
      回复  引用  查看    
  19. #19楼[楼主] 李永京      2008-11-05 09:51
    @Tristan Guo
    谢谢指出,已经改正
      回复  引用  查看    
  20. #20楼[楼主] 李永京      2008-11-05 10:24
    @TerryLee
    谢谢老李指出错误,我改好了!
      回复  引用  查看    
  21. #21楼 JuzzPig(橘子猪)[未注册用户]2008-11-05 10:42
    我最近的项目中,也是类似的做法。不采取传统分层手段。
    我不认为一个项目将来有多大的可能从SQL Server迁移到My SQL或者其他存储介质,即便有可能。把LINQ TO SQL替换为LINQ TO MYSQL即可。
    所以我不知道把LINQ TO SQL生搬硬套放进传统的三层架构中有什么意义。
    不过我给DBML层的命名不是Business而是Domain
      回复  引用    
  22. #22楼[楼主] 李永京      2008-11-05 10:50
    @JuzzPig(橘子猪)
    分层就是为了解耦,每个层负责每层的功能,比较单一
      回复  引用  查看    
  23. #23楼 winzheng      2008-11-07 13:25
    如此看来,Business 层的对象(Customer)和数据访问对象(Customer)应该是同一个了?突然觉得有点不习惯了...
      回复  引用  查看    
  24. #24楼[楼主] 李永京      2008-11-07 15:39
    @winzheng
    恩,不好意思,第一次写设计方面文章,很多名词说错了,这是一个对象
      回复  引用  查看    
  25. #25楼 林小2009-04-23 21:49
    ----- Test started: Assembly: UnitTest.dll ------

    .Net SqlClient Data Provider: 已将数据库上下文更改为 'LINQ'。.
    TestCase 'UnitTest.CustomerFacadeFixture.UpdateTest' failed: TestFixtureSetUp failed in CustomerFacadeFixture
    TestFixture failed: System.Data.SqlClient.SqlException : 在 sysdatabases 中找不到数据库 'MASTER' 所对应的条目。没有找到具有该名称的条目。请确保正确地输入了该名称。
    在 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
    在 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
    在 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
    在 System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
    在 System.Data.SqlClient.SqlInternalConnectionTds.ChangeDatabaseInternal(String database)
    在 System.Data.SqlClient.SqlInternalConnection.ChangeDatabase(String database)
    在 System.Data.SqlClient.SqlConnection.ChangeDatabase(String database)
    在 System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.DeleteDatabase()
    在 System.Data.Linq.DataContext.DeleteDatabase()
    在 UnitTest.UnitTestBase.Init() 位置 C:\Documents and Settings\Administrator\桌面\linq2sql\UnitTest\UnitTestBase.cs:行号 48

    0 passed, 1 failed, 0 skipped, took 6.64 seconds (NUnit 2.4).
    用楼主附上的代码测试,(对同一个方法)第一次运行测试时是可以成功的,不过再第二次运行测试就发生了异常了,用分步测试(Test With Debugger)跟踪测试时发现
    [TestFixtureSetUp]
    public void Init()
    {
    try
    {
    DataAccessEntitiesDataContext context = new DataAccessEntitiesDataContext(ConnectionString);
    context.Log = Console.Out;
    if (context.DatabaseExists() == true)
    {
    context.DeleteDatabase();
    }
    context.CreateDatabase();
    context.Connection.Close();
    context.Dispose();
    context = null;
    }
    catch (Exception ex)
    {
    throw ex;
    }
    }
    中的context.DeleteDatabase();调用引发了异常,是不是context不具有删除数据库的权限,还是缺了什么呢?希望楼主看一下
      回复  引用    
  26. #26楼 林小2009-04-23 21:57
    将UnitTest中的Init()改成这样时就不会出现异常了
    [TestFixtureSetUp]
    public void Init()
    {
    try
    {
    DataAccessEntitiesDataContext context = new DataAccessEntitiesDataContext(ConnectionString);
    context.Log = Console.Out;
    if (context.DatabaseExists() == false)
    {
    context.CreateDatabase();
    }
    context.Connection.Close();
    context.Dispose();
    context = null;
    }
    catch (Exception ex)
    {
    throw ex;
    }
    }
    不过这要在每次运行测试前都要手动删除linq数据库
      回复  引用