ADO.NET与ORM的比较(1):ADO.NET实现CRUD

本文介绍使用ADO.NET实现数据库基本操作的方法,包括创建、读取、更新和删除等核心功能,并对比了不同数据库访问技术。
说明:个人感觉在Java领域大型开发都离不了ORM的身影,所谓的SSH就是Spring+Struts+Hibernate,除了在学习基础知识的时候被告知可以使用JDBC操作数据库之外,大量的书籍中都是讲述使用Hibernate这个ORM工具来操作数据。在.NET中操作数据库的方式有多种,除了最直接的方式就是使用ADO.NET之外,还可以使用NHibernate这个Hibernate在.NET中的实现ORM,如果你对第三方的ORM持怀疑态度,你还可以使用来自微软的实现、根正苗红的Linq或者EntityFramework。
大部分从早期就开始使用.NET开发的程序员可能对ADO.NET有种迷恋,使用ADO.NET可以充分将我们早期的SQL知识发挥得淋漓尽致,并且出于对性能的考虑,有些人对.NET中的ORM还保持一种观望态度,包括我自己也是这种态度。不过即使在实际开发中不用,并不代表我们不能去了解和比较这些技术,任何事物的出现和消亡总有其原因的,我们可以了解它们的优点和长处。所以本人抽出了几个周末的时间分别用ADO.NET、NHibernate、Linq和EntityFramework来实现对数据库单表数据的创建、读取、更新和删除操作,也就是所谓的CRUD(C:Create/R:Read/U:Update/D:Delete)。
通过实现相同功能的比较,大家自己判断那种方式更适合自己。需要说明的是,如果在VS2008中使用EntityFramework就需要安装VS2008SP1。
在开始演示之前先准备好数据,在本系列中所使用的数据库是SQL Server2005,创建和初始化数据库数据的代码如下:
/****** 对象:    Table [dbo].[UserInfo]        脚本日期: 03/08/2010 12:20:11 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[UserInfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[UserInfo](
  [UserID] [int] IDENTITY(1,1) NOT NULL,
  [UserName] [varchar](20) COLLATE Chinese_PRC_CI_AS NOT NULL,
  [RealName] [nvarchar](8) COLLATE Chinese_PRC_CI_AS NOT NULL,
  [Age] [tinyint] NOT NULL,
  [Sex] [bit] NOT NULL,
  [Mobile] [char](11) COLLATE Chinese_PRC_CI_AS NULL,
  [Phone] [char](11) COLLATE Chinese_PRC_CI_AS NULL,
  [Email] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
CONSTRAINT [PK_UserInfo] PRIMARY KEY CLUSTERED    
(
  [UserID] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
END
GO
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[UserInfo]') AND name = N'IX_UserName')
CREATE UNIQUE NONCLUSTERED INDEX [IX_UserName] ON [dbo].[UserInfo]    
(
  [UserName] ASC
)WITH (IGNORE_DUP_KEY = ON)
GO
 
下面开始演示如何使用ADO.NET实现CRUD功能。
一、配置
创建一个控制台或者类库项目,并且添加一个app.config配置文件,在此文件中添加数据库配置信息,如下:
<connectionStrings>
  <add name="Conn" connectionString="Data Source=zhou;Initial Catalog=AspNetStudy;User ID=sa;Password=jerry" />
</connectionStrings>
 
二、创建实体类
InBlock.gifusing System;
InBlock.gifusing System.Collections.Generic;
InBlock.gifusing System.Text;
InBlock.gif
InBlock.gifnamespace ADODoNETDemo
InBlock.gif{
InBlock.gif        public class UserInfo
InBlock.gif        {
InBlock.gif                /// <summary>
InBlock.gif                /// 用户编号
InBlock.gif                /// </summary>
InBlock.gif                public int UserId { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 用户名
InBlock.gif                /// </summary>
InBlock.gif                public string UserName { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 真实姓名
InBlock.gif                /// </summary>
InBlock.gif                public string RealName { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 年龄
InBlock.gif                /// </summary>
InBlock.gif                public byte Age { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 性别
InBlock.gif                /// </summary>
InBlock.gif                public bool Sex { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 电子邮件
InBlock.gif                /// </summary>
InBlock.gif                public string Email { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 手机号
InBlock.gif                /// </summary>
InBlock.gif                public string Mobile { get; set; }
InBlock.gif                /// <summary>
InBlock.gif                /// 电话
InBlock.gif                /// </summary>
InBlock.gif                public string Phone { get; set; }
InBlock.gif        }
InBlock.gif}
 
三、创建数据库访问通用类
说明:下面的这个数据库通用类适用于访问数据库中任意表,不管是基于文本方式SQL、参数化SQL语句或者存储过程都可以。
很郁闷受篇幅限制,这里将代码放在附件里。
四、创建CRUD类
对数据库实现增删改查功能的类的代码如下:
InBlock.gifusing System;
InBlock.gifusing System.Collections.Generic;
InBlock.gifusing System.Linq;
InBlock.gifusing System.Text;
InBlock.gifusing System.Data.SqlClient;
InBlock.gifusing System.Data;
InBlock.gif
InBlock.gifnamespace ADODoNETDemo
InBlock.gif{
InBlock.gif        /// <summary>
InBlock.gif        /// 用ADO.NET实现CRUD功能
InBlock.gif        /// </summary>
InBlock.gif        public class ADODotNetCRUD
InBlock.gif        {
InBlock.gif                /// <summary>
InBlock.gif                /// 统计用户总数
InBlock.gif                /// </summary>
InBlock.gif                /// <returns></returns>
InBlock.gif                public int Count()
InBlock.gif                {
InBlock.gif                        string sql = "select count(1) from UserInfo";
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        return int.Parse(db.ExecuteScalar(sql).ToString());
InBlock.gif                }
InBlock.gif                /// <summary>
InBlock.gif                /// 创建用户
InBlock.gif                /// </summary>
InBlock.gif                /// <param name="info">用户实体</param>
InBlock.gif                /// <returns></returns>
InBlock.gif                public bool Create(UserInfo info)
InBlock.gif                {
InBlock.gif                        string sql = "insert UserInfo(UserName,RealName,Age,Sex,Mobile,Email,Phone)values(@UserName,@RealName,@Age,@Sex,@Mobile,@Email,@Phone)";
InBlock.gif                        SqlParameter[] paramters = new SqlParameter[]{
InBlock.gif                                new SqlParameter("@UserName",info.UserName),
InBlock.gif                                new SqlParameter("@RealName",info.RealName),
InBlock.gif                                new SqlParameter("@Age",info.Age),
InBlock.gif                                new SqlParameter("@Sex",info.Sex),
InBlock.gif                                new SqlParameter("@Mobile",info.Mobile),
InBlock.gif                                new SqlParameter("@Email",info.Email),
InBlock.gif                                new SqlParameter("@Phone",info.Phone),
InBlock.gif                        };
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        return db.ExecuteNonQuery(sql, CommandType.Text, paramters) > 0;
InBlock.gif                }
InBlock.gif                /// <summary>
InBlock.gif                /// 读取用户信息
InBlock.gif                /// </summary>
InBlock.gif                /// <param name="userId">用户编号</param>
InBlock.gif                /// <returns></returns>
InBlock.gif                public UserInfo Read(int userId)
InBlock.gif                {
InBlock.gif                        string sql = "select * from UserInfo Where UserId="+userId;
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        DataTable data = db.ExecuteDataTable(sql);
InBlock.gif                        if (data.Rows.Count > 0)
InBlock.gif                        {
InBlock.gif                                DataRow row = data.Rows[0];
InBlock.gif                                UserInfo info = new UserInfo()
InBlock.gif                                {
InBlock.gif                                        UserId=int.Parse(row["UserId"].ToString()),
InBlock.gif                                        UserName=row["UserName"].ToString(),
InBlock.gif                                        Age=byte.Parse(row["Age"].ToString()),
InBlock.gif                                        Email=row["Email"].ToString(),
InBlock.gif                                        Mobile=row["Mobile"].ToString(),
InBlock.gif                                        Phone=row["Phone"].ToString(),
InBlock.gif                                        RealName=row["RealName"].ToString(),
InBlock.gif                                        Sex=bool.Parse(row["Sex"].ToString())
InBlock.gif                                };
InBlock.gif                                return info;
InBlock.gif                        }
InBlock.gif                        else
InBlock.gif                        {
InBlock.gif                                return null;
InBlock.gif                        }
InBlock.gif                }
InBlock.gif                /// <summary>
InBlock.gif                /// 更新用户信息
InBlock.gif                /// </summary>
InBlock.gif                /// <param name="info">用户实体</param>
InBlock.gif                /// <returns></returns>
InBlock.gif                public bool Update(UserInfo info)
InBlock.gif                {
InBlock.gif                        string sql = "update UserInfo set UserName=@UserName,RealName=@RealName,Age=@Age,Sex=@Sex,Mobile=@Mobile,Email=@Email,Phone=@Phone where UserID=@UserID";
InBlock.gif                        SqlParameter[] paramters = new SqlParameter[]{
InBlock.gif                                new SqlParameter("@UserName",info.UserName),
InBlock.gif                                new SqlParameter("@RealName",info.RealName),
InBlock.gif                                new SqlParameter("@Age",info.Age),
InBlock.gif                                new SqlParameter("@Sex",info.Sex),
InBlock.gif                                new SqlParameter("@Mobile",info.Mobile),
InBlock.gif                                new SqlParameter("@Email",info.Email),
InBlock.gif                                new SqlParameter("@Phone",info.Phone),
InBlock.gif                                new SqlParameter("@UserID",info.UserId),
InBlock.gif                        };
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        return db.ExecuteNonQuery(sql, CommandType.Text, paramters) > 0;
InBlock.gif                }
InBlock.gif                /// <summary>
InBlock.gif                /// 删除用户
InBlock.gif                /// </summary>
InBlock.gif                /// <param name="userId">用户编号</param>
InBlock.gif                /// <returns></returns>
InBlock.gif                public bool Delete(int userId)
InBlock.gif                {
InBlock.gif                        string sql = "delete from UserInfo where UserId=" + userId;
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        return db.ExecuteNonQuery(sql) > 0;
InBlock.gif                }
InBlock.gif                /// <summary>
InBlock.gif                /// 获取用户表中编号最大的用户
InBlock.gif                /// </summary>
InBlock.gif                /// <returns></returns>
InBlock.gif                public int GetMaxUserId()
InBlock.gif                {
InBlock.gif                        string sql = "select max(userId) from UserInfo";
InBlock.gif                        SqlDbHelper db = new SqlDbHelper();
InBlock.gif                        return int.Parse(db.ExecuteScalar(sql).ToString());
InBlock.gif                }
InBlock.gif        }
InBlock.gif}
五、NUnit单元测试代码
在进行单元测试时没有使用VS自带的单元测试工具,仍是我平常用惯了的NUnit,下面是代码:
很遗憾,受篇幅限制,这里代码也以附件形式提供。
六、点评
在使用ADO.NET操作数据库时个人觉得有如下优点,可以随心所欲地使用熟知的SQL语句甚至是存储过程,在使用统计、分组功能时感觉尤其强烈,当然前提是你的SQL基础要相当好,此外它是其它几种ORM的基础,其它几种ORM都是在底层使用ADO.NET实现与数据库的交互的,所以在效率上来说它也要高一些。它的缺点是需要自己写操作数据的语句并且需要自己编写数据库记录到实体的映射转换代码,一旦表的结构发生变动,就需要修改实体类和映射转换代码。
2010/03/08
周公
原文地址:https://github.com/andolove/Data 感谢作者!很实用 简单的Ado.net数据访问客户端。 数据库访问入口 获取IDbClient 在开始之前,先添加一个数据库访问入口。当然,也可以使用任何你喜欢的方式来创建IDbClient(的实现类)实例。 public static class Db { private static readonly Dictionary<string, IDbClient> KnownClients = new Dictionary<string, IDbClient>(); public static IDbClient Northwind { get { return GetClient("Northwind", "server=.;database=Northwind;trusted_connection=true;"); } } private static IDbClient GetClient(string name, string connectionString) { IDbClient client; if (KnownClients.TryGetValue(name, out client)) return client; lock (KnownClients) { if (KnownClients.TryGetValue(name, out client)) return client; // 创建IDbClient的实例 client = new SqlDbClient(connectionString); KnownClients.Add(name, client); } return client; } } 现在,可以使用Db.Northwind来访问SQLServer的Northwind示例数据库了。 访问其他数据库 如果要访问MySql,可以用几行代码实现一个面向MySql的IDbClient实现。下面以使用 MySql.Data.dll 作为MySql .net客户端提供器为例。 /// <summary> /// Mysql数据库访问客户端。 /// </summary> public class MysqlDbClient : AbstractDbClient { private readonly string _connectionString; /// <summary> /// 使用指定的数据库类型和连接字符串初始化<see cref="SqlDbClient"/>的新实例。 /// </summary> /// <param name="connectionString">连接字符串。</param> public MysqlDbClient(string connectionString) { ArgAssert.NotNullOrEmptyOrWhitespace(connectionString, "connectionString"); _connectionString = connectionString; } /// <summary> /// 获取当前实例所使用的数据库连接字符串。 /// </summary> public override string ConnectionString { get { return _connectionString; } } /// <summary> /// 获取当前实例所使用的<see cref="DbProviderFactory"/>实例。 /// </summary> protected override DbProviderFactory Factory { get { return MySql.Data.MySqlClient.MySqlClientFactory.Instance; } } } 现在可以创建MySql的访问客户端了: IDbClient client = new MysqlDbClient("server=.;database=MySqlDb;uid=user;pwd=password"); 类似的,可以创建访问Oracle,Sqlite或是其他数据库的客户端,只需要找到对应的DbProviderFactory实例即可。 基本数据库操作 基础CRUD // 查询 string productName = (string)Db.Northwind.Scalar( "SELECT ProductName FROM Products WHERE ProductID=115"); DataTable productTable = Db.Northwind.DataTable("SELECT * FROM Products"); // 更新 int affectedRows = Db.Northwind.Execute( "UPDATE Products SET ProductName='The Name' WHERE ProductID=115"); // 在没有命中一行的时候抛出异常 int expectedSize = 1; Db.Northwind.SizedExecute( expectedSize, "UPDATE Products SET ProductName='The Name' WHERE ProductID=115"); // 获取一行 IDataRecord record = Db.Northwind.GetRow( "SELECT ProductName, SupplierID FROM Products WHERE ProductID=115"); int supplierId = Convert.ToInt32(record["SupplierID"]); // 在不用在意资源释放的情况下使用DataReader,利用了foreach的机制,在循环结束后DataReader会自动关闭 IEnumerable<IDataRecord> rows = Db.Northwind.Rows( "SELECT ProductName, SupplierID FROM Products WHERE ProductID=115"); foreach (IDataRecord row in rows) { Console.WriteLine(row["ProductName"]); } 使用参数和调用存储过程 // 使用参数 DbParameter parameter = Db.Northwind.CreateParameter(); parameter.DbType = DbType.String; parameter.ParameterName = "CustomerID"; parameter.Value = "ALFKI"; parameter.Direction = ParameterDirection.Input; // 调用存储过程 CustOrderHist @CustomerID DataSet ds = Db.Northwind.DataSet( "CustOrderHist", new[] { parameter }, CommandType.StoredProcedure); // 使用DbClientParamEx中的扩展方法快速创建参数(需要using Data命名空间) DbParameter[] parameters = new[] { Db.Northwind.CreateParameter("id", DbType.Int32, 115, direction: ParameterDirection.Input), Db.Northwind.CreateParameter("name", DbType.String, "Ikura", 5) }; Db.Northwind.DataSet("SELECT * FROM Products WHERE ProductName=@name OR ProductID=@id", parameters); 使用Mapper IMapper<T>接口定义了从IDataRecord到T类型的映射,可以用过实现该接口,以便从数据库读取并创建特定类型实例及实例的集合。 public class Product { public int ProductID; public string ProductName; } public class ProductMapper : IMapper<Product> { public Product MapRow(IDataRecord record, int rowNum) { var product = new Product(); product.ProductID = Convert.ToInt32(record["ProductID"]); product.ProductName = record["ProductName"].ToString(); return product; } } 利用上面的ProductMapper,我们可以直接从查询中创建Product实例了。 // 获取一个实例 Product product = Db.Northwind.Get( new ProductMapper(), "SELECT * FROM Products WHERE ProductID=115"); // 获取实例的集合 IList<Product> products = Db.Northwind.List(new ProductMapper(), "SELECT * FROM Products"); Mappers类中已经定义了部分简单类型的Mapper实现,以便实现便捷的查询。 // 使用已定义好的简单Mapper IList<string> productNames = Db.Northwind.List( Mappers.String(), "SELECT ProductName FROM Products"); IList<int> productIds = Db.Northwind.List( Mappers.Int32(), "SELECT ProductID FROM Products"); // 使用实现IConvertible的类型创建Mapper IList<DateTime> orderDates = Db.Northwind.List( Mappers.Convertible<DateTime>(), "SELECT OrderDate FROM Orders"); 使用事务 使用CreateTransaction方法来获取一个ITransactionKeeper事务容器。获取到的事务容器自身也实现了IDbClient,可以在其上进行各种CRUD操作。 事务的最后,别忘了Commit。 ITransactionKeeper同时也实现了IDisposable接口,其Dispose方法能够在事务没有提交时进行事务回滚(如果已经提交,则什么也不做),利用这个机制和C#的using语法,可以很方便的编写一个在出现异常时回滚的事务操作。 using (ITransactionKeeper tran = Db.Northwind.CreateTransaction()) { tran.Execute("UPDATE Products SET ProductName='The Name' WHERE ProductID=115"); tran.Execute("UPDATE Products SET ProductName='The Name2' WHERE ProductID=118"); tran.Commit(); } Dynamic扩展 在Data.Dynamic命名空间的ObjectiveExtension类中,定义了一套IDbClient的扩展方法,能够使用更快捷的方式进行数据库操作。 .net对象传参 这些扩展方法具有IDbClient中的方法很类似的签名,但能够接收一个用于存放参数信息的.net对象,以节省许多编码量(是的,和Dapper、ServiceStack.OrmLite很相似)。 通过这些扩展方法,上面使用参数的示例可以这样写了: DataSet ds = Db.Northwind.DataSet( "CustOrderHist", new { CustomerID = "ALFKI" }, CommandType.StoredProcedure); DataTable dt = Db.Northwind.DataTable( "SELECT * FROM Products WHERE ProductName=@name OR ProductID=@id", new { name = "Ikura", id = 115 }); 获取类型实例 现在不指定Mapper就可以直接进行对象查询了。 Product product = Db.Northwind.Get<Product>("SELECT * FROM Products WHERE ProductID=115"); IList<Product> products = Db.Northwind.List<Product>("SELECT * FROM Products"); IList<DateTime> orderDates = Db.Northwind.List<DateTime>("SELECT OrderDate FROM Orders"); 在这些方法内部,会在运行时动态生成对应的Mapper,并且生成一次以后,信息会被缓存下来,不需要每次都重新创建。当然,因为做了更多的是事情,它还是会比非扩展的原生版本慢那么一点点。 也可以使用匿名对象作为实体模板,在许多场景尤其是处理包含少量字段(但又多于1个)时尤其方便。 var template = new { ProductID = 0, ProductName = string.Empty }; var productsByTemplate = Db.Northwind.TemplateList(template, "SELECT * FROM Products"); 关于字段名称的匹配 .net对象的属性和公共字段使用Pascal命名法,但数据库规范中的字段命名法可能不一样,比如MySql的snake_case命名法;而且也有太多的数据库设计使用“意识流”了。为了解决这个命名差异问题,查询结果映射到非匿名对象字段时支持字段名称的模糊匹配,具体规则如下,越靠前的规则优先级越高: 查询结果的字段名称和对象字段名称完全一致; 大小写不敏感的匹配;例:查询结果字段goodName可映射到对象字段GoodName。 查询结果的字段名称移除下划线(头尾的下划线将保留)之后,再进行大小写不敏感的匹配;例:查询结果字段good_name可映射到对象字段GoodName;_goodName不会映射到GoodName,因为头尾的下划线不会被忽略。 字体匹配时,考前的规则将优先进行匹配,没有匹配到的字段再使用下一优先级的规则进行匹配。若所有规则都为命中,则对象字段将在映射中被忽略从而保持字段类型的默认值。 注意:使用匿名对象作为模板查询时,匿名对象的字段名称需和查询结果的字段名称完全匹配,不支持模糊匹配。 Indexing扩展 在Data.Indexing命名空间的IndexingExtension类中,定义了另外一套IDbClient的扩展方法,能够基于索引访问传入的参数。 记得string.Format方法吗: string.Format("My name is {0}, I'm {1} years old.", "John Doe", 8); 类似的,这些扩展方法用起来是这个样子的: DataTable dt = Db.Northwind.DataTable( "SELECT * FROM Products WHERE ProductName=@0 OR ProductID=@1", "Ikura", 115); IList<Product> products = Db.Northwind.List<Product>( "SELECT * FROM Products WHERE ProductID IN (@0, @1)", 15, 16); 通常在一个地方并不混用两套扩展。Dynamic扩展会更泛用一些,但在一些特定的场景下,使用Indexing扩展也是个好主意。还有,这套扩展方法速度会更快一些。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值