使用依赖注入器“Ninject”动态选择 ORM(Entity Framework 或 Dapper)和数据库(SQL Server 或 Oracle Database)






4.96/5 (22投票s)
其思想是使用依赖注入器 (DI) 框架 Ninject,通过全局设置动态地将我们的应用程序定位到 SQL Server 或 Oracle 数据库,使用 Entity framework (EF) 或 Dapper ORM。
想法
该想法是使用依赖注入(DI)框架 Ninject,通过使用 Entity framework (EF) 或 Dapper ORM,动态地将我们的应用程序指向 SQL Server 或 Oracle 数据库。
摘要
在本文中,我们将创建一个简单的 Asp.Net MVC 应用程序,该应用程序将与数据访问层交互以执行基本 CRUD 操作。主要重点将放在设计数据访问层,以便动态地将我们的应用程序指向 SQL Server 或 Oracle。另一个有趣之处在于选择 Entity Framework (EF) 或 Dapper 作为我们的底层对象关系映射 (ORM)。最棒的是,所有这些都通过简单的标志切换动态实现。
在深入了解细节之前,让我们快速回顾一下 SOLID 原则和依赖倒置原则 (DIP)
关于 SOLID 原则和 DIP 的快速说明
SOLID 原则
- 单一职责原则 (SRP):一个类应该只有一个职责(即,软件规范中的任何一个潜在更改都可能影响该类的规范)。
- 开闭原则 (OCP):“软件实体……应该对扩展开放,但对修改关闭。”
- 里氏替换原则 (LSP):“程序中的对象应该可以用其子类型的实例替换,而不会改变程序的正确性。”
- 接口隔离原则 (ISP):“许多客户端特定的接口比一个通用的接口更好。”
- 依赖倒置原则 (DIP):一个人应该“依赖于抽象。不要依赖于具体实现。”
依赖倒置原则
- 高层模块不应依赖于低层模块。两者都应依赖于抽象。
- 抽象不应依赖于细节。细节应依赖于抽象。
依赖倒置指出,项目中从高层模块到低层依赖模块的常规依赖关系被反转,从而使高层模块独立于低层模块的实现细节。
进程
- 创建实体数据模型层,使用 Entity Data Model Code-First 方法来容纳 Context 和 Entities。
- 创建一个遵循 Unity 和 Dependency Inversion 设计模式的数据访问层,该层允许我们在 #3 中配置 Ninject。
- 创建一个 UI 层,用于导入和配置 Ninject 的全局设置,以便注入抽象实现并执行基本 CRUD 操作。
创建实体数据模型层,使用 Entity Data Model Code-First 方法来容纳 Context 和 Entities。
首先创建一个名为“EntityModel”的“类库”项目,解决方案名称为“DI”。
从项目中删除 Visual Studio 提供的默认类。然后,在我们的项目中添加一个名为“DomainModel”的新文件夹。接着添加名为“DiContext”的新项“ADO.Net Entity Data Model”,它充当 UI 和数据库之间来回传递数据的媒介。
在“Entity Data Model Wizard”窗口中,选择“Empty Code First Model”并单击“Finish”。
现在,注释掉默认构造函数,并添加另一个接受连接字符串作为参数的构造函数。
public DiContext(string conString)
: base(conString)
{
Database.SetInitializer<DiContext>(null);
}
虽然以下方法不是必需的,但我们将覆盖 DbContext 中的“OnModelCreating”方法。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
现在我们的“DiContext”已准备就绪,我们可以将 POCO/模型类作为单独的文件添加到“DomainModel”文件夹中,并在 context 类中添加它们的引用(DbSet)。在此示例中,我们处理一个名为“Company”的表。向我们的 DomainModel 文件夹添加新类“Company.cs”。数据库表结构如下:
GO
CREATE TABLE [dbo].[Company](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NOT NULL,
[Description] [nvarchar](500) NULL,
[CreatedBy] [int] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[UpdatedDate] [datetime] NULL,
[Status] [int] NOT NULL,
CONSTRAINT [PK_Company] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
现在使用以下语句将 Company 添加到我们的 DiContext 中作为 DbSet,以从我们的 context 访问数据库表。
public virtual DbSet<Company> Company { get; set; }
这样,我们的“EntityModel”项目中的“Company”实体就准备好了。接下来,我们将创建具有数据库和 ORM 特定通用抽象和存储库类的数据访问层。
创建一个遵循 Unity 和 Dependency Inversion 设计模式的数据访问层,该模式允许我们在 #3 中配置 Ninject。
向我们的解决方案添加另一个名为“DataAccess”的类库项目,并删除生成的默认类文件。
创建三个新文件夹“Interface”、“Abstract”和“Repository”来存放相应的文件和类。现在我们的解决方案将如下所示:
我们的 Interface、Generic Abstract 和 Repository 类文件的关系应该是这样的……
在 Interface 文件夹下创建一个新的接口“IRepositoryBase.cs”,它实现 IDisposable 接口。
public interface IRepositoryBase : IDisposable
{
bool CanDispose { get; set; }
void Dispose(bool force);
}
现在,我们将创建通用的抽象存储库基类“EfRepositoryBase.cs”,该类特定于 Entity Framework,并实现 IRepositoryBase 接口。
添加 IRepository 基接口所需的 using 语句。
注意:为了使用 context 类,我们需要在“DataAccess”项目中添加对 Entity Framework 的引用。右键单击 DataAccess 项目中的“References”,然后选择“Manage Nuget Packages…”,然后安装 Entity Framework 包。还要添加对“EntityModel”库到 DataAccess 项目的引用,以使用 DiContext。
另外,请注意没有默认构造函数。我们将通过构造函数参数注入 context。如果 Ninject 未正确配置,并且我们在没有默认构造函数的情况下使用此类,将会引发错误消息。
现在,在 Abstract 文件夹中创建另一个通用的抽象基存储库“DapperRepositoryBase.cs”,该存储库特定于 Dapper,并且也实现 IRepositoryBase 接口。用以下代码替换新类。
在添加了 Interface 的有效命名空间后,前往 NuGet 安装“Dapper dot net”ORM 包。
对 Dapper 使用以下命名空间:
using Dapper;
现在是时候在“Interface”文件夹中创建我们的模块特定接口,然后在“Repository”文件夹中实现它了。
在“Interface”文件夹中添加新的接口“ICompanyRepository.cs”,它实现 IRepositoryBase 接口。
public interface ICompanyRepository : IRepositoryBase
{
List<Company> GetCompanies();
Company GetCompanyById(int id);
int InsertCompany(Company company);
int UpdateCompany(Company company);
bool DeleteCompany(int id);
}
添加 using 语句以引用“EntityModel.DomainModel”。
using EntityModel.DomainModel;
让我们开始为接口 ICompanyRepository 实现具体的行为,方法是创建 ORM 和数据库特定的存储库。首先,我们将创建文件夹结构,然后添加存储库类。在“Repository”文件夹中创建两个文件夹“Dapper”和“EF”,用于 ORM 特定。然后,在“Dapper”文件夹中创建两个新文件夹“MSSQL”和“ORA”,分别对应相应的数据库。
请注意,我们只有一个 Entity Framework 的存储库。原因在于,我们可以使用相同的存储库配合 Linq 表达式来指向任何数据库。
EF Repository
让我们首先实现 EF 存储库“CompanyEfRepository”。创建一个实现抽象类 EfRepositoryBase<Company> 和 ICompanyRepository 接口的新类。由于我们正在实现 ICompanyRepository,因此我们需要实现所有在基接口中声明的方法。
注意:请检查是否有单个带 context 参数的构造函数,并且没有默认构造函数。出于明显的原因,context 将被动态注入。
ORM Specific Repository
现在,我们将创建存储库,这些存储库位于 Dapper ORM 下,用于 SQL Server 数据库。在“MSSQL”文件夹中创建一个名为“CompanySqlRepository.cs”的新类,它应该实现 DapperRepositoryBase 和 ICompanyRepository。
注意:我们需要创建一个接受打开的连接接口 IDbConnection 和连接字符串的两个参数的构造函数。构造函数如下:
public CompanySqlRepository(IDbConnection conn, string conString)
{
_connection = conn;
_connection.ConnectionString = conString;
}
应用以下代码使用 Dapper 为 SQL Server 实现 CRUD 操作。
使用以下 SQL 脚本创建所需的表和存储过程。
现在,我们将创建另一个存储库“CompanyOraRepository.cs”,位于 Dapper/ORA 文件夹中,用于 Oracle 客户端,与 SQL 存储库相同,具有两个参数的构造函数。添加以下代码来实现 CRUD 操作。
注意:由于我无法访问 Oracle 客户端来测试实现,因此我将其设置为抛出 NotImplementedException()。
现在,我们添加完所有必需的接口、抽象类和存储库文件后,我们的文件夹结构如下所示:
构建整个解决方案一次。现在应该没有构建错误了。完美 :)
现在,接口、Unity 抽象类和存储库都已设置好,我们将继续创建 MVC 项目“UI”来配置和利用注入机制。
创建一个 UI 层,用于导入和配置 Ninject 的全局设置,以便注入抽象实现以执行基本 CRUD 操作。
在同一个解决方案中创建一个新的 Asp.Net Web Application。
从模板中选择“MVC”。
将身份验证更改为“No Authentication”,因为本文的重点不在于身份验证。
单击“Ok”创建新的 UI 项目。
从 Controller 文件夹中删除默认的 "HomeController" 以及 Views 文件夹中对应的视图。创建一个名为 "CompanyController" 的新 "MVC 5 Controller - Empty" 控制器,并删除默认的 action 方法。
声明一个类型为接口“ICompanyRepository”的对象,用于保存通过参数化构造函数设置的注入对象。
注意:没有默认的无参数构造函数。
private ICompanyRepository _repo;
public CompanyController(ICompanyRepository repo)
{
_repo = repo;
}
现在,通过添加引用将 EntityModel 和 DataAccess 项目都添加到 UI 中。
将以下代码添加到“CompanyController.cs”中,以执行 Get All、Get by Id、Insert、Update 和 Delete 操作。
还在 Company Controller 的 Views 文件夹中添加所需的视图。(有关相应视图,请参阅附加的源代码)
全局设置与配置
这是整个应用程序的关键,决定是使用 Entity Framework 还是 Dapper,以及在 SQL Server 和 Oracle 之间切换数据库。所以让我们开始在 web.config 文件中添加一些设置。
<add key="UseEF" value="true" />
<add key="TargetDataBase" value="mssql" /><!--oracle-->
这些是全局设置,决定是否使用 Entity Framework 以及我们正在面向哪个数据库。
还要在 web.config 文件中添加两个连接字符串设置。一个用于 SQL Server,另一个用于 Oracle 数据库。
<connectionStrings>
<add name="mssql" connectionString="Password=sa!2015;Persist Security Info=True;User ID=sa;Initial Catalog=DI;Data Source=MYLAP2" />
<add name="oracle" connectionString="!No Connection String Provided!" />
</connectionStrings>
现在,我们有了包含存储库的数据访问项目和包含视图、控制器和全局设置的 UI,让我们将 Ninject 包添加到我们的项目中。
Ninject 安装与配置
右键单击 UI 项目的 References,选择“Manage NuGet Packages…”在在线搜索中搜索 Ninject 并安装“Ninject.MVC5”。
接受许可条款以完成安装。
安装完成后,它将在“App_Start”文件夹中添加一个名为“NinjectWebCommon.cs”的新类文件。在这里,我们将根据 web.config 设置设置切换逻辑。
继续并将以下代码添加到“RegisterServices”方法中,以将我们的方法注册到 Ninject 内核。
添加必要的 using 语句以访问配置设置和存储库。
kernel.Bind<IDbConnection>().To<SqlConnection>().InRequestScope();
当我们在访问高层模块或接口 IDbConnection 时,上面的语句会注入 SqlConnection。通常,我们将其作为构造函数参数或类的属性传递。在我们的例子中是类的构造函数。
kernel.Bind<ICompanyRepository>().To<DataAccess.Repository.Dapper.MSSQL.CompanySqlRepository>().InRequestScope().WithConstructorArgument("conString", conString);
上面的语句在声明接口 IDbConnection 的高层模块时注入 SqlConnection。通常,我们将此作为构造函数参数或类的属性传递。在我们的例子中是类的构造函数。
注意:为了使用 context 类,我们需要通过 NuGet 添加对“Entity Framework”的引用。
还要添加对程序集下的 OracleClient 的引用,并使用命名空间。
using System.Data.OracleClient;
就是这样。到目前为止一切顺利。
现在,在构建和运行应用程序之前,让我们更改默认的路由,使其指向 Company Controller 中的“GetCompanies” action 方法。
这就是我们的 UI 项目结构的样子:
构建并运行应用程序,将全局设置配置为使用 Entity Framework ORM 并面向 SQL Server 数据库。
首先出现的屏幕是公司“列表”。
点击“创建新”时
插入后将显示公司列表。
点击记录的“编辑”链接时
更新后将显示更新的公司列表。
点击记录的“详细信息”链接时
现在,通过将 web.config 应用程序设置配置为使用 Dapper 作为 ORM 来面向 SQL Server 数据库来运行应用程序。
很棒,不是吗?如果你有 Oracle 数据库,可以尝试用 Oracle Repository 和 Client 完成示例 DI 应用程序。
在本文中,我们涵盖了:
- 通用存储库,也称为 Unity,同时适用于 Entity Framework 和 Dapper。
- 使用 Ninject 实现依赖倒置原则。
- 实现存储库模式。
- 使用 Entity Framework 和 Dapper(仅存储过程)实现 CRUD 操作,并且
- 最后,我们演示了如何配置 Ninject 以动态地在 ORM 和数据库之间进行选择。
希望你从中有所收获。我们需要做一些更改才能将其作为生产标准的代码。对本文进行评分,并在下方的评论部分提供您的建议。
我可能错过的其他文章
使用 AngularJs、Asp.Net MVC 和 Entity Framework 实现 CRUD 操作