Entity Framework 6 提供程序模型

借助 Entity Framework 提供程序模型,Entity Framework 可用于不同类型的数据库服务器。 例如,可插入一个提供程序以允许 EF 用于 Microsoft SQL Server,同时再插入另一个提供程序以允许 EF 用于 Microsoft SQL Server Compact Edition。 我们了解的 EF6 提供程序可以在 Entity Framework 提供程序页面上找到。

EF 与提供程序交互的方式需要进行某些更改,以允许 EF 在开放源代码许可证下发布。 这些更改需要针对 EF6 程序集以及用于注册提供程序的新机制重新生成 EF 提供程序。

重新生成

使用 EF6 时,以前属于 .NET Framework 的核心代码现在作为带外 (OOB) 程序集提供。 有关如何针对 EF6 生成应用程序的详细信息,请参阅更新 EF6 应用程序页面。 还需要使用这些说明重新生成提供程序。

提供程序类型概述

EF 提供程序实际上是由 CLR 类型定义的提供程序特定服务的集合,CLR 类型从这些服务扩展(基类)或实现(接口)。 其中两项服务是 EF 正常运行的基础和必要服务。 其他服务是可选的,且仅当需要特定功能和/或这些服务的默认实现不适用于目标特定数据库服务器时,才需要实现。

基本提供程序类型

DbProviderFactory

EF 依赖于从 System.Data.Common.DbProviderFactory 派生的类型来执行所有低级数据库访问。 DbProviderFactory 实际上不是 EF 的一部分,而是 .NET Framework 中的一个类,该类为 ADO.NET 提供程序提供入口点,EF、其他 O/RM 或应用程序可直接使用此类提供程序以与提供程序无关的方式获取连接、命令、参数和其他 ADO.NET 抽象的实例。 有关 DbProviderFactory 的详细信息,可查看 ADO.NET 的 MSDN 文档

DbProviderServices

EF 依赖于从 DbProviderServices 派生的类型,用于在 ADO.NET 提供程序已经提供的功能之上提供 EF 所需的附加功能。 在旧版本的 EF 中,DbProviderServices 类是 .NET Framework 的一部分,位于 System.Data.Common 命名空间中。 从 EF6 开始,此类现在是 EntityFramework.dll 的一部分,并且位于 System.Data.Entity.Core.Common 命名空间中。

有关 DbProviderServices 实现的基本功能的更多详细信息,请参阅 MSDN。 但是,请注意,截至撰写本文时,此信息尚未针对 EF6 进行更新,尽管大多数概念仍然有效。 DbProviderServices 的 SQL Server 和 SQL Server Compact 实现也已签入开放源代码库,并可作为其他实现的有用参考。

在旧版本的 EF 中,要使用的 DbProviderServices 实现是直接从 ADO.NET 提供程序获得的。 此操作是通过将 DbProviderFactory 转换为 IServiceProvider 并调用 GetService 方法来完成的。 这会将 EF 提供程序紧密耦合到 DbProviderFactory。 这种耦合阻止 EF 从 .NET Framework 中移出,因此对于 EF6,这种紧密耦合已被删除,DbProviderServices 的实现现在直接在应用程序的配置文件或基于代码的配置中注册,下面的“注册 DbProviderServices 部分”有更为详细的描述

其他服务

除了上述基本服务,EF 还使用许多始终或有时特定于提供程序的其他服务。 这些服务的默认提供程序特定实现可通过 DbProviderServices 实现提供。 应用程序还可以替代这些服务的实现,或在 DbProviderServices 类型不提供默认值时提供实现。 下面的“解析其他服务”部分有更为详细的描述

下面列出了提供程序可能感兴趣的其他服务类型。 有关上述每种服务类型的更多详细信息,请参阅“API 文档”。

IDbExecutionStrategy

这是一项可选服务,让提供程序可以在对数据库执行查询和命令时实现重试或其他行为。 如果未提供实现,EF 将只执行命令并传播引发的任何异常。 对于 SQL Server,此服务用于提供重试策略,这在针对基于云的数据库服务器(如 SQL Azure)运行时特别有用。

IDbConnectionFactory

这是一项可选服务,当仅给定数据库名称时,让提供程序可以按照约定创建 DbConnection 对象。 请注意,虽然 DbProviderServices 实现可以解析此服务,但它自 EF 4.1 以来一直存在,也可以在配置文件或代码中显式设置。 仅当提供程序已注册为默认提供程序(请参阅下面的默认提供程序)并且没有在其他地方设置默认连接工厂时,该提供程序才可以解析此服务

DbSpatialServices

