从 Entity Framework 实现中抽象出 DAL
Abstract away the DAL from Entity Framework implementation
首先,如果这会很长,我很抱歉post,但如果没有所需的详细信息,我不知道如何以正确的方式解释问题。
我很难找到一种方法从 Entity Framework 实现中抽象出我的 DAL。我正在从事的项目非常小,但如果将来我想切换到另一个 ORM,如 NHibernate,或者只是简单的 ADO.NET,我想编写代码只是为了实现,而不是整个 DAL。
假设我的 MyWallet.DAL:
中有这些实体
public interface IWallet {
long Id { get; set; }
float TotalAmountOfMoney { get; set; }
long CurrencyId { get; set; }
ICurrency Currency { get; set; }
DateTime RecordedOn { get; set; }
ICollection<IMoneyMovement> MoneyMovements { get; set; }
}
public interface ICurrency {
long Id { get; set; }
char Symbol { get; set; }
string Code { get; set; }
string Description { get; set; }
}
public interface IMoneyMovement {
long Id { get; set; }
float Amount { get; set; }
string Description { get; set; }
long WalletId { get; set; }
IWallet Wallet { get; set; }
DateTime RecordedOn { get; set; }
DateTime MovedOn { get; set; }
}
如您所见,这些是我计划在另一个包含实际 Entity Framework 实现(比如 MyWallet.DAL.EntityFramework)的库中实现的普通接口。当然,我将使用 Entity Framework 特定属性(如 [Key] 或 [ForeignKey] 等)来装饰实体实现。
我还在 MyWallet.DAL 中定义了一些存储库,例如 IWalletRepository、IMoneyMovementRepository、ICurrencyRepository 以获得对实体的访问权限。实际上我不知道这是否是设计对实体的访问的正确方法。当然我也定义了工厂来获取实体的具体实现。
在我的业务层中,我定义了服务来处理对象请求,使用 DAL 实体和 return 业务对象,如下所示:
public class WalletService {
private readonly IWalletRepository _walletRepository;
private readonly IWalletFactory _walletFactory;
public WalletService(IWalletRepository walletRepository,
IWalletFactory walletFactory) {
_walletRepository = walletRepository;
_walletFactory = walletFactory;
}
public CreatedWallet CreateWallet(CreateWalletRequest request) {
var wallet = _walletFactory.Create();
wallet.CurrencyId = request.CurrencyId;
wallet.TotalAmountOfMoney = request.TotalAmountOfMoney;
wallet.RecordedOn = DateTime.Now;
_walletRepository.Create(wallet);
_walletRepository.SaveChanges();
return new CreatedWallet {
Id = wallet.Id
}
}
}
我认为这会无缝地工作,或者在最坏的情况下——在我有多个存储库的情况下——我可以共享 DataContext,所以我只需要在一个存储库上触发 SaveChanges 方法反映数据库的变化。
问题出在存储库实现上,在这种情况下,我将继续 Entity Framework:
public class EFDataContext : DbContext {
public EFDataContext() : base ("name=MyConnectionString") {
}
public virtual DbSet<EFWallet> Wallets { get; set; }
public virtual DbSet<EFMoneyMovement> MoneyMovements { get; set; }
public virtual DbSet<EFCurrency> Currencies { get; set; }
}
public class EFWalletRepository : IWalletRepository {
private readonly EFDbContext _dataContext;
public EFWalletRepository(EFDbContext dataContext) {
_dataContext = dataContext ?? new EFDbContext();
}
public int SaveChanges() {
return _dataContext.SaveChanges();
}
public void Dispose() {
_dataContext.Dispose();
}
public void Create(IWallet wallet) {
...???
}
}
问题来了:当 DataContext 只知道具体实现时,我如何使用接口?我做错了吗?
更新:
好吧,基本上,正如@TomTom 所说,当您可以拥抱它的力量时,为什么还要战斗 Entity Framework?我想我会让 EF 成为抽象。事实上,通过让 EF 充当 DAL,您可以只专注于项目的业务逻辑。
然后将它们放在一起并就存储库/工作单元问题回复@tdragon:是的,我可以将多个存储库包装在一个工作单元中,或者简单地让 DbContext 成为工作单元:
public class EFWalletRepository : IWalletRepository {
private readonly EFDbContext _dataContext;
public EFWalletRepository() {
_dataContext = new EFDbContext();
}
public void Dispose() {
_dataContext.Dispose();
}
public IEnumerable<Wallet> Wallets {
get { return _dataContext.Wallets; }
}
public void SaveWallet(Wallet wallet) {
if (wallet.Id == 0) {
_dataContext.Wallets.Add(wallet);
} else {
var databaseEntry = _dataContext.Wallets.Find(wallet.Id);
//update properties
}
_dataContext.SaveChanges();
}
}
简单来说:是的,你做错了。你引入了一个糟糕的抽象(这让你在功能上付出了高昂的代价)"because of"。 EF 已经是一个抽象。
在它之上的任何抽象都会使您在使用的功能方面付出代价——就数据库而言,这会带来很大的性能影响。想要一个例子吗? "Include" 预加载导航属性(而不是延迟加载)。您将不得不解决这个问题以及许多特定于 ORM 的更详细的行为——为了获得什么?如果你放弃那些更高更具体的功能,你的表现将会受到影响。
我看不出有任何理由抽象你的模型(实体)。当您更改访问数据库的方式时,您希望它们发生变化吗?
但是如果你想保持这种方式,你可以使你的存储库接口通用,并在定义存储库时传递具体的实体类型,所以你最终会得到:
public class EFWalletRepository : IWalletRepository<EFWallet>
{
public void Create(EFWallet wallet)
{
_dataContext.Add(wallet);
}
}
其他建议:
- 您不应公开模型属性集。这违反了 OOP 规则 - 你应该公开一些方法来操纵对象,状态应该更内部。
- 您可能不应该将
SaveChanges()
方法添加到您的存储库 - 这应该是一个 "unit of work" 作业来提交对数据库的所有更改。
- 当您在服务层中使用多个存储库时,您会遇到一个问题,因为您为存储库创建了一个新的
DbContext
,而您应该为单个 "unit of work" 创建一个存储库。
首先,如果这会很长,我很抱歉post,但如果没有所需的详细信息,我不知道如何以正确的方式解释问题。
我很难找到一种方法从 Entity Framework 实现中抽象出我的 DAL。我正在从事的项目非常小,但如果将来我想切换到另一个 ORM,如 NHibernate,或者只是简单的 ADO.NET,我想编写代码只是为了实现,而不是整个 DAL。
假设我的 MyWallet.DAL:
中有这些实体public interface IWallet {
long Id { get; set; }
float TotalAmountOfMoney { get; set; }
long CurrencyId { get; set; }
ICurrency Currency { get; set; }
DateTime RecordedOn { get; set; }
ICollection<IMoneyMovement> MoneyMovements { get; set; }
}
public interface ICurrency {
long Id { get; set; }
char Symbol { get; set; }
string Code { get; set; }
string Description { get; set; }
}
public interface IMoneyMovement {
long Id { get; set; }
float Amount { get; set; }
string Description { get; set; }
long WalletId { get; set; }
IWallet Wallet { get; set; }
DateTime RecordedOn { get; set; }
DateTime MovedOn { get; set; }
}
如您所见,这些是我计划在另一个包含实际 Entity Framework 实现(比如 MyWallet.DAL.EntityFramework)的库中实现的普通接口。当然,我将使用 Entity Framework 特定属性(如 [Key] 或 [ForeignKey] 等)来装饰实体实现。
我还在 MyWallet.DAL 中定义了一些存储库,例如 IWalletRepository、IMoneyMovementRepository、ICurrencyRepository 以获得对实体的访问权限。实际上我不知道这是否是设计对实体的访问的正确方法。当然我也定义了工厂来获取实体的具体实现。
在我的业务层中,我定义了服务来处理对象请求,使用 DAL 实体和 return 业务对象,如下所示:
public class WalletService {
private readonly IWalletRepository _walletRepository;
private readonly IWalletFactory _walletFactory;
public WalletService(IWalletRepository walletRepository,
IWalletFactory walletFactory) {
_walletRepository = walletRepository;
_walletFactory = walletFactory;
}
public CreatedWallet CreateWallet(CreateWalletRequest request) {
var wallet = _walletFactory.Create();
wallet.CurrencyId = request.CurrencyId;
wallet.TotalAmountOfMoney = request.TotalAmountOfMoney;
wallet.RecordedOn = DateTime.Now;
_walletRepository.Create(wallet);
_walletRepository.SaveChanges();
return new CreatedWallet {
Id = wallet.Id
}
}
}
我认为这会无缝地工作,或者在最坏的情况下——在我有多个存储库的情况下——我可以共享 DataContext,所以我只需要在一个存储库上触发 SaveChanges 方法反映数据库的变化。
问题出在存储库实现上,在这种情况下,我将继续 Entity Framework:
public class EFDataContext : DbContext {
public EFDataContext() : base ("name=MyConnectionString") {
}
public virtual DbSet<EFWallet> Wallets { get; set; }
public virtual DbSet<EFMoneyMovement> MoneyMovements { get; set; }
public virtual DbSet<EFCurrency> Currencies { get; set; }
}
public class EFWalletRepository : IWalletRepository {
private readonly EFDbContext _dataContext;
public EFWalletRepository(EFDbContext dataContext) {
_dataContext = dataContext ?? new EFDbContext();
}
public int SaveChanges() {
return _dataContext.SaveChanges();
}
public void Dispose() {
_dataContext.Dispose();
}
public void Create(IWallet wallet) {
...???
}
}
问题来了:当 DataContext 只知道具体实现时,我如何使用接口?我做错了吗?
更新:
好吧,基本上,正如@TomTom 所说,当您可以拥抱它的力量时,为什么还要战斗 Entity Framework?我想我会让 EF 成为抽象。事实上,通过让 EF 充当 DAL,您可以只专注于项目的业务逻辑。
然后将它们放在一起并就存储库/工作单元问题回复@tdragon:是的,我可以将多个存储库包装在一个工作单元中,或者简单地让 DbContext 成为工作单元:
public class EFWalletRepository : IWalletRepository {
private readonly EFDbContext _dataContext;
public EFWalletRepository() {
_dataContext = new EFDbContext();
}
public void Dispose() {
_dataContext.Dispose();
}
public IEnumerable<Wallet> Wallets {
get { return _dataContext.Wallets; }
}
public void SaveWallet(Wallet wallet) {
if (wallet.Id == 0) {
_dataContext.Wallets.Add(wallet);
} else {
var databaseEntry = _dataContext.Wallets.Find(wallet.Id);
//update properties
}
_dataContext.SaveChanges();
}
}
简单来说:是的,你做错了。你引入了一个糟糕的抽象(这让你在功能上付出了高昂的代价)"because of"。 EF 已经是一个抽象。
在它之上的任何抽象都会使您在使用的功能方面付出代价——就数据库而言,这会带来很大的性能影响。想要一个例子吗? "Include" 预加载导航属性(而不是延迟加载)。您将不得不解决这个问题以及许多特定于 ORM 的更详细的行为——为了获得什么?如果你放弃那些更高更具体的功能,你的表现将会受到影响。
我看不出有任何理由抽象你的模型(实体)。当您更改访问数据库的方式时,您希望它们发生变化吗?
但是如果你想保持这种方式,你可以使你的存储库接口通用,并在定义存储库时传递具体的实体类型,所以你最终会得到:
public class EFWalletRepository : IWalletRepository<EFWallet>
{
public void Create(EFWallet wallet)
{
_dataContext.Add(wallet);
}
}
其他建议:
- 您不应公开模型属性集。这违反了 OOP 规则 - 你应该公开一些方法来操纵对象,状态应该更内部。
- 您可能不应该将
SaveChanges()
方法添加到您的存储库 - 这应该是一个 "unit of work" 作业来提交对数据库的所有更改。 - 当您在服务层中使用多个存储库时,您会遇到一个问题,因为您为存储库创建了一个新的
DbContext
,而您应该为单个 "unit of work" 创建一个存储库。