【问题标题】:Abstract Entity Framework抽象实体框架
【发布时间】:2015-08-16 11:11:40
【问题描述】:

我想在实体框架和我的应用程序的其余部分之间创建一个抽象层。但是我在使用 Entity Framework 时遇到了一些问题。

基本上(我也没有向您展示我创建的所有接口层),我已经将我的应用程序拆分为多个项目,如下所示:

    • 包含我的域对象,我的数据存储对象的抽象
  1. DAL
    • 在我的数据存储和我的业务层之间创建一个链接。包含两种类型的元素:
      • 私有对象:我的 EDMX、我的数据库对象和其他一些生成的对象,这些对象为我提供了一些有用的方法,例如 ToDomain/ToEntity
      • 公共对象:我的数据访问对象,提供 CRUD 方法
  2. 业务
    • 包含我的应用程序的逻辑。只知道 DAL 和域层的公共元素。
  3. 演示文稿
    • 为用户呈现域对象。只知道业务层。

正如我所说,我想创建我的数据存储对象的抽象(在我的情况下为 Database 对象,但我想要一个也适用于文件或 WCF 存储的解决方案),以便我的业务层不对我的 DAL 实施一无所知。

这是我在 DAL 中所做的一瞥:

public abstract class GenericDao<TEntity, TDomain, TDbContext> : IGenericDao<TDomain>
    where TDbContext : DbContext, new()
    where TEntity : class
    where TDomain : class
{
    protected TDbContext _context;
    protected DbSet<TEntity> _dbSet;

    public GenericDao(TDbContext dbContext)
    {
        this._context = dbContext;
        this._dbSet = dbContext.Set<TEntity>();
    }

    public TDomain Create()
    {
        return this.ToDomain(this._dbSet.Create());
    } 

    public IList<TDomain> GetAll()
    {
        return this._dbSet.ToList().Select(entity => this.ToDomain(entity)).ToList();
    }

    public void Update(TDomain domain)
    {
        var entity = this.ToEntity(domain);

        var entry = this._context.Entry(entity);

        entry.State = EntityState.Modified;
    }

    public void Remove(TDomain domain)
    {
        _dbSet.Remove(this.ToEntity(domain));
    }

    protected abstract TDomain ToDomain(TEntity entity);

    protected abstract TEntity ToEntity(TDomain domain);
}

通过阅读我的代码,您可能会发现它有什么问题:当我尝试删除或更新实体时,我并没有在操作附加到 Entity Framework 的实体。如果我尝试将我的实体附加到dbContext,它会失败,因为上下文中已经有一个具有相同 ID 的实体。

我已经考虑过几种解决方案,但没有一个能让我满意。

也许我的方法有问题?我对 Repository 和 DAO 模式有点困惑(我在互联网上阅读了任何内容,但完全相反)。

【问题讨论】:

  • 因为上下文中已经存在具有相同 id 的实体 - 为什么?
  • 首先我检索一个实体。它存储在 dbContext 中。然后我将该实体作为新的域对象返回到我的业务层(因为我不希望我的业务层知道我的数据访问层的任何信息)。我的业务层执行其工作,然后在我的实体上调用更新。由于更新方法的入口点是我的域对象,因此我没有任何指向实体对象的指针。创建一个新的会导致我遇到的问题。使用 Kirill Bestemyanov 的第二个选项,我可以解决它,但我对它并不完全满意。我还在努力。我会告诉你的!

标签: c# entity-framework entity-framework-6


【解决方案1】:

你有两个选择:

  1. 为每个操作初始化新的dbcontext,并在操作结束时处理它:

    public abstract class GenericDao<TEntity, TDomain, TDbContext> : IGenericDao<TDomain>
    where TDbContext : DbContext, new()
    where TEntity : class
    where TDomain : class
    {
        protected Func<TDbContext> _contextFactory;
    
        public GenericDao(Func<TDbContext> contextFactory)
        {
            _contextFactory = contextFactory;
        }
    
        public TDomain Create()
        {
           using(var context = _contextFactory())
           {
               return context.Set<TEntity>().Create();
           }
        } 
    
        public IList<TDomain> GetAll()
        {
           using(var context = _contextFactory())
           {
              return context.Set<TEntity>().ToList()
                     .Select(entity => this.ToDomain(entity)).ToList();
           }
        }
    
        public void Update(TDomain domain)
        {
           using(var context = _contextFactory())
           {
              var entity = this.ToEntity(domain);
              context.Attach(entity);
              var entry = this._context.Entry(entity);
              entry.State = EntityState.Modified;
              context.SaveChanges();
           }
       }
    
       public void Remove(TDomain domain)
       {
          using(var context = _contextFactory())
          {
            var entity = this.ToEntity(domain);
            context.Attach(entity);
            context.Set<TEntity>.Remove(entity);
            context.SaveChanges();
         }
       }
    
       protected abstract TDomain ToDomain(TEntity entity);
    
       protected abstract TEntity ToEntity(TDomain domain);
    }
    
  2. 或者您可以尝试使用DbSet 的属性Local 在您的dbcontext 实例中查找实体:

    var contextEntity = context.Set<TEntity>().Local
        .Where(c=>c.Id == entity.Id).FirstOrDefault();
    

【讨论】:

  • 我正在为您的第二个选项制定解决方案。我不知道 DbSet 属性,谢谢。直到现在我对它并不完全满意,但我更喜欢它而不是你的第一个解决方案。甚至推荐吗?我的意思是,为每次调用我的 DAL 实例化一个新的 dbContext(目前,我有一个每个请求的 dbContext)?我会尽快发布我的解决方法,以便我们最终改进它^^。
  • 每次调用 DAL 实例化 dbcontext 并不罕见。这在网站和 Web 服务等断开连接的场景中很常见。
【解决方案2】:

您似乎被困在抽象中的实现编码中。如果您将接口注入到泛型而不是具体类型(如 EF),那么您的 GenericDao 会变得更加灵活。您可以注入您选择的任何实现,只要它实现所需的接口。在你的情况下,WCF、文件、随便什么。例如;

protected IDbContext _context;

public GenericDao(IDbContext dbContext)
{
    this._context = dbContext;
}

public void Remove(TDomain domain)
{
    _context.Remove(this.ToEntity(domain));
}

//abstraction
public interface IDbContext
{
    void Remove(Entity entity);
}


//EF Implementation
public MyEfClass : IDbContext
{
    public void Remove(Entity entity)
    {
        //code to remove for EF example
        context.Attach(entity);
        context.State = EntityState.Modified;
        context.Set<TEntity>.Remove(entity);
        context.SaveChanges();
    }

}

//WCF Implementation
public MyWCFClass : IDbContext
{
    public void Remove(Entity entity)
    {
        //Wcf implementation here
    }

}

//File example 
public FileWriter : IDbContext
{
    public void Remove(Entity entity)
    {
        LoadFile();
        FindEntry(entity);
        WriteFile(entity);
        SaveFile();
    }
    public void LoadFile()
    {
          //use app settings for file directory
    }

}

【讨论】:

  • 你可以注入你选择的任何实现我不相信你。 IQueryable 就是这样一个 leaky abstraction,你甚至不能让两个 ORM 的两个实现做同样的事情,更不用说完全不同的存储系统了。调用者总是必须了解底层实现,这违背了抽象的目的。
猜你喜欢
  • 1970-01-01
  • 2015-05-05
  • 1970-01-01
  • 2016-06-22
  • 1970-01-01
  • 1970-01-01
  • 2017-10-05
  • 2022-11-21
  • 1970-01-01
相关资源
最近更新 更多