这是一项可选服务,让提供程序可以添加对地理和几何空间类型的支持。 必须提供此服务的实现,应用程序才能将 EF 与空间类型一同使用。 DbSpatialServices 以两种方式请求。 首先,使用 DbProviderInfo 对象(包含固定名称和清单令牌)作为密钥来请求特定于提供程序的空间服务。 其次,可以在没有键的情况下请求 DbSpatialServices。 这用于解析创建独立 DbGeography 或 DbGeometry 类型时所使用的“全局空间提供程序”。

MigrationSqlGenerator

这是一项可选服务,让 EF 迁移可用于生成 SQL,这些 SQL 用于通过 Code First 创建和修改数据库架构。 需要提供此服务的实现才能支持迁移。 如果提供了实现,则当使用数据库初始值设置项或 Database.Create 方法创建数据库时,也将使用它。

Func<DbConnection, string, HistoryContextFactory>

这是一项可选服务,让提供程序可以配置 HistoryContext 到 EF 迁移使用的 __MigrationHistory 表的映射。 HistoryContext 是 Code First DbContext,可使用普通的 Fluent API 进行配置,以更改表名称和列映射规范等内容。 如果该提供程序支持所有默认表和列映射,则 EF 为所有提供程序返回的此服务的默认实现可能适用于给定的数据库服务器。 在这种情况下,提供程序不需要提供此服务的实现。

IDbProviderFactoryResolver

这是一项可选服务,用于从给定的 DbConnection 对象获取正确的 DbProviderFactory。 EF 针对所有提供程序返回的此服务的默认实现适用于所有提供程序。 但是,在 .NET 4 上运行时,DbProviderFactory 不能从其 DbConnections 公开访问。 因此,EF 使用一些启发式方法来搜索已注册的提供程序以查找匹配项。 对于某些提供程序,这些启发式方法可能会失败,在这种情况下,提供程序应提供新的实现。

注册 DbProviderServices

要使用的 DbProviderServices 实现可以在应用程序的配置文件(app.config 或 web.config)中进行注册,也可以使用基于代码的配置进行注册。 在任一情况下,注册都使用提供程序的“固定名称”作为密钥。 因此可在单个应用程序中注册和使用多个提供程序。 用于 EF 注册的固定名称与用于 ADO.NET 提供程序注册和连接字符串的固定名称相同。 例如,对于 SQL Server,使用固定名称“System.Data.SqlClient”。

配置文件注册

要使用的 DbProviderServices 类型在应用程序配置文件的 entityFramework 部分的提供程序列表中注册为提供程序元素。 例如:

<entityFramework>
  <providers>
    <provider invariantName="My.Invariant.Name" type="MyProvider.MyProviderServices, MyAssembly" />
  </providers>
</entityFramework>

type 字符串必须是要使用的 DbProviderServices 实现的程序集限定类型名称

基于代码的注册

从 EF6 开始,也可使用代码注册提供程序。 这样,无需更改应用程序的配置文件,即可使用 EF 提供程序。 若要使用基于代码的配置,应用程序应创建 DbConfiguration 类,如基于代码的配置文档中所述。 然后,DbConfiguration 类的构造函数应调用 SetProviderServices 来注册 EF 提供程序。 例如:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetProviderServices("My.New.Provider", new MyProviderServices());
    }
}

解析其他服务

正如上面提供程序类型概述部分所述,DbProviderServices 类还可用于解析其他服务。 这是可能的,因为 DbProviderServices 实现 IDbDependencyResolver,并且每个已注册的 DbProviderServices 类型都添加为“默认解析程序”。 IDbDependencyResolver 机制在依赖项解析中有更详细的描述。 但解析提供程序中的其他服务并不需要了解此规范中的所有概念。

提供程序解析其他服务的最常见方式是,为 DbProviderServices 类的构造函数中的每个服务调用 DbProviderServices.AddDependencyResolver。 例如,SqlProviderServices(SQL Server 的 EF 提供程序)具有与此类似的初始化代码:

private SqlProviderServices()
{
    AddDependencyResolver(new SingletonDependencyResolver<IDbConnectionFactory>(
        new SqlConnectionFactory()));

    AddDependencyResolver(new ExecutionStrategyResolver<DefaultSqlExecutionStrategy>(
        "System.data.SqlClient", null, () => new DefaultSqlExecutionStrategy()));

    AddDependencyResolver(new SingletonDependencyResolver<Func<MigrationSqlGenerator>>(
        () => new SqlServerMigrationSqlGenerator(), "System.data.SqlClient"));

    AddDependencyResolver(new SingletonDependencyResolver<DbSpatialServices>(
        SqlSpatialServices.Instance,
        k =>
        {
            var asSpatialKey = k as DbProviderInfo;
            return asSpatialKey == null
                || asSpatialKey.ProviderInvariantName == ProviderInvariantName;
        }));
}

