fharreau
fharreau

Reputation: 2337

Abstract Entity Framework

I want to create an abstraction layer between Entity Framework and the rest of my application. But I am having a few problems with Entity Framework.

Basically (I don't show you all the interface layers that I've created too), I've split my application into several projects like this :

  1. Domain
    • Contains my domain object, an abstraction of my datastorage object
  2. DAL
    • Creates a link between my datastorage and my business layer. Contains two types of elements :
      • Private ones : my EDMX, my database object, and some other generated objects providing me some useful methods like ToDomain/ToEntity
      • Public ones : my Data Access Object, providing CRUD methods
  3. Business
    • Contains the logic of my application. Only knows about the public elements of the DAL and the Domain Layer.
  4. Presentation
    • Presents the domain objects for the user. Only knows about the business layer.

As I said, I want to create an abstraction of my datastorage objects (in my case Database object, but I want a solution that works also for file or WCF storage for example) so that my business layer don't know anything about my DAL implementation.

Here is a glimpse of what I've done in my 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);
}

You will probably see what's wrong with my code by reading it: when I try to delete or update an entity, I am not manipulating an entity attached to Entity Framework. If I try to attach my entity to the dbContext, it fails because there is already an entity in the context with the same id.

I already thought about several solutions, but none of them please me.

Maybe am I doing something wrong in my approach? I am a little bit confused about the Repository and DAO pattern (I read anything and the very opposite about that difference on the internet).

Upvotes: 1

Views: 2621

Answers (2)

Kirill Bestemyanov
Kirill Bestemyanov

Reputation: 11964

You have two options:

  1. initialize new dbcontext for each operation and dispose it when operation is ended:

    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. or you can try to find entity in your instance of dbcontext using property Local of DbSet:

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

Upvotes: 1

CaRDiaK
CaRDiaK

Reputation: 885

You seem to be getting stuck coding to an implementation within your abstraction. If you injected an interface to your generic rather than a concrete type (like EF) then your GenericDao becomes much more flexible. You can inject whatever implementation you choose providing it implements the required interface. In your case, WCF, File, Whatever. For example;

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
    }

}

Upvotes: 0

Related Questions