波斯马BOSSMA Information Technology

Linq从数据库查询数据的原理及实现

发布时间:2011年3月21日 / 分类:ASP.NET, DOTNET / 12,704 次浏览 / 评论

Linq查询数据库使用起来很方便,无论是LINQ to SQL,还是LINQ to Entities, 一直想知道它的原理到底是什么。这几天研究了下,大体了解了其原理,及实现方式。主要用到的技术包括:扩展方法 、表达式树 和 延迟执行(实现接口IEnumerable中的GetEnumerator方法)。

现在公布出来,欢迎就此问题进行讨论。

1、创建要查询的数据库及数据表

顺序执行这两段SQL:

USE [master]
GO
CREATE DATABASE [CLINQDEMO]
USE [CLINQDEMO]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PersonInfo](
?[ID] [int] IDENTITY(1,1) NOT NULL,
?[UserName] [nvarchar](50) NULL,
?[RealName] [nvarchar](50) NULL,
?[Birthday] [datetime] NULL,
?[Gender] [int] NULL,
?CONSTRAINT [PK_PersonInfo] PRIMARY KEY CLUSTERED
(
?[ID] ASC
) ON [PRIMARY])

2、创建数据实体类

因为在实际的应用中数据实体类的属性和数据表的字段可能并不是完全一致的,为了在构造查询语句的时候,能够映射到实际的字段,需要首先建立数据实体和数据表之间的映射关系。比如NHibernate会生成一堆的映射XML文件。

我这里使用自定义特性的方式,在构造SQL语句时,通过反射获取这些特性的值。

定义两个特性DataField和TableName:

??? /// <summary>
??? /// 定义属性对应的字段名称及字段类型
??? /// </summary>
??? [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)]
??? public class DataField : System.Attribute
??? {
??????? public string Name;
??????? public string DataType;

??????? public DataField(string name)
??????? {
??????????? this.Name = name;
??????????? this.DataType = "string";
??????? }
??? }

??? /// <summary>
??? /// 定义实体类对应的数据表名
??? /// </summary>
??? [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Class)]
??? public class TableName : System.Attribute
??? {
??????? public string Name;

??????? public TableName(string name)
??????? {
??????????? this.Name = name;
??????? }
??? }

然后创建数据实体类:PersonInfo

[TableName("PersonInfo")]
??? public class PersonInfo
??? {
??????? public PersonInfo()
??????? {
??????? }

??????? [DataField("ID", DataType = "int")]
??????? public int ID
??????? {
??????????? get;
??????????? set;
??????? }

??????? [DataField("UserName", DataType = "string")]
??????? public string UserName
??????? {
??????????? get;
??????????? set;
??????? }

??????? [DataField("RealName", DataType = "string")]
??????? public string RealName
??????? {
??????????? get;
??????????? set;
??????? }

??????? [DataField("Birthday", DataType = "DateTime")]
??????? public DateTime Birthday
??????? {
??????????? get;
??????????? set;
??????? }

??????? [DataField("Gender", DataType = "int")]
??????? public int Gender
??????? {
??????????? get;
??????????? set;
??????? }
??? }

可以看到自定义的特性已经应用上了。

3、定义支持Linq查询方式的数据集

首先定义这个数据集:

public class EntitySet<T> : List<T>, IEntitySet<T>
? ? {
? ? ? ? private bool IsReInitData = false;
? ? ? ? private string strWhere = string.Empty;

? ? ? ? public void SetCondition(string str)
? ? ? ? {
? ? ? ? ? ? if (strWhere == string.Empty)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? strWhere += str;
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? strWhere += " and " + str;
? ? ? ? ? ? }
? ? ? ? }