此构造函数使用下列帮助程序类:

  • SingletonDependencyResolver:提供了一种简单的方式来解析单一实例服务,即每次调用 GetService 时都返回相同实例的服务。 暂时性服务通常注册为单一实例工厂,用于按需创建暂时性实例。
  • ExecutionStrategyResolver:特定于返回 IExecutionStrategy 实现的解析程序。

除了使用 DbProviderServices.AddDependencyResolver, 还可替代 DbProviderServices.GetService 并直接解析其他服务。 当 EF 需要由特定类型定义的服务时(在某些情况下,针对给定密钥),将调用此方法。 该方法应返回服务(如果可以)或返回 null 以选择退出返回服务,并改为允许其他类对其进行解析。 例如,若要解析默认连接工厂,GetService 中的代码可能如下所示:

public override object GetService(Type type, object key)
{
    if (type == typeof(IDbConnectionFactory))
    {
        return new SqlConnectionFactory();
    }
    return null;
}

注册顺序

当多个 DbProviderServices 实现在应用程序的配置文件中注册时,它们将按照列出的顺序添加为辅助解析程序。 由于解析程序始终添加到辅助解析程序链的顶部,这意味着,列表末尾的提供程序将可以在其他提供程序之前解析依赖项。 (乍一看这似乎有点违反直觉,但如果看作将每个提供程序从列表中删除并将其堆叠在现有提供程序的顶部,便说得通了。)

此顺序通常并不重要,因为大多数提供程序服务都是特定于提供程序的,并使用提供程序固定名称进行键控。 但是,对于不是由提供程序固定名称或其他特定于提供程序的密钥进行键控的服务,将根据此顺序解析服务。 例如,如果未在其他任何位置以不同方式显式设置,则默认连接工厂将来自链中最顶层的提供程序。

其他配置文件注册

可以直接在应用程序的配置文件中显式注册上述部分所述的其他提供程序服务。 完成此操作后,将使用配置文件中的注册,而不是 DbProviderServices 实现的 GetService 方法返回的任何内容。

注册默认的连接工厂

从 EF5 开始,EntityFramework NuGet 包自动在配置文件中注册 SQL Express 连接工厂或 LocalDb 连接工厂。

例如:

<entityFramework>
  <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" >
</entityFramework>

type 是默认连接工厂的程序集限定类型名称,它必须实现 IDbConnectionFactory

建议在安装时,提供程序 NuGet 包以这种方式设置默认连接工厂。 请参阅下面的“提供程序的 NuGet 包”

其他 EF6 提供程序更改

空间提供程序更改

支持空间类型的提供程序现在必须在派生自 DbSpatialDataReader 的类上实现一些附加方法:

  • public abstract bool IsGeographyColumn(int ordinal)
  • public abstract bool IsGeometryColumn(int ordinal)

此外,还有一些现有方法的新异步版本,建议将其替代为默认实现委托给同步方法,这样便不会以异步方式执行:

  • public virtual Task<DbGeography> GetGeographyAsync(int ordinal, CancellationToken cancellationToken)
  • public virtual Task<DbGeometry> GetGeometryAsync(int ordinal, CancellationToken cancellationToken)

Enumerable.Contains 的本机支持

EF6 引入了一种新的表达式类型 DbInExpression,添加该类型是为了解决在 LINQ 查询中使用 Enumerable.Contains 的性能问题。 DbProviderManifest 类具有新的虚拟方法 SupportsInExpression,该方法由 EF 调用来确定提供程序是否处理新的表达式类型。 为了与现有提供程序实现兼容,此方法返回 false。 若要从此改进中获益,EF6 提供程序可添加代码来处理 DbInExpression,并替代 SupportsInExpression 以返回 true。 可以通过调用 DbExpressionBuilder.In 方法来创建 DbInExpression 的实例。 DbInExpression 实例由 DbExpression 和 DbConstantExpression 列表组成,前者通常表示表列,后者用于测试匹配项。

提供程序的 NuGet 包

使 EF6 提供程序可用的一种方法是将其作为 NuGet 包发布。 使用 NuGet 包具有以下优势:

  • 可以轻松使用 NuGet 将提供程序注册添加到应用程序的配置文件
  • 可以对配置文件进行其他更改以设置默认连接工厂,以便按约定建立的连接将使用已注册的提供程序
  • NuGet 处理添加绑定重定向的操作,以便 EF6 提供程序即使在新的 EF 包发布后仍可继续工作

这方面的一个例子是包含在开源代码库中的 EntityFramework.SqlServerCompact 包。 此包提供了一个用于创建 EF 提供程序 NuGet 包的良好模板。

PowerShell 命令

安装 EntityFramework NuGet 软件包时,会注册一个包含两个命令的 PowerShell 模块,这些命令对于提供程序包非常有用:

  • Add-EFProvider 在目标项目的配置文件中为提供程序添加新实体,并确保其位于已注册提供程序列表的末尾。
  • Add-EFDefaultConnectionFactory 在目标项目的配置文件中添加或更新 defaultConnectionFactory 注册。

