上一篇文章,我们分析了数据访问是如何定义,以及如何与其它(特别是Business)模块耦合的,今天我们来看一下数据访问(DataAccess)是如何实现的。
从系统结构图和上一篇“数据访问的定义”中可以知道,数据访问层的任务就是实现IEntityDataAccess和IDatabase接口。由于Linq的出现,我们只需要一个Adapter,将Linq2Sql的类适配到IEntityDataAccess就可以了,其类设计图和文件结构如下如下:
从图中可以看到,Database类继承与DataContext,而实现了IDatabase接口;EntityDataAccessAdapter使用System.Data.Linq.Table这个Linq的核心类实现了IEntityDataAccess接口。
Database类的代码如下:
1: using System;
2: using System.Collections.Generic;
3: using System.Data.Linq;
4: using System.Data.Linq.Mapping;
5: using System.IO;
6: using System.Linq;
7: using System.Reflection;
8: using System.Text;
9:
10: using DongBlog.Common;
11: using DongBlog.Business;
12: using DongBlog.Business.Blogs;
13:
14: namespace DongBlog.DataAccess
15: {
16: /// <summary>
17: /// 数据库
18: /// </summary>
19: public class Database : DataContext, IDatabase
20: {
21: #region Singelton Pattern
22:
23: private const string MAPPING_SOURCE_RESOURCE_NAME = "Srims.DataAccess.MappingSource.Xml";
24: private static MappingSource _MappingSource;
25: private static object _Locker = new object();
26:
27: private Database(string connectionString, MappingSource mappingSource)
28: : base(connectionString, mappingSource)
29: {
30: }
31:
32: private static MappingSource getMappingSource()
33: {
34: if (_MappingSource == null)
35: {
36: lock (_Locker)
37: {
38: if (_MappingSource == null)
39: {
40: Stream mapingSourceStream = Assembly.GetAssembly(typeof(Database))
.GetManifestResourceStream(MAPPING_SOURCE_RESOURCE_NAME);
41: if (mapingSourceStream == null)
42: throw new System.IO
.InvalidDataException(
"映射文件不存在!您确定已经将其编译属性设置为\"嵌入资源(Embedded Resource)\"?");
43: _MappingSource = XmlMappingSource.FromStream(mapingSourceStream);
44: }
45: }
46: }
47: return _MappingSource;
48: }
49:
50: /// <summary>
51: /// 构造新的数据库
52: /// </summary>
53: /// <param name="connectionString">数据库连接字符串</param>
54: /// <returns>数据库实例</returns>
55: public static Database New(string connectionString)
56: {
57: if (string.IsNullOrEmpty(connectionString))
58: throw new ArgumentNullException("connectionString");
59:
60: return new Database(connectionString, getMappingSource());
61: }
62:
63: #endregion
64:
65: #region IDatabase Members
66:
67: /// <summary>
68: /// 取得某一个实体的数据访问
69: /// </summary>
70: /// <typeparam name="T">实体类型</typeparam>
71: /// <returns>该实体的数据访问</returns>
72: public IEntityDataAccess<T> GetDataAccess<T>() where T : class
73: {
74: return new EntityDataAccessAdapter<T>(this);
75: }
76:
77: /// <summary>
78: /// 提交数据库变更
79: /// </summary>
80: public void Submit()
81: {
82: base.SubmitChanges();
83: }
84:
85: #endregion
86:
87: #region Blogs
88:
89: public IEntityDataAccess<Blog> Blogs
90: {
91: get { return new EntityDataAccessAdapter<Blog>(this); }
92: }
93:
94: public IEntityDataAccess<BlogClass> BlogClasses
95: {
96: get { return new EntityDataAccessAdapter<BlogClass>(this); }
97: }
98:
99: #endregion
100: }
101: }
这个代码比较长,我们可以把它分为三部分来看,第一部分是实现了一个Singelton模式,用于构造Linq的外部映射文件,涉及的代码如下:
1: //定义内嵌XML映射资源文件的位置
2: private const string MAPPING_SOURCE_RESOURCE_NAME = "DongBlog.DataAccess.MappingSource.Xml";
3: //定义XML映射源
4: private static MappingSource _MappingSource;
5: //定义锁
6: private static object _Locker = new object();
7:
8: //私有的构造函数
9: private Database(string connectionString, MappingSource mappingSource)
10: : base(connectionString, mappingSource) { }
11:
12: //构造Linq映射源
13: private static MappingSource getMappingSource()
14: {
15: if (_MappingSource == null)
16: {
17: lock (_Locker)
18: {
19: if (_MappingSource == null)
20: {
21: Stream mapingSourceStream = Assembly.GetAssembly(typeof(Database))
.GetManifestResourceStream(MAPPING_SOURCE_RESOURCE_NAME);
22: if (mapingSourceStream == null)
23: throw new System.IO
.InvalidDataException(
"映射文件不存在!您确定已经将其编译属性设置为\"嵌入资源(Embedded Resource)\"?");
24: _MappingSource = XmlMappingSource.FromStream(mapingSourceStream);
25: }
26: }
27: }
28: return _MappingSource;
29: }
30:
31: /// <summary>
32: /// 构造新的数据库
33: /// </summary>
34: /// <param name="connectionString">数据库连接字符串</param>
35: /// <returns>数据库实例</returns>
36: public static Database New(string connectionString)
37: {
38: if (string.IsNullOrEmpty(connectionString))
39: throw new ArgumentNullException("connectionString");
40:
41: return new Database(connectionString, getMappingSource());
42: }
以上代码通过Database的私有构造函数和共有静态方法保证数据库使用的外部Linq映射源是静态唯一的。其中Database构造函数继承于DataContext的构造函数,该构造函数在Linq中的定义如下:
1: //
2: // Summary:
3: // Initializes a new instance of the System.Data.Linq.DataContext class by referencing
4: // a file source and a mapping source.
5: //
6: // Parameters:
7: // fileOrServerOrConnection:
8: // This argument can be any one of the following:The name of a file where a
9: // SQL Server Express database resides.The name of a server where a database
10: // is present. In this case the provider uses the default database for a user.A
11: // complete connection string. LINQ to SQL just passes the string to the provider
12: // without modification.
13: //
14: // mapping:
15: // The System.Data.Linq.Mapping.MappingSource.
16: public DataContext(string fileOrServerOrConnection, MappingSource mapping);
Linq的映射文件使用嵌入式资源的方式储存(将其文件属性改为Embedded Resource,如下图)。而getMappingSource方法通过加锁的方式使_MappingSource只初始化一次,其中两次使用了if(_MappingSource == null),确保取得锁的过程中的不会出现并发冲突(尽管这种概率极小)。
Database的第二部分是实现IDatabase接口,涉及代码如下:
1: #region IDatabase Members
2:
3: /// <summary>
4: /// 取得某一个实体的数据访问
5: /// </summary>
6: /// <typeparam name="T">实体类型</typeparam>
7: /// <returns>该实体的数据访问</returns>
8: public IEntityDataAccess<T> GetDataAccess<T>() where T : class
9: {
10: return new EntityDataAccessAdapter<T>(this);
11: }
12:
13: /// <summary>
14: /// 提交数据库变更
15: /// </summary>
16: public void Submit()
17: {
18: base.SubmitChanges();
19: }
20:
21: #endregion
22:
23: #region Blogs
24:
25: public IEntityDataAccess<Blog> Blogs
26: {
27: get { return new EntityDataAccessAdapter<Blog>(this); }
28: }
29:
30: public IEntityDataAccess<BlogClass> BlogClasses
31: {
32: get { return new EntityDataAccessAdapter<BlogClass>(this); }
33: }
34:
35: #endregion
可以从代码中看到,对于GetDataAccess<T>的调用,全部委托给了EntityDataAccessAdapter类。至于EntityDataAccessAdapter类,全部是调用System.Data.Linq.Table的方法,看一下代码很容易就可以明白,就不贴出来了。
最后一个问题:Linq的外部映射文件MappingSource.Xml是怎么来的?答案是代码生成。在DongBlog.Test\Util.cs中新加入以下代码用于生成MappingSource.Xml。
1: /// <summary>
2: /// 构造Linq的XML映射文件
3: /// </summary>
4: [TestMethod, Description("构造Linq的XML映射文件")]
5: public void Util_CreateXmlMappingFile()
6: {
7: LinqMappingXmlGenerater linqMappingXmlBuilder =
new LinqMappingXmlGenerater("", "", Gobal.DatabaseConnectionString);
8: var typeArray = new System.Type[]{
9: typeof(Blog),
10: typeof(BlogClass),
11: };
12: linqMappingXmlBuilder.BuildMappingXml(typeArray, Gobal.LingMappingFile);
13: }
这段调用了YD.Data.LinqGenerater.LinqMappingXmlGenerater类用于生成MappingSource.Xml,其实质是使用SqlMetal这个Linq2Sql附带的工具生成外部映射文件,具体情况可以参看YD\Data\LinqGenerater\LinqMappingXmlGenerater.cs。
至此,我们就有了一个完成的数据访问层。下一篇我们就进入UI层的开发,看看我们如何把我们前面写的这些代码整合在一起。