【问题标题】:Abstract away the DAL from Entity Framework implementation从实体框架实现中抽象出 DAL
【发布时间】:2016-06-22 01:37:00
【问题描述】:

首先很抱歉,这将是一篇很长的帖子,但如果没有所需的详细信息,我不知道如何以正确的方式解释问题。

我在寻找从实体框架实现中抽象出我的 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; }
}

如您所见,这些是我计划在另一个库中实现的普通接口,该库将包含实际的实体框架实现(例如 MyWallet.DAL.EntityFramework)。当然,我将使用实体框架特定属性作为 [Key] 或 [ForeignKey] 来装饰实体实现以及类似的东西。

我还在 MyWallet.DAL 中定义了一些存储库,例如 IWalletRepository、IMoneyMovementRepository、ICurrencyRepository 以获得对实体的访问权限。实际上我不知道这是否是设计对实体的访问的正确方法。当然我也定义了工厂来获取实体的具体实现。

在我的业务层中,我定义了服务来处理对象请求,使用 DAL 实体并返回一个业务对象,如下所示:

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 方法以反映数据库的变化。

问题在于存储库实现,在这种情况下,我将继续使用实体框架:

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 所说,当您可以接受它的力量时,为什么还要与实体框架抗争呢?我想我会让 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();
    }
}

【问题讨论】:

  • 你也在抽象数据;你应该只抽象逻辑。将 POCO 保留在业务层中。

标签: c# entity-framework design-patterns data-access-layer abstraction


【解决方案1】:

我看不出有任何理由抽象您的模型(实体)。当您更改访问数据库的方式时,您是否希望它们发生变化?

但是如果你想保持这种方式,你可以让你的存储库接口通用,并在定义存储库时传递具体的实体类型,所以你最终会得到:

public class EFWalletRepository : IWalletRepository<EFWallet>
{
    public void Create(EFWallet wallet)
    {
        _dataContext.Add(wallet);
    }
}

其他建议:

  1. 您不应公开模型属性的集合。这违反了 OOP 规则 - 您应该公开一些方法来操作对象,状态应该更加内部。
  2. 您可能不应该将 SaveChanges() 方法添加到您的存储库 - 这应该是一个“工作单元”作业来提交对数据库的所有更改。
  3. 当您在服务层中使用多个存储库时会遇到问题,因为您为存储库创建了一个新的DbContext,而您应该为单个“工作单元”创建一个。

【讨论】:

  • 我正在抽象实体,因为 EF 有自己的装饰器,我不想将其包含在我的 DAL 库中。 [Table("wallet")] public class Wallet { [Key] [Column("id")] public long Id { get; set; } .... }
  • 您可以使用配置文件,而不是使用属性来装饰您的实体。然后,您的实体将保持无 EF 且干净,请参见此处:entityframeworktutorial.net/code-first/…
【解决方案2】:

简单地说:是的,你做错了。你引入了一个糟糕的抽象(这会让你在功能上付出高昂的代价)“因为”。 EF 已经是一种抽象了。

在它之上的任何抽象都会使您在使用的功能方面付出代价——就数据库而言,这会对性能产生很大影响。想要一个例子吗? “包含”预加载导航属性(而不是延迟加载)。您将不得不解决这个问题以及许多特定于 ORM 的更详细的行为 - 为了获得什么?如果你放弃那些更高更具体的功能,你的表现就会受到影响。

【讨论】:

  • 我认为这取决于他的要求。在需要 YAGNI 之前,我不会介绍这些功能。首要问题是背后最大的商业价值在哪里?有一个更简单的方法来更改 dbms 或有一个更容易打开 EF 功能的窗口。 EF 只是一个“中途”抽象,因为它强烈依赖于 EF 技术本身。默认情况下这还不错,但它是一个依赖项。我认为这两种方式都是可能的,应该根据要求进行验证
  • 我不这么看。在我的情况下,我从来不需要这个 EF 功能,而且很可能我永远不需要它们。如果这个功能很明显你的权利。但他们真的在他的情况下吗?但是你粗鲁的帖子对我来说感觉有点奇怪而且不是很专业......实施一切只是为了担心“也许10年后我可能需要它”是非常糟糕的。如果您知道自己将需要它,那么就实施它。但如果不知道他的背景,你就无法知道。最好建议他考虑一下他是否很可能需要此功能或询问商人!
  • 好吧,也许我应该在之前说明过,但我正在做的这个项目只是“功课”,只是从一个很小的起点开始构建一些东西来提高设计技能的一种方式。当然,这个项目根本不需要这种抽象,而且在这种情况下,EF 本身就是一个抽象,即使不是我想象的抽象。
  • @BoasEnkler 好吧,Boas,这很可能是一个无能的例子,因为不知何故,我在超过 50% 的查询中都使用了这个特殊功能。所以,要么我只处理非常复杂的查询,要么你基本上落入了我在 TON 项目团队中看到的陷阱:人们不知道他们在做什么,然后一旦有人向他们展示了适当的表现就会感到惊讶。似乎有很多您从未使用过的基线功能。嗯,对你很好。我们中的一些人必须专业地工作,因为应用程序需要它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 2015-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多