这两个命令都负责将 entityFramework 部分添加到配置文件中,并在必要时添加提供程序集合。

这样做是为了从 install.ps1 NuGet 脚本中调用这些命令。 例如,SQL Compact 提供程序的 install.ps1 如下所示:

param($installPath, $toolsPath, $package, $project)
Add-EFDefaultConnectionFactory $project 'System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework' -ConstructorArguments 'System.Data.SqlServerCe.4.0'
Add-EFProvider $project 'System.Data.SqlServerCe.4.0' 'System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact'</pre>

可通过使用包管理器控制台窗口中的 get-help 获取有关这些命令的详细信息。

包装提供程序

包装提供程序是 EF 和/或 ADO.NET 提供程序,它包装现有提供程序以使用其他功能(如分析或跟踪功能)对其进行扩展。 包装提供程序可通过正常方式进行注册,但更方便的一种方式是,在运行时通过拦截与提供程序相关的服务的解析来设置包装提供程序。 DbConfiguration 类上的静态事件 OnLockingConfiguration 可用于执行此操作。

OnLockingConfiguration 事件的调用时间发生在 EF 已确定从其获取应用域的所有 EF 配置之后,但在锁定以供使用之前。 在应用启动时(在使用 EF 之前),应用应为此事件注册事件处理程序。 (我们正在考虑在配置文件中添加对注册此处理程序的支持,但目前尚不支持。)然后,事件处理程序应为需要包装的每个服务调用 ReplaceService。

例如,若要包装 IDbConnectionFactory 和 DbProviderService,应注册类似于以下内容的处理程序:

DbConfiguration.OnLockingConfiguration +=
    (_, a) =>
    {
        a.ReplaceService<DbProviderServices>(
            (s, k) => new MyWrappedProviderServices(s));

        a.ReplaceService<IDbConnectionFactory>(
            (s, k) => new MyWrappedConnectionFactory(s));
    };

已解析的服务现在应与用于解析服务的密钥一起打包到处理程序。 然后,处理程序可以包装此服务,并用包装后的版本替换返回的服务。

使用 EF 解析 DbProviderFactory

DbProviderFactory 是 EF 所需的一种基本提供程序类型,如上面的“提供程序类型概述”部分所述。 如前所述,它不是 EF 类型,注册通常不是 EF 配置的一部分,而是 machine.config 文件和/或应用程序的配置文件中的正常的 ADO.NET 提供程序注册。

尽管如此,EF 在寻找要使用的 DbProviderFactory 时仍然使用其正常的依赖项解析机制。 默认解析程序使用配置文件中的正常的 ADO.NET 注册,因此这通常是透明的。 但由于使用了正常的依赖项解析机制,这意味着即使没有完成正常的 ADO.NET 注册,也可使用 IDbDependencyResolver 来解析 DbProviderFactory。

以这种方式解析 DbProviderFactory 有几个含义:

  • 使用基于代码的配置的应用程序可以在其 DbConfiguration 类中添加调用来注册相应的 DbProviderFactory。 这对于不想(或无法)使用任何基于文件的配置的应用程序特别有用。
  • 该服务可使用 ReplaceService 进行包装或替换,如上面的“包装提供程序”部分所述
  • 理论上,DbProviderServices 实现可以解析 DbProviderFactory。

执行这些操作时需要注意的重要一点是,它们只会影响 EF 对 DbProviderFactory 的查找。 其他非 EF 代码可能仍会要求 ADO.NET 提供程序以正常方式注册,如果找不到注册,则可能会失败。 出于此原因,通常最好以正常的 ADO.NET 方式注册 DbProviderFactory。

如果 EF 用于解析 DbProviderFactory,那么它还应解析 IProviderInvariantName 和 IDbProviderFactoryResolver 服务。

IProviderInvariantName 是一种服务,用于确定给定类型 DbProviderFactory 的提供程序固定名称。 此服务的默认实现使用 ADO.NET 提供程序注册。 这意味着,如果 ADO.NET 提供程序没有以正常方式注册(因为 EF 正在解析 DbProviderFactory),则还需要解析此服务。 请注意,使用 DbConfiguration.SetProviderFactory 方法时,会自动添加此服务的解析程序。

如上面的“提供程序类型概述”部分所述,IDbProviderFactoryResolver 用于从给定的 DbConnection 对象获取正确的 DbProviderFactory。 在 .NET 4 上运行时,此服务的默认实现使用 ADO.NET 提供程序注册。 这意味着,如果 ADO.NET 提供程序没有以正常方式注册(因为 EF 正在解析 DbProviderFactory),则还需要解析此服务。