代码改变世界

走进Linq-Linq to SQL源代码赏析 DataContext的初始化

2008-08-14 12:02  横刀天笑  阅读(...)  评论(...编辑  收藏
Linq是微软为大家提供的一个类库,在CLR层没有添加任何东西,那么这就让我们可以一窥Linq的全貌,前面介绍的Linq to Objects很简单,它的核心就是那些扩展方法,所以也没有怎么说它的源代码,从今天起我们来赏析Linq to SQL的源代码。

 

话说DataContextLinq to SQL的入口点,那么我们就从DataContext开始我们的赏析之旅吧。

 

DataContext的构造函数(先看我们用的最多的那个):

        public DataContext(string fileOrServerOrConnection)

        {

            this.objectTrackingEnabled = true;

            this.deferredLoadingEnabled = true;

            if (fileOrServerOrConnection == null)

            {

                throw Error.ArgumentNull("fileOrServerOrConnection");

            }

            this.InitWithDefaultMapping(fileOrServerOrConnection);

        }

this.objectTrackingEnabled = true;

this.deferredLoadingEnabled = true;

这两个设置非常重要,但是这里先不说了。

现在我们到InitWithDefaultMapping方法里:

        private void InitWithDefaultMapping(object connection)

        {

            this.Init(connection, new AttributeMappingSource());

        }

 

调用的是Init方法,看看Init方法:

        private void Init(object connection, MappingSource mapping)

        {

            MetaModel model = mapping.GetModel(base.GetType());

            this.services = new CommonDataServices(this, model);

            this.conflicts = new ChangeConflictCollection();

            if (model.ProviderType == null)

            {

                throw Error.ProviderTypeNull();

            }

            Type providerType = model.ProviderType;

            if (!typeof(IProvider).IsAssignableFrom(providerType))

            {

                throw Error.ProviderDoesNotImplementRequiredInterface(providerType, typeof(IProvider));

            }

            this.provider = (IProvider)Activator.CreateInstance(providerType);

            this.provider.Initialize(this.services, connection);

            this.tables = new Dictionary<MetaTable, ITable>();

            this.InitTables(this);

        }

 

接受两个参数:一个连接对象,从前面可以看出这个连接对象可能是一个连接串,也可能是SQL Server Express的文件路径,还可以是一个IDbConnection对象。第二个参数是一个MappingSource类型的对象,这里就有点猫腻了。MappingSource(它是一个抽象类)是用来构建我们映射模型的,在Linq to SQL里提供两种映射方式:基于Attribute的和使用外部的XML配置文件。那么对应的,MappingSource就有两个子类:AttributeMappingSourceXmlMappingSource(我们还可以提供自己的映射方式)。

再回去看Init方法,它首先使用MappingSourceGetModel方法获得一个MetaModel对象。如果你使用Reflector展开System.Data.Linq.Mapping命名空间,你会发现这里有很多以Meta开头的抽象类:MetaModel,MetaTable,MetaFunction,这是干什么用的呢?还记得使用Attribute的时候有DatabaseTableFunction么?一个MetaModel就是描述一个数据库和一个DataContext类型(你可以继承这个类型)之间的映射模型,同理,MetaFunction就是存储过程或用户自定义函数与.NET的方法之间的映射模型。

我们来看看GetModel方法,它的内容很多,实际就一句话:

model = this.CreateModel(dataContextType)

调用MappingSource的另外一个方法CreateModel创建MetaModel对象,CreateModel是一个抽象方法,它是在AttributeMappingSourceXmlMappingSource里实现的。大家还可以看到MetaModel是个抽象类,它有各种实现。

 

关于源代码中设计模式的旁白

 

这里使用了什么设计模式呢?模板方法?好像有,工厂方法?好像也有。


MappingSource里有两个方法,GetModel方法是一个模板方法,定义了获得MetaModel对象的流程,实际上是调用抽象的CreateModel方法来实现的,CreateModel的实现在子类里。

再看下面这个图:


CreateModel是一个工厂方法,他们推迟到子类实现,在子类创建不同的产品,AttributeMappingSource里的CreateModel创建AttributeMetaModel类,XmlMappingSource里的CreateModel创建MappedMetaModel类。AttributeMetaModelMappedMetaModel都继承自MetaModel类。

 

继续赏析我们的源代码

Type providerType = model.ProviderType;

Linq to SQL本是可以支持多种数据库的,对于这种场景当然是使用Provider Pattern了,这里的providerType就是数据库提供者,从model那里获取,我们就来看看AttributeMetaModel的构造函数里如何得到这个ProviderType:

ProviderAttribute[] customAttributes = (ProviderAttribute[])this.contextType.GetCustomAttributes(typeof(ProviderAttribute), true);

if ((customAttributes != null) && (customAttributes.Length == 1))

{

      this.providerType = customAttributes[0].Type;

}

else

{

     this.providerType = typeof(SqlProvider);

}

 

DataContext类上Attribute,看看是否加了ProviderAttribute这个特性,该特性就是指定数据库提供者的,如果没有指定就默认的使用SqlProvider。我看到这个代码很兴奋,微软太开放了,扩展性这么好,呵呵。继续看Init中的代码:

            //判断你指定的数据库提供者是否实现了IProvider接口,但是

            //微软却将IProvider设为internal的

            if (!typeof(IProvider).IsAssignableFrom(providerType))

            {

                throw Error.ProviderDoesNotImplementRequiredInterface(providerType, typeof(IProvider));

            }

            //使用反射实例化一个提供者对象

            this.provider = (IProvider)Activator.CreateInstance(providerType);

            //初始化提供者

            this.provider.Initialize(this.services, connection);

你指定的Provider必须实现了IProvider接口,但是,但是,可恶的微软居然把IProvider设为internal的,我气愤啊,我无助啊。

Init最后一句就是初始化数据库中的表了,不过这个方法只在强类型的DataContext中才有用,如果直接使用微软提供的DataContext是不会起作用的。

 

不知道微软上次发布的.NET Framework的源代码中有没有Linq的,反正我没找到,所以我用Reflctor还原了代码,然后自己建立了工程,这样你就可以借助VS快速的浏览代码了,不过不能编译。

我会从前到后把Linq to SQL的源代码都说一遍,这个过程是迭代进行的,每篇文章我会附带有文章涉及的代码,并加了我自己的注释。

 

 

点击这里下载本文涉及到的Linq to SQL源代码,不能编译