Entity SQL (eSQL) 教程

Entity SQL (eSQL) 是一种针对实体框架设计的查询语言,它类似于传统的 SQL,但专门用于查询概念模型(EDM - Entity Data Model),而不是直接查询数据库表。

entity sql 教程
(图片来源网络,侵删)

目录

  1. 什么是 Entity SQL?
  2. eSQL vs. 传统 SQL vs. LINQ to Entities
  3. 核心概念
    • 概念模型
    • EDM (Entity Data Model)
    • ENTITY 关键字
  4. 基本语法
    • SELECT 子句
    • FROM 子句
    • WHERE 子句
    • GROUP BYHAVING 子句
  5. eSQL 特有的高级功能
    • 导航属性
    • 集合操作
    • 行构造函数
    • TREAT 操作符
    • OfType 函数
  6. 如何执行 eSQL 查询
    • 使用 EntityCommandEntityDataReader
    • 使用 ObjectQuery<T> (更推荐的方式)
  7. 一个完整的示例
  8. eSQL 的优缺点与未来

什么是 Entity SQL?

Entity SQL 是一种面向对象的查询语言,它操作的是实体和它们之间的关系,而不是数据库的表和列,当你使用 eSQL 时,你是在与概念模型进行交互,而不是底层的数据库结构。

核心思想: 将查询逻辑与特定的数据库实现(如 SQL Server, Oracle)和物理数据结构(表、列)分离开来。


eSQL vs. 传统 SQL vs. LINQ to Entities

特性 传统 SQL Entity SQL (eSQL) LINQ to Entities
语言类型 声明式字符串 声明式字符串 强类型语言集成查询
编译时检查 无,运行时才报错 无,运行时才报错 ,编译时检查语法和类型
IntelliSense 不支持 不支持 支持
参数化 使用 @param 语法 使用 @param 语法 使用强类型参数
主要用途 直接查询数据库 动态构建查询、复杂查询 应用程序中所有数据查询的首选
灵活性 高,但易出错 高,适合动态查询 高,但灵活性略低于字符串拼接
推荐度 (EF中) 不推荐 较少使用,特定场景 强烈推荐

在 Entity Framework 中,LINQ to Entities 是首选,因为它提供了编译时安全、类型检查和更好的开发体验,eSQL 主要在需要动态构建查询的场景下才有用武之地。


核心概念

概念模型

eSQL 查询的基础是概念模型,它是一个 .edmx 文件,描述了你的实体(如 Person, Order)、它们的属性(如 Person.Name, Order.Date)以及它们之间的关系(如 Person.Orders)。

entity sql 教程
(图片来源网络,侵删)

EDM (Entity Data Model)

.edmx 文件包含了三个部分:

  1. CSDL (Conceptual Schema Definition Language): 定义了实体、属性和关系,这是 eSQL 直接操作的部分。
  2. SSDL (Storage Schema Definition Language): 定义了数据库表、列和关系。
  3. MSL (Mapping Schema Language): 定义了 CSDL 和 SSDL 之间的映射关系。

ENTITY 关键字

在 eSQL 中,当你引用一个实体时,通常需要使用 ENTITY 关键字来明确表示你正在操作的是概念模型中的实体,而不是数据库对象。

查询一个名为 Person 的实体:

-- eSQL
SELECT VALUE p FROM Entities.Person AS p

Entities 是 CSDL 中定义的命名空间。

entity sql 教程
(图片来源网络,侵删)

基本语法

eSQL 的基本语法与 SQL 非常相似,但有一些关键区别。

SELECT 子句

使用 SELECT VALUE 来指定查询返回的结果。VALUE 关键字表示结果集合中的每个元素就是一个实体或标量值,而不是一个匿名的记录包装器。

-- 返回所有 Person 实体
SELECT VALUE p FROM Entities.Person AS p
-- 返回 Person 的 Name 属性(标量值)
SELECT p.Name FROM Entities.Person AS p

