一、什么是企业库?
企业库包含一系列应用程序模块和核心架构。这些高复用的组件旨在帮助开发者解决一些共同的企业开发问题。 企业库同时提供高可配置的特性,使我们管理重复的工作更加容易,例如我们知道的在应用的很多地方发生的横切关注点。包括像日志记录、缓存、异常管理等。另外,它提供的依赖注入容器能够简化和分解你的设计,使他们更加可实验和容易理解,帮助你创建更加高性能的设计和各种应用。 二、使用企业库的3个简单步骤: 1.引用和配置你需要的程序集。 2.通过企业库配置应用。 3.在你的代码中创建和使用企业库对象。 三、企业库的好处: 应用模块帮助解决开发者从一个项目到另一个项目面对的共同问题。他们的设计封装了微软推荐的做法,这些都是基于微软框架的应用开发。例如,数据访问应用模块提供了对ADO.NET访问最频繁使用的特征。在某些情况下,应用模块还添加了一些基础类库没有直接提供的相关功能。 四、企业库的目标: 1.一致。所有的企业库模块都坚持一致的设计模式和实现方式。 2.可扩展性。所有的应用模块包括定义扩展点,允许开发人员通过添加自己的代码定制应用模块的行为。 3.易用性。企业库提供了许多实用性的东西,包括一个图形化配置工具,简单的安装过程,完成的文档和示例。 4.集成。企业库应用模块被设计得能够一起很好的工作,并且也被这样测试过。但是你不必一起使用他们。我们可以单独使用这些应用模块,同时这些应用模块之间也有些依赖,比如在企业库核心和Unity中的一些通用组件。
上面是企业库的基本概念,理解了企业库的相关知识后,我们可以开始来安装企业库了
1、下载地址:点我进入下载页面(不是直接下载),安装后就可以使用了。
这次5.0相比4.1的最大改动就是整个配置工具采用了WPF重新构建和实例化和管理对象生命周期的全依赖注入的实现,同时支持VS2008SP1和VS2010,话说虽然这次的配置工具变化挺大的,但是一旦熟悉了就觉得比4.1的好,因为可以清楚的看见每个模块的之间的依赖关系。
一、Unity和对象生成器的整合 在这个版本中,用于创建对象的基本技术是一个单一的依赖注入容器,默认的是Unity。你可以使用容器生成企业库对象的实例并注入到其他的对象。 企业库提供一个标准的接口从Unity容器中来获得定义在企业库配置中的对象的实例,如SqlDatabase或LogWriter.另外,注入友好的实例门面处理静态门面之外是有效的,因为静态门面不能用注入,但是为了向后兼容以前的版本而存在。在本版本中的示例中都是用依赖注入,但是以前版本中是用的静态工厂类和静态门面在这个版本中还是支持的。对象生成器,一个低版本的依赖注入机制在这个版本中被归入Unity中,在项目中不再需要单独引用对象生成器集。 支持Unity容器,必须引用一个被包含在企业库中的Microsoft.Practices.ServiceLocation程序集。如果要使用一个第3方的依赖注入容器,必须直接实现IServiceLocator接口或者通过适配器实现。
二、影响所有模块的变化: 1.在企业库中主要修正是使用依赖注入机制。所用的应用模块以及核心系统都是用依赖注入机制,并使用Unity作为默认的依赖注入容器,来创建和管理企业库对象。 2.自主容器的实现通过实现Common Service Locator项目提供的 IServiceLocator 接口来完成。 3.由于错误配置引发的错误将提供更多有用的错误信息。 4.配置系统公开了一个 fluent接口,用来为单个对象或整个应用创建和填充配置源。fluent API使得为各种情景创建配置源更加容易。 5.ConfigurationView类被删除. 6.一些配置元素有默认值,可能不同于以前版本的隐式默认值. 7.企业库现在允许你通过另一个配置文件合成一个混合配置文件. 8.可以可通过不同的配置文件读取不同的配置信息. 9.企业库不支持XAML浏览器应用程序(XBAP). 10.WmiEnabled标志为了像前兼容仍然存在企业库中,但是在5.0中是被忽略的,而且将来会被删除. 11.改进式的安装允许你只安装部分应用模块及配置工具. 12.在以前版本中要做统一集成,必须添加核心企业库的扩展和每个模块的扩展。现在如果你只需要直接访问容器,那么只有核心扩展是必须的。单独模块将自动支持。 旧的功能为了保持像前兼容仍然保留,但已经不起作用。 13.FileConfigurationSource.Save 的签名已经改变,参数由3个变为2个。 14.快速入门不再包含在主安装程序中。
三、Breaking变化: 1.企业库现在抛出了一个配置错误ActivationException,之前是System.Configuration.ConfigurationErrorsException。这包括试着解决没有配置信息错误的一个实例提供者。 2.以前版本在获取应用模块错误时抛出BuildFailedException错误,现在对于所有的应用模块都抛出ActivationException 3 .之前的版本,在讲一个空源传到容器来调用容器的时候,会抛出ArgumentNullException,现在抛出NullReferenceException 4.ObjectBuilder2不再是一个单独的程序集,而是集成到了Unity集合中,在项目中也不需要引用ObjectBuilder2.dll。
5.WMI支持已经从企业库中删除,除了在logging模块中的WMI跟踪监听器。 6.如果你没有关闭DbDataReader,可能会导致随机的、很难在您的代码中找到的错误,尤其是当你正在一个由TransactionScope上下文创建的隐式事务下操作时。 你必须始终确保您的应用程序及时关闭DbDataReader,无论是明确的DbDataReader.Close方法关闭或是逼迫DbDataReader释放。 7.如果你使用 validator 特性必须引用 System.ComponentModel.DataAnnotations程序集。 8.为FileConfigurationSource.Save方法签名已更改。该方法有两个参数,而不是3个参数 9.Microsoft.Practices.EnterpriseLibrary.Configuration.Design.dll集合的功能和其他设计时集合被一个新的集合Microsoft.Practices.EnterpriseLibrary.Configuration.DesignTime.dll代替。 10,性能计数器异常从PolicyInjection.CallHandlers移到 PolicyInjection 程序集。 11.包含在Policy Injection Application Block中的CachingCallHandler有未处理的安全漏洞,已经从Policy Injection Application Block中移除。
四、配置工具的改变: 1.新的企业拥有一个新的GUI库和一个元数据驱动的可扩展性模。 2.支持向导 3.新的类型选择。 4.不支持对依赖策略的Environmental Overrides 。日志模块处理Categories。 五、缓存模块变化: 1.缓存清除已被完全重写的性能优化 六、数据库访问模块: 1.ExecuteReader, ExecuteXmlReader, ExecuteScalar, and ExecuteNonQuery方法具有异步版本。 2.包含了很多新的方法和类允许你提取数据作为对象序列。例如在合适的时候使用客户端查询技术,如LINQ. 3.存在的方法ExecuteReader和新的方法BeginExecuteReader不接收CommandBehavior 参数。默认的当调用这些方法的时候这些方法 会自动设置CommandBehavior 属性到reder中用来关闭连接直到指定一个事务。
七、异常处理模块: 1.日志异常处理重新使用日志模块的Log Writer and Trace Listeners 。这在之前版本中不是默认设置。 2.增加一个功能,通过ExceptionManager.Process 方法接收一个默认值并返回一个值。
[EntLib]微软企业库5.0 学习之路——第二步、使用VS2010+Data Access模块建立多数据库项目
现在我就开始进入学习之路的第二步——Data Access模块,这个模块是企业库中被使用频率最高的模块,它很好的封装了数据库操作应用,为我们进行多数据库系统开发提供了便利,只需更改配置文件就可以很快的切换数据库访问(可惜还是要重写SQL语句,没法和ORM比)。
下面是我在配置企业库的时候碰到问题,如果没有碰到可以略去不看(可能有点小白)
注意:此处切换数据库配置必须是计算机中已经安装好相应的数据库访问模块,如需要进行从MS SQL向SQLite数据库的变更时,计算机中必须安装好SQLite数据库访问模块(在这里我就碰到了这个问题,原来我机器上在VS2008开发时已经安装过SQLite数据库访问模块,但是新装了VS2010,在VS2010引用对话框中也能访问到在VS2008安装的SQLite(但是在企业库5.0配置器中无法查看到SQLite),但是发现更改企业库的配置文件后无法访问SQLite数据库,尝试了很多方法都没用,结果死马当活马医又重新装了一遍SQLite数据库访问模块再重新打开企业库配置器就可以看到SQLite数据库了(所以请确保在企业库编辑器中可以查看到要切换的数据库,否则可能导致无法访问数据库)。看下图:
回归正题,这次的学习由于VS2010发布了,而且企业库5.0也都支持.NET4.0,所以决定企业库的学习之路采用VS2010进行学习(顺便熟悉下.NET4的特性,毕竟公司的项目不可能立马转移到.NET4.0的,现在就当练手吧)
好了,现在就开始进行第2步的学习了,首先看下项目的结构:
项目采用仿MS PetShop架构,如不了解此架构可以到此查看了解:PetShop的系统架构设计
其中DAL和DALSQLite层对应MS SQL和SQLite数据库,Helper为整个项目的帮助器
现在来具体了解下DAL层
在DAL层中引用了Helper,IDAL,EnterpriseLibrary.Common和EnterpriseLibrary.Data这4个项目,其中Helper项目中有个DBHelper.cs,用于获取当前的数据对象,其代码如下(采用了C#4.0的语法特性,默认参数,数据库对象名默认为空,这样则会调用企业库默认的数据库对象,同时也可以在调用的时候赋值,这样则根据传递过来的数据库对象名来创建数据库,通过这个参数我们将原来需要重载的2个方法才能实现合并成了一个方法):
01 | using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; | |
02 | using Microsoft.Practices.EnterpriseLibrary.Data; |
03 | ||
04 | namespace EntLibStudy.Helper |
05 | { | |
06 | public static class DBHelper |
07 | { | |
08 | /// <summary> |
09 | /// 获取数据库对象 | |
10 | /// </summary> |
11 | /// <param name="name">数据库实例名(默认name为空,调用默认数据库实例)</param> | |
12 | /// <returns>数据库对象</returns> |
13 | public static Database CreateDataBase(string name = "") | |
14 | { |
15 | //return DatabaseFactory.CreateDatabase(name); | |
16 | return EnterpriseLibraryContainer.Current.GetInstance<Database>(name); |
17 | } |
18 | } |
19 | } |
在DAL层中则引用Helper来获取数据库对象,进行数据库操作,我们现在来看下具体的数据库访问类编写代码,学员操作类:
001 | using System; | |
002 | using System.Collections.Generic; |
003 | using System.Data; | |
004 | using System.Data.Common; |
005 | using System.Linq; |
006 | using System.Text; |
007 | ||
008 | using Microsoft.Practices.EnterpriseLibrary.Data; |
009 | ||
010 | using EntLibStudy.Model; |
011 | using EntLibStudy.Helper; | |
012 |
013 | namespace EntLibStudy.DAL | |
014 | { |
015 | public class StudentService : EntLibStudy.IDAL.IStudentService | |
016 | { |
017 | /// <summary> | |
018 | /// 新增学生 |
019 | /// </summary> | |
020 | /// <param name="student">学生对象</param> |
021 | /// <returns></returns> | |
022 | public int Add(Student student) |
023 | { | |
024 | Database db = DBHelper.CreateDataBase(); |
025 | StringBuilder sb = new StringBuilder(); | ||
026 | sb.Append("insert into Student values(@ClassId,@SID,@Password,@Name,@Sex,@Birthday,@IsAdmin);SELECT @@IDENTITY;"); |
027 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); | |
028 | db.AddInParameter(cmd, "@ClassId", DbType.String, student.ClassId); |
029 | db.AddInParameter(cmd, "@SID", DbType.String, student.Sid); | |
030 | db.AddInParameter(cmd, "@Password", DbType.String, student.Password); |
031 | db.AddInParameter(cmd, "@Name", DbType.String, student.Name); | |
032 | db.AddInParameter(cmd, "@Sex", DbType.Int32, student.Sex); |
033 | db.AddInParameter(cmd, "@Birthday", DbType.DateTime, student.Birthday); |
034 | db.AddInParameter(cmd, "@IsAdmin", DbType.Int32, student.IsAdmin); |
035 | int id = Convert.ToInt32(db.ExecuteScalar(cmd)); | |
036 | return id; |
037 | } | |
038 |
039 | /// <summary> | |
040 | /// 更新 |
041 | /// </summary> | |
042 | /// <param name="classInfo">学生对象</param> |
043 | /// <returns>是否成功</returns> | |
044 | public bool Update(Student student) |
045 | { | |
046 | Database db = DBHelper.CreateDataBase(); |
047 | StringBuilder sb = new StringBuilder(); | |
048 | sb.Append("update Student set ClassId=@ClassId,"); |
049 | sb.Append("SID=@SID,"); | |
050 | sb.Append("Password=@Password,"); |
051 | sb.Append("Name=@Name,"); | |
052 | sb.Append("Sex=@Sex,"); |
053 | sb.Append("Birthday=@Birthday,"); | |
054 | sb.Append("IsAdmin=@IsAdmin "); |
055 | sb.Append(" where ID=@ID"); | |
056 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); |
057 | db.AddInParameter(cmd, "@ClassId", DbType.String, student.ClassId); | |
058 | db.AddInParameter(cmd, "@SID", DbType.String, student.Sid); |
059 | db.AddInParameter(cmd, "@Password", DbType.String, student.Password); | |
060 | db.AddInParameter(cmd, "@Name", DbType.String, student.Name); |
061 | db.AddInParameter(cmd, "@Sex", DbType.Int32, student.Sex); | |
062 | db.AddInParameter(cmd, "@Birthday", DbType.DateTime, student.Birthday); |
063 | db.AddInParameter(cmd, "@IsAdmin", DbType.Int32, student.IsAdmin); | |
064 | db.AddInParameter(cmd, "@ID", DbType.Int32, student.Id); |
065 | return db.ExecuteNonQuery(cmd) > 0 ? true : false; | |
066 | } |
067 | ||
068 | /// <summary> |
069 | /// 删除 | |
070 | /// </summary> |
071 | /// <param name="id">学生ID</param> | |
072 | /// <returns>是否成功</returns> |
073 | public bool Delete(int id) | |
074 | { |
075 | Database db = DBHelper.CreateDataBase(); | |
076 | StringBuilder sb = new StringBuilder(); |
077 | sb.Append("delete from Student "); | |
078 | sb.Append(" where ID=@ID"); |
079 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); | |
080 | db.AddInParameter(cmd, "@ID", DbType.Int32, id); |
081 | ||
082 | return db.ExecuteNonQuery(cmd) > 0 ? true : false; |
083 | } | |
084 |
085 | /// <summary> | |
086 | /// 根据学生ID查询学生对象 |
087 | /// </summary> | |
088 | /// <param name="id">学生ID</param> |
089 | /// <returns></returns> | |
090 | public Student SelectById(int id) |
091 | { | |
092 | Student student = null; |
093 | Database db = DBHelper.CreateDataBase(); | |
094 | StringBuilder sb = new StringBuilder(); |
095 | sb.Append("select * from Student "); | |
096 | sb.Append(" where ID=@ID"); |
097 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); | |
098 | db.AddInParameter(cmd, "@ID", DbType.Int32, id); |
099 | ||
100 | using (IDataReader reader = db.ExecuteReader(cmd)) |
101 | { | |
102 | if (reader.Read()) |
103 | { | |
104 | student = new Student() |
105 | { | |
106 | Id = reader.GetInt32(0), |
107 | ClassId = reader.GetInt32(1), | |
108 | Sid = reader.GetString(2), |
109 | Password = reader.GetString(3), | |
110 | Name = reader.GetString(4), |
111 | Sex = reader.GetInt32(5), | |
112 | Birthday = reader.GetDateTime(6), |
113 | IsAdmin = reader.GetInt32(7) | |
114 | }; |
115 | } |
116 | } |
117 | ||
118 | return student; |
119 | } | |
120 |
121 | /// <summary> | |
122 | /// 查询所有学生信息 |
123 | /// </summary> | |
124 | /// <returns></returns> |
125 | public IList<Student> SelectAll() | |
126 | { |
127 | List<Student> list = new List<Student>(); | |
128 | Database db = DBHelper.CreateDataBase(); |
129 | StringBuilder sb = new StringBuilder(); | |
130 | sb.Append("select * from Student "); |
131 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); | |
132 |
133 | using (IDataReader reader = db.ExecuteReader(cmd)) | |
134 | { |
135 | while (reader.Read()) | |
136 | { |
137 | list.Add(new Student() | |
138 | { |
139 | Id = reader.GetInt32(0), | |
140 | ClassId = reader.GetInt32(1), |
141 | Sid = reader.GetString(2), | |
142 | Password = reader.GetString(3), |
143 | Name = reader.GetString(4), | |
144 | Sex = reader.GetInt32(5), |
145 | Birthday = reader.GetDateTime(6), | |
146 | IsAdmin = reader.GetInt32(7) |
147 | }); | |
148 | } |
149 | } | |
150 | return list; |
151 | } | |
152 |
153 | /// <summary> | |
154 | /// 查询所有学生信息 |
155 | /// </summary> | |
156 | /// <returns></returns> |
157 | public IList<Student> SelectAllMapper() | |
158 | { |
159 | var list = new List<Student>(); | |
160 | Database db = DBHelper.CreateDataBase(); |
161 | DataAccessor<Student> studentAccessor; | |
162 | //studentAccessor = db.CreateSqlStringAccessor("select * from Student", |
163 | // MapBuilder<Student>.MapAllProperties(). | |
164 | // Build() |
165 | // ); | |
166 | studentAccessor = db.CreateSqlStringAccessor("select * from Student", |
167 | MapBuilder<Student>.MapAllProperties(). | |
168 | Map(p => p.Id).ToColumn("ID"). |
169 | Map(p => p.Sid).ToColumn("SID"). | |
170 | Map(p => p.Password).WithFunc(f => "******").//将密码转换为"*",无法直接查看 |
171 | Map(p => p.Name).WithFunc(ToUpperName).//将学员名称转换为大写 | |
172 | Map(p => p.Sex).ToColumn("Sex"). |
173 | Map(p => p.Birthday).ToColumn("Birthday"). | |
174 | Build() |
175 | ); | |
176 | list = studentAccessor.Execute().ToList(); |
177 | return list; | |
178 | } |
179 | ||
180 | /// <summary> |
181 | /// 将学员名称转换为大写 | |
182 | /// </summary> |
183 | /// <param name="dataRecord"></param> | |
184 | /// <returns></returns> |
185 | private string ToUpperName(IDataRecord dataRecord) | |
186 | { |
187 | var name = (string)dataRecord["Name"]; | |
188 | return name.ToUpper(); |
189 | } | |
190 |
191 | public Student SelectBySid(string sid) | |
192 | { |
193 | Student student = null; | |
194 | Database db = DBHelper.CreateDataBase(); |
195 | StringBuilder sb = new StringBuilder(); | |
196 | sb.Append("select * from Student "); |
197 | sb.Append(" where SID=@SID"); | |
198 | DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); |
199 | db.AddInParameter(cmd, "@SID", DbType.String, sid); | |
200 |
201 | using (IDataReader reader = db.ExecuteReader(cmd)) | |
202 | { |
203 | if (reader.Read()) | |
204 | { |
205 | student = new Student() | |
206 | { |
207 | Id = reader.GetInt32(0), | |
208 | ClassId = reader.GetInt32(1), |
209 | Sid = reader.GetString(2), | |
210 | Password = reader.GetString(3), |
211 | Name = reader.GetString(4), | |
212 | Sex = reader.GetInt32(5), |
213 | Birthday = reader.GetDateTime(6), | |
214 | IsAdmin = reader.GetInt32(7) |
215 | }; | |
216 | } |
217 | } | |
218 |
219 | return student; | |
220 | } |
221 | ||
222 | } |
223 | } |
其中的代码都是采用了比较常见的老套路:
1、获取数据库对象
2、构建Command对象并进行执行语句及参数赋值
3、通过数据库对象调用相应方法执行Command
企业库在Data Access上帮我们做了比较好的封装,相当于为我们提供了如SQLHelper,OracleHelper类,只不过这个帮助类转换了一个个数据库的对象,通过数据库对象来对数据库数据进行操作
(个人认为通过这种方式进行操作更加直观,而且企业库的对SQL语句的参数操作方法也很直观:AddInParameter,AddOutParameter,GetParameterValue很好的区分了参数的操作,比原来的SQLCommand好多了)
如果仔细看了上面操作代码的朋友肯定发现了类中有个叫SelectAllMapper的方法,这个方法采用的是企业库5.0中新提供的Accessor进行RowMapper来直接为实体赋值,相比原来的使用reader方式取值赋值更加优雅,只要SQL查询出来的对象字段和实体对象属性一样就可以使用MapAllProperties()方法直接赋值,如果不同的话可以使用map方法来对个别属性单独映射,而且在映射的时候还可以使用WithFunc来进行进一步操作,在代码中我将密码进行了替换,以“*”的形式展示,同时把学员的名称以大写的形式展示。
(注:更多的企业库Data Access模块方法使用可以点击这里下载微软给出的学习例子和http://www.entlib.com/发布的学习手册)
在完成底层的操作,现在我们就开始对企业库的数据库访问进行配置:
在Web层的Web.config上右键打开企业库配置器:Blocks-Add Data Settings-Add DataBase Connstring,新建2个数据库对象,一个是MS SqlServer数据库,一个是SQLite数据库.
新建一个数据库设置
新建二个数据库连接
一个为EntLibStudy,另一个为EntLibSQLite
我们来看下具体的配置文件代码:
01 | <configuration> | |
02 | <configSections> |
03 | <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> | ||
04 | </configSections> |
05 | ||
06 | <dataConfiguration defaultDatabase="EntLibStudy" /> |
07 | <connectionStrings> | ||
08 | <add name="EntLibStudy" connectionString="server=VOLK\SQL2005;database=EntLibStudy;Integrated Security=True;" |
09 | providerName="System.Data.SqlClient" /> | |
10 | <add name="EntLibStudySQLite" connectionString="data source=|DataDirectory|EntLibStudySQLite.db3" |
11 | providerName="System.Data.SQLite" /> | |
12 | </connectionStrings> |
13 | ||
14 | <appSettings> |
15 | <add key="DAL" value="EntLibStudy.DAL" /> | |
16 | </appSettings> |
17 | ||
18 | <system.web> |
19 | <compilation debug="true" targetFramework="4.0" > | |
20 | <expressionBuilders> |
21 | <add expressionPrefix="RouteUrl" type="System.Web.Compilation.RouteUrlExpressionBuilder"/> |
22 | <add expressionPrefix="RouteValue" type="System.Web.Compilation.RouteValueExpressionBuilder"/> |
23 | </expressionBuilders> | |
24 | </compilation> |
25 | <authentication mode="Forms"> | |
26 | </authentication> |
27 | ||
28 | </system.web> |
29 | ||
30 | </configuration> |
至此我们就完成了Data Access模块的代码编写和基本设置(具体代码请到文章底部下载源代码,类似代码则不再描述)。
这时如果项目需求发生了变更,由于成本太高不能使用MS SQL SERVER,而要改用SQLite数据库时则只需更改配置文件,将dataConfiguration配置节中defaultDatabase更改为EntLibStudySQLite,将appSettings配置节中DAL的值改为EntLibStudy.DALSQLite即可立刻切换到SQLite数据库。
下面我来看下运行出来的效果:
红色框中地址采用了ASP.NET4中的路由功能,实现了地址重写
红框中如上面的分析,在DAL层进行属性映射的时候已经将密码以*代替,学员姓名以大写形式展现(此页面仅用来展示属性映射,无其他用处,页面地址为:~/Student/StudentList.aspx)
至此,学习之路的第二步——Data Access模块的学习就到此为止了,其他一些东西请下载源代码查看。
注意:
1、MSSQL数据库在DataBase目录下(需要自行附加数据库),SQLite数据库在Web目录的App_Data下,由于考虑到项目的大小,所以每个项目的BIN目录都已经删除,如出现无法生成项目请自行添加相关企业库的DLL。
2、由于微软企业库5.0 学习之路这个系列我是准备以一个小型项目的形式介绍企业库的各模块,所以源代码会根据系列文章的更新而更新,所以源代码不能保证与文章中所贴代码相同。
3、项目开发环境为:VS2010+SQL2005。