资源描述
Entity Data Model (EDM) 深入分析, Part 1
Entity Data Model是.NET Framework v3.5 SP1引入的新功能,比Linq To SQL更加强大的ORM,让开发人员只需要着眼于领域对象模型的开发,而不需要考虑它们是如何与关系数据库交互。本系列文章逐步深入介绍如下内容:EDMX Schema 文件、Model Browser、映射关系、产生的实体类(Generated entity classes)、文档(Documentation)等等。
1. EDMX Schema 文件
可以将EDMX作为XML文件打开,你会发现该文件包含3个主要部分。
Conceptual Models (CSDL)
Storage Models (SSDL)
Mapping (MSL)
一般情况下,你没有必要手动修改EDMX -XML文件。可视化的EDM 设计器、Mapping Details窗口和Model Browser 窗口包含有上述3个部分,并非常友好地显示整个Entity Data Model模型。
当你编译项目时,MSBuild 将从EDMX文件提取CSDL/SSDL/MSL内容,并放置3个独立的XML文件到项目的输出目录。
2. Model Browser 窗口
Model Browser窗口以可视的树形图显示概念模型和存储模型。
3. Mapping details 窗口
EDM设计器也提供了一个不错的Mapping Details 窗口,包含2个视图。
Map Entity to Tables / View
这一视图显示了数据库中所有字段和相应实体中的属性,可以用来查看和编辑EDM的映射关系。
Map Entity to Functions
这一视图用来选择一个特定的存储过程来插入、更新或删除Entity实例。
4. 生成的实体类(Generated Entity Classes)
除了上述的XML Schema文件外,EDM向导也生成了实体类。下一步仔细分析.Designer.cs文件中的实体类,并和LINQ to SQL中的类进行比较。
1) 比较 LINQ to SQL class 和 EDM EntityObject class
// LINQ to SQL
[Table(Name="dbo.Employees")]
public partial class Employee : INotifyPropertyChanging, INotifyPropertyChanged
EDM类则是以不同的attributes,并且总是继承EntityObject或ComplexObject 类。EntityObject 类提供了变更跟踪和关系管理。
// Entity Data Model
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="NorthwindModel", Name="Employee")]
[global::System.Runtime.Serialization.DataContractAttribute()]
[global::System.Serializable()]
public partial class Employee : global::System.Data.Objects.DataClasses.EntityObject
2) 比较LINQ to SQL entity constructor 和 EDM Create method
// LINQ to SQL
public Employee()
{
this._Employees = new EntitySet<Employee>(new Action<Employee>(this.attach_Employees), new Action<Employee>(this.detach_Employees));
this._EmployeeTerritories = new EntitySet<EmployeeTerritory>(new Action<EmployeeTerritory>(this.attach_EmployeeTerritories),
new Action<EmployeeTerritory>(this.detach_EmployeeTerritories));
this._Orders = new EntitySet<Order>(new Action<Order>(this.attach_Orders), new Action<Order>(this.detach_Orders));
this._Employee1 = default(EntityRef<Employee>);
OnCreated();
}
EDM没有生成上述LINQ to SQL的构造函数,而是创建了一个特定的Create方法,并提供了所有必需属性(not nullable)的输入参数。
// Entity Data Model
public static Employee CreateEmployee(int employeeID, string lastName, string firstName)
{
Employee employee = new Employee();
employee.EmployeeID = employeeID;
employee.LastName = lastName;
employee.FirstName = firstName;
return employee;
}
3) 比较LINQ to SQL 和 EDM : 实体属性(entity property)
// LINQ to SQL
[Column(Storage="_EmployeeID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int EmployeeID
{
get
{
return this._EmployeeID;
}
set
{
if ((this._EmployeeID != value))
{
this.OnEmployeeIDChanging(value);
this.SendPropertyChanging();
this._EmployeeID = value;
this.SendPropertyChanged("EmployeeID");
this.OnEmployeeIDChanged();
}
}
}
尽管EDM公有属性(public property)的attribute是不同的,但get和set 基本是一样的。
// Entity Data Model
[global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[global::System.Runtime.Serialization.DataMemberAttribute()]
public int EmployeeID
{
get
{
return this._EmployeeID;
}
set
{
this.OnEmployeeIDChanging(value);
this.ReportPropertyChanging("EmployeeID");
this._EmployeeID = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
this.ReportPropertyChanged("EmployeeID");
this.OnEmployeeIDChanged();
}
}
4) 比较LINQ to SQL Table 和 EDM ObjectQuery
// LINQ to SQL
public System.Data.Linq.Table<Employee> Employees
{
get
{
return this.GetTable<Employee>();
}
}
在LINQ to SQL中,调用GetTable 方法,返回实体集合。在EDM中,通过Object Services compoment 执行Entity SQL 查询,返回EntityType的计划EntitySet。
// Entity Data Model
[global::System.ComponentModel.BrowsableAttribute(false)]
public global::System.Data.Objects.ObjectQuery<Employee> Employees
{
get
{
if ((this._Employees == null))
{
this._Employees = base.CreateQuery<Employee>("[Employees]");
}
return this._Employees;
}
}
private global::System.Data.Objects.ObjectQuery<Employee> _Employees;
5) 比较LINQ to SQL DataContext 和 EDM ObjectContext
// LINQ to SQL
[System.Data.Linq.Mapping.DatabaseAttribute(Name="Northwind")]
public partial class NorthwindDataContext : System.Data.Linq.DataContext
EDM有一个类似于LINQ to SQL DataContext 的ObjectContext类,ObjectContext 类是负责与EDM 中实体类型交互的基本类。ObjectContext用来创建数据库连接、检索数据、持久化对象、以及对数据库的插入、更新和删除操作。
// Entity Data Model
public partial class NorthwindEntities : global::System.Data.Objects.ObjectContext
ObjectContext的连接字符串指向元数据(CSDL/SSDL/MSL 文件)和数据源(数据库连接字符串)。
connectionString="metadata=.NorthwindModel.csdl|.NorthwindModel.ssdl|.NorthwindModel.msl;
provider=System.Data.SqlClient;provider connection string="
Data Source=SQLEXPRESS; Initial Catalog=Northwind; Integrated Security=True; MultipleActiveResultSets=True""
5. Documentation 属性
EDM中的实体类型(EntityTypes)、关联和属性有一个Documentation属性,对LINQ to SQL而言,这是一个新的属性。
Documentation属性将更新生成的partial实体类的XML注释,可以用来生成代码文档的帮助文件。
/// <summary>
/// Employee entity which corresponds with the Northwind.Employees table
/// </summary>
/// <KeyProperties>
/// EmployeeID
/// </KeyProperties>
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="NorthwindModel", Name="Employee")]
[global::System.Runtime.Serialization.DataContractAttribute()]
[global::System.Serializable()]
public partial class Employee : global::System.Data.Objects.DataClasses.EntityObject
英文链接:
1. ADO.NET Entity Framework & LINQ to Entities,
http://www.scip.be/index.php?Page=ArticlesNET12
Entity Data Model (EDM) 深入分析, Part 2
实体 SQL (Entity SQL),它是一种新的 SQL 语言,其中加入了之前的 SQL 语言并不支持的基于概念的查询功能。ESQL 扩展现有 SQL 语言的方式与 EDM 扩展数据库中所使用的关系模型的方式十分类似。此外,ESQL 未绑定到任何特定于后台数据库的语法,因此可一次性编写查询(和/或应用程序),无论针对的是哪个后台数据库都无影响。
Entity SQL 是基于文本的、面向集合的、延后绑定的查询语言,也受到了T-SQL的影响。可以使用Entity SQL 创建对EDM的查询,Entity SQL 既可以通过Object Services components来执行,也可以通过Entity Client components 来执行。Entity SQL 设计的非常灵活,因此也变得有些复杂。本篇文章侧重于不同的查询技术,仅仅使用简单的查询,不包含复杂的条件、关联和聚合公式。
本系列文章上一篇:
Entity Data Model (EDM) 深入分析, Part 1
1. 使用ObjectQuery<T> 查询返回实体类型(Entity Type)集合
下面演示如何执行Entity SQL 查询,返回实体类型的实例集合。
1) 首先创建Northwind ObjectContext 实例。
2) Entity SQL语句本身是字符串表达式,在大多数情况下,由SELECT-FROM 查询语句组成。在SELECT语句中使用VALUE关键字来表示返回的实体是一条数据行。
3) 使用Object Services components 执行查询。调用ObjectContext 的工厂方法CreateQuery<T>(),创建一个ObjectQuery 对象,该对象表示对存储数据源的查询,查询表达式为Entity SQL 语句。
4) Entity Framework实体框架采用延迟装载(Deferred loading)。因此只有在显式需要数据时,才真正执行SQL语句。在这种情况下,在ForEach第一次迭代时,才执行查询语句。
NorthwindEntities context = new NorthwindEntities();
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp";
var query = context.CreateQuery<Employee>(sql);
foreach (var emp in query)
Console.WriteLine("{0} {1} {2} {3}", emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
除了使用工厂方法CreateQuery<T>外,也可以直接创建ObjectQuery对象实例,并传入Object Context 参数,示例代码如下:
NorthwindEntities context = new NorthwindEntities();
var sql = "NorthwindEntities.Employees";
ObjectQuery<Employee> query = new ObjectQuery<Employee>(sql, context);
foreach (var emp in query)
Console.WriteLine("{0} {1} {2} {3}", emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
下面增加一个WHERE条件:
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = 'USA'";
var query = context.CreateQuery<Employee>(sql);
2. 带参数的ObjectQuery<T>查询
参数变量是在Entity SQL外定义的,在查询语句中需要以@符合作为前缀定义变量名。参数定义为ObjectParameter 对象,然后增加到ObjectQuery 实例中。
下面增加一个Country 变量:
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = @country";
var query = context.CreateQuery<Employee>(sql);
query.Parameters.Add(new ObjectParameter("country", "USA"));
同样以ObjectQuery 示例对象实现这一功能:
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = @country";
ObjectQuery<Employee> query = new ObjectQuery<Employee>(sql, context);
query.Parameters.Add(new ObjectParameter("country", "USA"));
ObjectParameter对象也可以直接传入CreateQuery<T>方法:
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = @country";
var query = context.CreateQuery<Employee>(sql, new ObjectParameter("country", "USA"));
第三种方法的是使用Where 扩展方法,使用关键字it 指向当前的查询语句:
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp";
var query = context.CreateQuery<Employee>(sql)
.Where("it.Country = @country", new ObjectParameter("country", "USA"));
3. ObjectQuery<T> 查询返回基本类型(Primitive Type)
除了返回实体类型外,也可以返回基本类型集合,因此需要确保SELECT语句仅仅返回1个值,同时也需要在CreateQuery方法中指定需要返回的基本类型。
var sql = "SELECT VALUE emp.EmployeeID FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = @country";
var query = context.CreateQuery<int>(sql, new ObjectParameter("Country", "USA"));
foreach (var id in query)
Console.WriteLine("{0}", id.ToString());
另一示例脚本:
var sql = "SELECT VALUE emp.Country FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.EmployeeID = @id";
var query = context.CreateQuery<string>(sql, new ObjectParameter("id", 1));
Console.WriteLine(query.First());
除了使用 Entity SQL外,你也能使用查询构造(Query Builder)方法实现相同的效果。Entity SQL 提供了SelectValue 方法,可以提过隐式的行构造器,仅仅返回指定的列。
string country = context.Employees.SelectValue<string>("it.Country", new ObjectParameter("id", 1)).First();
Console.WriteLine(country);
4. ObjectQuery<T> 查询返回匿名类型
也有可能需要调整数据,并使用ObjectQuery<T>查询返回匿名类型。在CreateQuery 方法中,改变SELECT语句并使用DbDataRecord 类,DbDataReader类在.NET 1.0引入,它提供了对任何枚举类型的数据绑定支持。
var sql = "SELECT emp.LastName, emp.FirstName " +
"FROM NorthwindEntities.Employees AS emp ";
var query = context.CreateQuery<DbDataRecord>(sql);
foreach (var emp in query)
Console.WriteLine("{0} {1}", emp[0], emp[1]);
另一示例代码:
var sql = "SELECT emp.LastName AS FamilyName, emp.FirstName " +
"FROM NorthwindEntities.Employees AS emp ";
var query = context.CreateQuery<DbDataRecord>(sql);
foreach (var emp in query)
Console.WriteLine("{0} {1}", emp["FamilyName"], emp["FirstName"]);
Entity Data Model (EDM) 深入分析, Part 3
EntityClient
实体框架(Entity Framework)在ADO.NET 3.5 提供程序的基础上引入新的 ADO.NET 提供程序 EntityClient。EntityClient 看上去与之前使用的 ADO.NET 提供程序非常类似,它将提供第一个抽象,可允许开发人员使用标准的 Connection、Command 和 DataReader 对象依照 EDM 执行查询。它还会将映射域模型所需的客户端视图引擎(根据 EDM 定义的)添加到底层关系数据库架构。必要时,EntityClient 可借助 ESQL 查询字符串让开发人员以行和列的形式处理实体,而不必生成类来表示概念架构。
1. EntityCommand 查询返回实体类型
Entity SQL也可以通过EntityClient 来执行,尽管代码比较啰嗦,但是在某些情况下,也是优点。
1) 首先创建EntityConnection,重用Northwind data context 的连接字符串,并打开连接。
2) 创建 EntityCommand 对象,并传入Entity SQL语句和数据库连接对象。
3) 创建DbDataReader对象,并循环读取返回的结果集。
NorthwindEntities context = new NorthwindEntities();
EntityConnection conn = new EntityConnection(context.Connection.ConnectionString);
conn.Open();
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp";
EntityCommand cmd = new EntityCommand(sql, conn);
DbDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
{
Console.WriteLine("{0} {1} {2} {3}", reader["EmployeeID"], reader["LastName"],
reader["FirstName"], reader["Country"]);
}
当时使用SequentialAccess的DbDataReader时,需要小心访问数据,务必有序的读取。
如你改变成员的顺序,将抛出InvalidOperationException 异常 - "Attempt to read from column ordinal '0' is not valid. With CommandBehavior.SequentialAccess, you may only read from column ordinal '2' or greater."
Console.WriteLine("{0} {1} {2} {3}", reader["LastName"], reader["EmployeeID"],
reader["FirstName"], reader["Country"]);
2. EntityCommand 查询返回匿名类型
采用相同的技术可以实现返回匿名类型。
EntityConnection conn = new EntityConnection(context.Connection.ConnectionString);
conn.Open();
var sql = "SELECT emp.LastName, emp.FirstName " +
"FROM NorthwindEntities.Employees AS emp";
EntityCommand cmd = new EntityCommand(sql, conn);
DbDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
{
Console.WriteLine("{0} {1}", reader["LastName"], reader["FirstName"]);
}
3. EntityCommand 带参数查询
EntityCommand 带参数也比较容易,在Entity SQL字符串中参数名称以@作为前缀,接着创建EntityParameter对象,并增加到EntityCommand 的Parameters集合内。
EntityConnection conn = new EntityConnection(context.Connection.ConnectionString);
conn.Open();
var sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp " +
"WHERE emp.Country = @country";
EntityCommand cmd = new EntityCommand(sql, conn);
EntityParameter param = new EntityParameter("country", DbType.String);
param.Value = "USA";
cmd.Parameters.Add(param);
DbDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
{
Console.WriteLine("{0} {1} {2} {3}", reader["EmployeeID"], reader["LastName"],
reader["FirstName"], reader["Country"]);
}
LINQ to Entities
LINQ是在.NET v3.5 引入的新技术。相对于前面的Entity SQL而言,我更偏爱LINQ to entities。LINQ查询虽然有一些局限,但是LINQ更容易、更自然,此外,还支持强类型,因此智能提示能帮助编写LINQ查询脚本。
LINQ to Entities 与LINQ to Objects和LINQ to SQL 基本一样,因此下面简单演示2个基本的LINQ to Entities的查询。
1. LINQ 带参数查询
NorthwindEntities context = new NorthwindEntities();
string country = "USA";
var query = from e in context.Employees
where e.Country == country
select e;
foreach (var emp in query)
Console.WriteLine("{0} {1} {2} {3}", emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
2. LINQ 查询返回匿名类型
NorthwindEntities context = new NorthwindEntities();
var query = from e in context.Employees
select new { e.LastName, e.FirstName };
foreach (var emp in query)
Console.WriteLine("{0} {1}", emp.LastName, emp.FirstName);
这篇文章对Entity Data Model 和Entity Framework 提供了各种查询技术进行了简单的介绍,希望对你有帮助。下一篇文章将介绍更高级的 Entity SQL 查询技术、查看SQL 语句、eager loading、变更跟踪、并发… 等等。
ADO.NET Entity Framework 深入分析, Part 4
Entity Data Model 是一个概念模型,所有Entity SQL和LINQ to Entities 查询将最终转化为T-SQL的脚本,从数据库中查询数据。这里演示了几种方法来查看生成的T-SQL,有助于Debug或分析问题。
1. 使用SQL Server Profiler 工具
与LINQ to SQL比较而言,ObjectContext 类没有提供Log属性或者通用的log机制,因此,无法在Visual Studio 中跟踪所有的T-SQL语句。
如果你想查看所有执行的T-SQL语句,你需要使用SQL Server的Profiler 工具,关于具体如何使用SQL Server Profiler工具,请参考如下文章:
SQL Profiler: Features, functions and setup in SQL Server 2005
2. ToTraceString 方法
另外一种方法去查看生成的T-SQL语句的方法,包括 EntityCommand和ObjectQuery类都有一个ToTraceString() 方法。在一些情况下,可以用来查看内部到底生成什么SQL脚本,而不必一定要使用SQL Server Profiler 工具。需要注意的是:ToTraceString() 方法实际上没有执行查询操作,仅仅是转化查询为SQL脚本。
通过增加一个断点,你可以轻松查看SQL脚本,需要记住的是:事先需要打开数据库连接,否则会抛出InvalidOperationException 异常(Execution of the command requires an open and available connection. The connection’s current state is closed.)
展开阅读全文