FROM 子句

指定查询的数据源,数据源是概念模型中的实体集合。

SELECT VALUE p FROM Entities.Person AS p

AS p 是一个可选的别名,方便在后续子句中引用。

WHERE 子句

用于过滤结果,语法与 SQL 类似。

SELECT VALUE p FROM Entities.Person AS p
WHERE p.Age > 30 AND p.Name LIKE 'John%'

GROUP BYHAVING 子句

与 SQL 类似,用于聚合和分组。

SELECT p.City, COUNT(p) AS PersonCount
FROM Entities.Person AS p
GROUP BY p.City
HAVING COUNT(p) > 10

eSQL 特有的高级功能

eSQL 的强大之处在于它提供了许多传统 SQL 所不具备的、面向对象的特性。

导航属性

这是 eSQL 的核心优势,你可以直接通过导航属性访问关联实体,而无需 JOIN

假设 PersonOrder 是一对多关系,Person 有一个 Orders 导航属性。

-- 查找所有订单数量超过5的客户的姓名
SELECT VALUE p.Name
FROM Entities.Person AS p
WHERE p.Orders.Count > 5

这相当于 SQL 中的 JOINGROUP BY,但 eSQL 的语法更简洁直观。

集合操作

eSQL 支持 UNION, INTERSECT, EXCEPT

-- 合并两个查询结果,并去除重复项
SELECT VALUE p FROM Entities.Person AS p WHERE p.City = 'London'
UNION
SELECT VALUE p FROM Entities.Person AS p WHERE p.City = 'Paris'

行构造函数

使用 ROW 关键字可以创建包含多个属性的新对象,非常适合 SELECT 子句返回匿名类型。

SELECT ROW(p.Name AS FullName, p.Age) FROM Entities.Person AS p

这将返回一个包含 FullNameAge 属性的对象集合。

TREAT 操作符

TREAT 用于将一个实体视为其派生类型,当你有一个基类实体(如 Person)和一些派生类(如 Employee, Customer)时,TREAT 非常有用。

假设 Employee 继承自 Person

-- 查询所有员工,并访问其特有的 'Salary' 属性
SELECT VALUE e.Salary
FROM TREAT(Entities.Person AS Employee) AS e
WHERE e IS OF (Employee)

e IS OF (Employee) 确保我们只处理 Employee 类型的实例。

OfType 函数

OfTypeTREAT 的一个更简洁的替代品,用于从集合中筛选特定类型的实体。

-- 查询所有员工
SELECT VALUE e.Salary
FROM Entities.Person.OfType(Employee) AS e

如何执行 eSQL 查询

在代码中执行 eSQL 查询主要有两种方式。

使用 EntityCommandEntityDataReader (底层方式)

这种方式类似于传统的 SqlCommandSqlDataReader

using (var context = new MyDbContext())
{
    string eSqlQuery = "SELECT VALUE p FROM Entities.Person AS p WHERE p.Name = @name";
    using (EntityCommand cmd = context.Connection.CreateCommand())
    {
        cmd.CommandText = eSqlQuery;
        cmd.Parameters.Add(new EntityParameter("name", DbType.String) { Value = "Alice" });
        using (EntityDataReader reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                // 注意:这里需要手动将数据映射到对象
                string name = reader["Name"].ToString();
                int age = Convert.ToInt32(reader["Age"]);
                Console.WriteLine($"Name: {name}, Age: {age}");
            }
        }
    }
}

使用 ObjectQuery<T> (推荐方式)

ObjectQuery<T> 是一个更高级、更方便的类,它可以直接执行 eSQL 查询并自动将结果映射到指定的实体类型 T

