
1. 在Visual Studio中创建一个示例工程。最简单的莫过于ConsoleApplication
2. 在工程中添加ADO.NET Entity Data Model。
选择其中的两个表作为示例,表Teacher和表Course
添加edmx之后,Visual
Studio为自动帮我们生成/添加所有需要的文件和内容,然后我们就可以开始在代码中 *** 作数据库了。来看看在EF中几种不同的数据加载方式。
惰性加载(Lazy Loading)
默认情况下,EF会使用惰性加载方式加载数据,即ctx.Configuration.LazyLoadingEnabled = true
在下面的代码中,外层循环会执行一次查询,并将返回的结果存放在变量q中。而内层循环会在每一次循环过程中独立进行查询,所以,如果数据库表Teacher中有100条记录而Course有1000条记录,那么整个过程将产生1001次查询。
using (var ctx = new SchoolDBEntities())
{
var q = from t in ctx.Teachers
select t
foreach (var teacher in q)
{
Console.WriteLine("Teacher : {0}", teacher.TeacherName)
Console.WriteLine("Respective Courses...")
foreach (var course in teacher.Courses)
{
Console.WriteLine("Course name : {0}", course.CourseName)
}
Console.WriteLine()
Console.ReadKey()
}
}
下面是程序执行的结果以及在SQL Server
Profiler中的跟踪记录。可以清楚地看到,对表Teacher只进行了一次查询,由于该表只有8条记录,于是在内层循环中又分别产生了8次对表Course的查询。
在某些场合下,这种情况是可以接受的。你完全可以根据需要来控制内层循环何时显示加载数据,或者根本不加载数据。但是,在分层结构的应用程序中,上述代码结构并不适用,因为内层循环需要依赖于外层的Context,也就是说它们是在同一个数据库上下文中完成的,如果尝试将内层循环的代码移到外面或者其它类中,则它将获取不到任何数据。
显式加载(Explicit Loading)
如果你想人为控制惰性加载的行为,可以尝试使用下面的代码。首先需要手动关闭EF的惰性加载,通过代码ctx.Configuration.LazyLoadingEnabled
= false来完成。
using (var ctx = new SchoolDBEntities())
{
ctx.Configuration.LazyLoadingEnabled = false
var q = from t in ctx.Teachers
select t
foreach (var teacher in q)
{
Console.WriteLine("Teacher : {0}", teacher.TeacherName)
Console.WriteLine("Respective Courses...")
// Conditionally load the child data
if (true)
{
ctx.Entry(teacher).Collection(c =>c.Courses).Load()
}
foreach (var course in teacher.Courses)
{
Console.WriteLine("Course name : {0}", course.CourseName)
}
Console.WriteLine()
Console.ReadKey()
}
}
注意内层循环只有在上面高亮显示部分的代码执行之后才会获取到数据,否则返回结果为0。通过添加判断条件,我们可以对数据加载方式进行控制,从而有效地减少程序与数据库交互的次数。大多数情况下,我们从数据库获取到的数据并不都是有用的,如果每次只有很少一部分数据有用,那么我们为什么不过滤掉那些无用的数据从而尽量较少数据交互的次数呢?
预先加载(Eager Loading)
如果你想让所有数据一次性全部加载到内存中,那么你需要使用.Include(Entity)方法。看下面的代码,
using (var ctx = new SchoolDBEntities())
{
var q = from t in ctx.Teachers.Include("Courses")
select t
foreach (var teacher in q)
{
Console.WriteLine("Teacher : {0}", teacher.TeacherName)
Console.WriteLine("Respective Courses...")
foreach (var course in teacher.Courses)
{
Console.WriteLine("Course name : {0}", course.CourseName)
}
Console.WriteLine()
Console.ReadKey()
}
}
如果你查看SQl Server
Profiler中的跟踪信息,你会发现只有一次数据交互过程,即程序只通过一次查询便获取到了所有需要的数据。在分层结构中,该方法是最容易的,我们可以将数据库底层获取到的结果返回给上层,它不具有任何依赖项。同时,它也可以减少程序与数据库的交互次数。不过仍然有缺点,那就是如果数据量较大,一次性将所有数据载入内存往往并不是最明智的选择。.Include(Entity)方法允许级联使用,你可以预先加载具有多层级结构的数据。
一、使用存储过程的必要性我们知道EF通过元数据,即概念模型(Concept Model)、存储模型(Storage Model)和概念/存储映射(C/S Mapping),和状态追踪(State Tracking)机制可以为基于模型的 *** 作自动生成SQL。对于一些简单的项目开发,这是非常理想的,因为他们完全可以不用关注数据存储层面的东西,你可以采用一些完全不具有数据库知识的开发者。但是理想总归是理想,对于企业级开发来说,我们需要的是对数据库层面数据的 *** 作有自己的控制。在这方面,我们可以随便举两个典型的场景:
逻辑删除:对于一些重要的数据,我们可能需要让它们永久保存。当我们试图“删除”这些数据的时候,我们并不是将它们从数据表中移除(物理删除),而是为这条记录作一个已经被删除的标记;
并发处理:为了解决相同的数据在获取和提交这段时间内被另一个用户修改或者删除,我们往往SQL层面增加并发控制的逻辑。比较典型的做法是在每一个表中添加一个VersionNo这样的字段,你可以采用TimeStamp,也可以直接采用INT或者GUID。在执行Update或者Delete的SQL中判断之前获取的VersionNo是否和当前的一致。
让解决这些问题,就不能使用EF为我们自动生成的SQL,只有通过使用我们自定义的存储过程。
二、实现存储过程自动匹配的必要条件
本篇文章提供的存储过程自动映射机制是通过代码生成的方式完成的。说白了,就是读取原来的.edmx模型文件,通过分析在存储模型中使用的数据表,导入基于该表的CUD存储过程;然后再概念/存储映射节点中添加实体和这些存储过程的映射关系。那实现这样的代码生成,需要具有如下三个固定的映射规则。
数据表名-存储过程名:这个映射关系帮助我们通过存储模型中的实体名找到对应CUD三个存储过程(如果实体是数据表);
数据表列名-存储过程参数名:当存储过程被执行的时候,通过这个映射让概念模型实体某个属性值作为对应的参数;
存储过程参数名-版本:当进行参数赋值的时候,通过这个映射决定是使用Original或者Current版本。
在实际的开发过程中,这样的标准存储过程一般都是通过代码生成器生成的(在我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]》中有过相应的实现),它们具有这样的映射关系。
基于这三种映射关系,我定义了如下一个名为IProcedureNameConverter的接口。其中OperationKind是我自定义的一个表示CUD *** 作类型的枚举。
1: public interface IProcedureNameConverter
2: {
3: string GetProcedureName(string tableName, OperationKind operationKind)
4: string GetColumnName(string parameterName)
5: DataRowVersion GetVersion(string parameterName)
6: }
7:
8: public enum OperationKind
9: {
10: Insert,
11: Update,
12: Delete
13: }
按照我们当前项目采用的命名规范,我定义了如下一个默认的DefaultNameConverter。它体现的是这样的映射关系,比如有个数据表明为T_USER(大写,单词之间用“_”隔开,并以T_为前缀),它对应的CUD存储过程名分别为:P_USER_I、P_USER_U和P_USER_D(大写,以代表存储过程的P_为前缀,后缀_I/U/D表示CUD *** 作类型,中间为去除前缀的表名)。如果列名为USER_ID,参数名为p_user_id(小写,加p_前缀)。如果需要用Original值为参数赋值,需要将p_前缀改成o_前缀(o_user_id)。
1: public class DefaultNameConverter: IProcedureNameConverter
2: {
3: public string GetProcedureName(string tableName, OperationKind operationKind)
4: {
5: switch (operationKind)
6: {
7: case OperationKind.Insert:
8: return string.Format("P_{0}_I", tableName.Substring(2))
9: case OperationKind.Update:
10: return string.Format("P_{0}_U", tableName.Substring(2))
11: default:
12: return string.Format("P_{0}_D", tableName.Substring(2))
13: }
14: }
15:
16: public string GetColumnName(string parameterName)
17: {
18: return parameterName.Substring(2).ToUpper()
19: }
20:
21: public DataRowVersion GetVersion(string parameterName)
22: {
23: if(parameterName.StartsWith("o"))
24: {
25: return DataRowVersion.Original
26: }
27: else
28: {
29: return DataRowVersion.Current
30: }
31: }
32: }
三、通过T4生成新的.edmx模型
我们采用的基于T4的代码生成,了解EF的应该对T4不会感到陌生了。如果对代码生成感兴趣的话,可以看看我的文章《与VS集成的若干种代码生成解决方案[博文汇总(共8篇)]》。这里利用借助于T4 ToolBox这个开源工具箱,并采用SQL Server SMO获取存储过程信息。所有涉及到的文本转化都实现在如下一个ProcedureMappingTemplate类型中,由于内容较多,具体实现就忽略了,有兴趣的朋友可能下载源代码。ProcedureMappingTemplate具有两个构造函数的参数分别表示:源.edmx文件,服务器和数据库名,存储过程的Schema(默认为dbo)和具体的ProcedureNameConverter(默认为DefaultNameConverter)。
1: public class ProcedureMappingTemplate: Template
2: {
3: public XmlDocument SourceModel { getprivate set}
4: public IProcedureNameConverter ProcedureNameConverter { getprivate set}
5: public Database Database { getprivate set}
6: public string Schema { getprivate set}
7:
8: public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName)
9: public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName,
10: IProcedureNameConverter procedureNameConverter, string schema)
11:
12: protected virtual XElement GenerateStorageModelNode()
13: protected virtual XElement GenerateMappingNode()
14: public override string TransformText()
15: {
16: XElement newStorageModelNode = this.GenerateStorageModelNode()
17: XElement newMappingNode = this.GenerateMappingNode()
18:
19: XmlNode storageModelNode = this.SourceModel.GetElementsByTagName("edmx:StorageModels")[0]
20: storageModelNode.InnerXml = newStorageModelNode.Elements().ToArray()[0].ToString()
21:
22: XmlNode mappingNode = this.SourceModel.GetElementsByTagName("edmx:Mappings")[0]
23: mappingNode.InnerXml = newMappingNode.Elements().ToArray()[0].ToString()
24:
25: this.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
26: this.Write(this.SourceModel.DocumentElement.OuterXml.Replace("xmlns=\"\"",""))
27: return GenerationEnvironment.ToString()
28: }
29: }
在使用过程中,你只需要在tt模板中创建这个ProcedureMappingTemplate对象,调用Render方法即可。
1: <#@ template debug="true" hostSpecific="true" #>
2: <#@ output extension=".edmx" #>
3: <#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>
4: <#@ assembly name="Microsoft.SqlServer.Smo" #>
5: <#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
6: <#@ assembly name="$(TargetDir)Artech.ProcedureMapping.dll" #>
7: <#@ import namespace="Artech.ProcedureMapping" #>
8: <#@ include file="T4Toolbox.tt" #>
9: <#
10: new ProcedureMappingTemplate(this.Host.ResolvePath("UserModel.edmx"),".","EFExtensions").Render()
11: #>
四、看看生成出来的.emdx
通过上面创建的TT模板(你指定的数据库中一定要存在具有相应映射关系的存储过程),新的.edmx模型文件会作为该tt文件的依赖文件被生成出来。而这个新生成的.edmx具有存储过程映射信息。具体来说,下面是原始的.edmx文件(只保留元数据节点)。
1: <?xml version="1.0" encoding="utf-8"?>
2: <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
3: <!-- EF Runtime content -->
4: <edmx:Runtime>
5: <!-- SSDL content -->
6: <edmx:StorageModels>
7: <Schema Namespace="Artech.UserModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
8: <EntityContainer Name="ArtechUserModelStoreContainer">
9: <EntitySet Name="T_USER" EntityType="Artech.UserModel.Store.T_USER" store:Type="Tables" Schema="dbo" />
10: </EntityContainer>
11: <EntityType Name="T_USER">
12: <Key>
13: <PropertyRef Name="USER_ID" />
14: </Key>
15: <Property Name="USER_ID" Type="varchar" Nullable="false" MaxLength="50" />
16: <Property Name="USER_NAME" Type="nvarchar" Nullable="false" MaxLength="50" />
17: </EntityType>
18: </Schema>
19: </edmx:StorageModels>
20: <!-- CSDL content -->
21: <edmx:ConceptualModels>
22: <Schema Namespace="Artech.UserModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
23: <EntityContainer Name="EFExtensionsEntities" annotation:LazyLoadingEnabled="true">
24: <EntitySet Name="Users" EntityType="Artech.UserModel.User" />
25: </EntityContainer>
26: <EntityType Name="User">
27: <Key>
28: <PropertyRef Name="ID" />
29: </Key>
30: <Property Name="ID" Type="String" Nullable="false" MaxLength="50" Unicode="false" FixedLength="false" />
31: <Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" />
32: </EntityType>
33: </Schema>
34: </edmx:ConceptualModels>
35: <!-- C-S mapping content -->
36: <edmx:Mappings>
37: <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
38: <EntityContainerMapping StorageEntityContainer="ArtechUserModelStoreContainer" CdmEntityContainer="EFExtensionsEntities">
39: <EntitySetMapping Name="Users">
40: <EntityTypeMapping TypeName="Artech.UserModel.User">
41: <MappingFragment StoreEntitySet="T_USER">
42: <ScalarProperty Name="ID" ColumnName="USER_ID" />
43: <ScalarProperty Name="Name" ColumnName="USER_NAME" />
44: </MappingFragment>
45: </EntityTypeMapping>
46: </EntitySetMapping>
47: </EntityContainerMapping>
48: </Mapping>
49: </edmx:Mappings>
50: </edmx:Runtime>
51: </edmx:Edmx>
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)