? ? ? ? public new IEnumerator<T> GetEnumerator()
? ? ? ? {
? ? ? ? ? ? //重新计算数据,并返回
? ? ? ? ? ? if (!IsReInitData)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //获取类型
? ? ? ? ? ? ? ? Type modelType = typeof(T);

? ? ? ? ? ? ? ? //获取表名称
? ? ? ? ? ? ? ? TableName att = (TableName)Attribute.GetCustomAttribute(modelType, typeof(TableName));
? ? ? ? ? ? ? ? string tableName = att.Name;

? ? ? ? ? ? ? ? SqlConnection objSqlConnection = new SqlConnection("server=192.168.100.100;uid=sa;pwd=sa;database=CLINQDEMO");
? ? ? ? ? ? ? ? objSqlConnection.Open();

? ? ? ? ? ? ? ? //生成查询语句
? ? ? ? ? ? ? ? string query = "SELECT * from " + tableName;
? ? ? ? ? ? ? ? if (strWhere != string.Empty)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? query += " Where " + strWhere;
? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? SqlDataAdapter obj = new SqlDataAdapter();
? ? ? ? ? ? ? ? obj.SelectCommand = new SqlCommand(query, objSqlConnection);
? ? ? ? ? ? ? ? SqlCommand objSqlCommand = new SqlCommand(query, objSqlConnection);
? ? ? ? ? ? ? ? SqlDataReader objSqlReader = objSqlCommand.ExecuteReader();
? ? ? ? ? ? ? ? while (objSqlReader.Read())
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //创建类的实例
? ? ? ? ? ? ? ? ? ? T model = (T)System.Activator.CreateInstance(modelType, true);

? ? ? ? ? ? ? ? ? ? //得到实体类属性的集合
? ? ? ? ? ? ? ? ? ? PropertyInfo[] properites = typeof(T).GetProperties();
? ? ? ? ? ? ? ? ? ? foreach (PropertyInfo propertyInfo in properites)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? DataField field = (DataField)Attribute.GetCustomAttribute(propertyInfo, typeof(DataField));
? ? ? ? ? ? ? ? ? ? ? ? string fieldName = field.Name;

? ? ? ? ? ? ? ? ? ? ? ? if (field.DataType == "string")
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? string fieldValue = objSqlReader[fieldName].ToString();
? ? ? ? ? ? ? ? ? ? ? ? ? ? propertyInfo.SetValue(model, fieldValue, null);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else if (field.DataType == "DateTime")
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? DateTime fieldValue = DateTime.Parse(objSqlReader[fieldName].ToString());
? ? ? ? ? ? ? ? ? ? ? ? ? ? propertyInfo.SetValue(model, fieldValue, null);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else if (field.DataType == "int")
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? int fieldValue = int.Parse(objSqlReader[fieldName].ToString());
? ? ? ? ? ? ? ? ? ? ? ? ? ? propertyInfo.SetValue(model, fieldValue, null);
? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? base.Add(model);
? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? objSqlReader.Close();
? ? ? ? ? ? }

? ? ? ? ? ? return base.GetEnumerator();
? ? ? ? }
? ? }

这个数据集的类继承了List<T>和IEntitySet<T>。

继承List<T>是为了重写GetEnumerator,这个方法会在真正需要数据的时候才调用,在这里就可以实现我们的数据查询延迟执行了。

注意这里使用反射来创建泛型类的实例,获取类属性以及给属性赋值。使用反射会对程序性能造成一定的影响。

这里对属性赋值只做了几个简单类型的赋值,实际应用还需要更多编码工作来实现更多的字段类型赋值。

IEntitySet<T>是我自己定义的,代码如下:

这个接口定义了实体集必须实现的方法。然后我们会针对这个接口添加扩展方法。

这个扩展方法的效果类似于我们在Linq to EF中使用Where。在这里使用这个方法构造条件语句,需要对表达式树进行解析。

这里只做了简单的解析,实际应用还有许多工作要做。

关于表达式树,这里不作介绍,详细请阅读:http://msdn.microsoft.com/zh-cn/library/bb397951.aspx

4、最后测试一下:

static void Main(string[] args)
??????? {
????????????EntitySet<PersonInfo> set = new EntitySet<PersonInfo>();
??????????? set.Condition(t => t.Gender == 1).Condition(t => t.ID >= 1);

??????????? foreach (PersonInfo person in set)
??????????? {
??????????????? Console.WriteLine(person.ID + "," + person.RealName + "," + person.UserName + "," + person.Birthday.ToString("yyyy-MM-dd") + "," + person.Gender);
??????????? }

??????????? Console.ReadLine();
??????? }

这里使用我们刚才添加的Condition方法,是不是和Where很相似?

OK,这篇文章我写的很简单,很多概念和方面没有深入。欢迎有兴趣的朋友与我探讨。

还有几篇文章供大家参考,实现自己的Linq:

http://www.cnblogs.com/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html

http://www.cnblogs.com/sweatypalms/archive/2007/10/18/929312.html

http://msdn.microsoft.com/zh-cn/library/bb546158(v=vs.90).aspx

本博客所有文章如无特别注明均为原创。
复制或转载请以超链接形式注明转自波斯马,原文地址《Linq从数据库查询数据的原理及实现

关键字:

建议订阅本站,及时阅读最新文章!
【上一篇】 【下一篇】

发表评论