using (var context = new MyDbContext())
{
    string eSqlQuery = "SELECT VALUE p FROM Entities.Person AS p WHERE p.Name = @name";
    // 创建 ObjectQuery
    ObjectQuery<Person> query = new ObjectQuery<Person>(eSqlQuery, context);
    // 添加参数
    query.Parameters.Add(new ObjectParameter("name", "Alice"));
    // 执行查询并获取结果集合
    List<Person> people = query.ToList();
    foreach (var person in people)
    {
        // 结果已经是 Person 对象,无需手动映射
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

一个完整的示例

假设我们有一个简单的模型:PersonOrder

定义模型 (CSDL 部分)

<EntityType Name="Person">
  <Key><PropertyRef Name="PersonId"/></Key>
  <Property Name="PersonId" Type="Int32" Nullable="false"/>
  <Property Name="Name" Type="String" MaxLength="50" Nullable="false"/>
  <NavigationProperty Name="Orders" Relationship="MyModel.FK_Order_Person" FromRole="Person" ToRole="Order" Multiplicity="*"/>
</EntityType>
<EntityType Name="Order">
  <Key><PropertyRef Name="OrderId"/></Key>
  <Property Name="OrderId" Type="Int32" Nullable="false"/>
  <Property Name="OrderDate" Type="DateTime" Nullable="false"/>
  <NavigationProperty Name="Person" Relationship="MyModel.FK_Order_Person" FromRole="Order" ToRole="Person" Multiplicity="1"/>
</EntityType>

执行 eSQL 查询 (C#)

// 假设 MyDbContext 已正确配置
using (var context = new MyDbContext())
{
    // 查询:找到所有下过订单的客户姓名和订单数量
    string eSqlQuery = @"
        SELECT 
            p.Name AS CustomerName, 
            p.Orders.Count AS OrderCount
        FROM Entities.Person AS p
        WHERE p.Orders.Count > 0";
    try
    {
        ObjectQuery<DbDataRecord> query = new ObjectQuery<DbDataRecord>(eSqlQuery, context);
        foreach (DbDataRecord record in query)
        {
            string customerName = record["CustomerName"].ToString();
            int orderCount = Convert.ToInt32(record["OrderCount"]);
            Console.WriteLine($"Customer: {customerName}, Orders: {orderCount}");
        }
    }
    catch (EntityException ex)
    {
        Console.WriteLine("eSQL Query Error: " + ex.Message);
    }
}

这个示例展示了如何使用导航属性 p.Orders.Count 来轻松获取关联实体的数量,而无需编写复杂的 JOIN


eSQL 的优缺点与未来

优点

  1. 与数据库无关:查询基于概念模型,更换数据库后(理论上)无需修改查询。
  2. 强大的面向对象特性:如导航属性、TREATOfType 等,使查询更符合对象模型。
  3. 适合动态查询:因为它是字符串,可以在运行时动态构建复杂的查询逻辑。

缺点

  1. 无编译时检查:eSQL 是字符串,如果写错了属性名或语法,只有在运行时才会报错。
  2. 缺乏 IntelliSense:在编写查询时,IDE 无法提供智能提示,容易出错。
  3. 学习曲线:对于习惯了 LINQ 的开发者来说,eSQL 的字符串形式显得笨重。
  4. 已被边缘化:微软官方推荐使用 LINQ to Entities,eSQL 的文档和支持都较少。

未来

eSQL 已经不是一个主流技术,在 Entity Framework 6 和 Entity Framework Core 中,LINQ to Entities 是绝对的主角,除非你有非常特殊的需求(需要从外部来源动态生成极其复杂的查询),否则应该优先选择 LINQ。


特性
是什么 一种面向概念模型的、类 SQL 的查询语言。
核心优势 导航属性、与数据库无关、适合动态查询。
主要劣势 无编译时检查、无 IntelliSense、易出错。
与 LINQ 的关系 LINQ to Entities 是 EF 的首选,eSQL 是一个补充,用于特定场景。
何时使用 当你需要在运行时动态构建无法用 LINQ 表示的复杂查询时。
学习建议 先精通 LINQ to Entities,再了解 eSQL 作为补充知识。

希望这份教程能帮助你理解 Entity SQL!