How to map Data Access to Business Logic objects in Entity Framework

asp.net c# entity-framework

Question

I am using Entity Framework in an ASP.NET C# MVC application.

I have objects that are generated by EF in the Data Access Layer:

namespace Project1.DataAccess
{
    using System;
    using System.Collections.Generic;

    public partial class User
    {
        public User()
        {
            this.Files = new HashSet<File>();
            this.Folders = new HashSet<Folder>();
        }
        //...

    }
}

Now, I want to create Business Logic objects, and then map them with the Data Access ones:

namespace Project1.Logic
{
    public class User
    {
        public int Id { get; set; }
    }
}

I have a very small number of tables in the database. Do I need to use Automapper? If no, how can I achieve the mapping?

1
2
11/24/2014 3:55:35 PM

Accepted Answer

I use EF6 all the time to generate my Data Access Layer from MSSQL tables, then I create a set of objects that represent how I want interact with my code (or display it) which are POCO. The "mapping" is taken care of through the implementation of a Repository pattern. Below is a generic interface that helps me ensure all my repo classes follow the same shape.

    public interface IDataRepository<T>
    {
        IQueryable<T> Get();
        T Get(int id);
        T Add(T obj);
        T Update(T obj);
        void Delete(T obj);
    }

Then, I create repo classes like this. (using your UserBusiness and UserDAL classes)

public class NewRepo : IDataRepository<UserBusiness>
{
    YourContext db = new YourContext();

    public IQueryable<UserBusiness> Get()
    {
        return (from u in db.UserDAL select new UserBusiness()
        {
           Id = u.Id,
           Name = u.Name
        });
    }

    public UserBusiness Get(int id)
    {
        return (from u in db.UserDAL where u.Id == id select new UserBusiness()
        {
           Id = u.Id,
           Name = u.Name
        }).FirstOrDefault();
    }

    public Order Add(UserBusiness obj)
    {
        UserDAL u= new UserDAL();
        u.Name = obj.Name;

        db.UserDAL.Add(u);
        db.SaveChanges();

        //Assuming the database is generating your Id's for you
        obj.Id = u.Id;

        return obj;

    }

    public Order Update(UserBusiness obj)
    {
        UserDAL u= new UserDAL();
        u.Id = obj.Id;
        u.Name = obj.Name;

        db.Entry(u).State = EntityState.Modified;
        db.SaveChanges();

        return obj;
    }

    public void Delete(UserBusiness obj)
    {
        UserDAL u = db.UserDAL
            .Where(o=>o.Id == obj.Id)
            .FirstOrDefault();

        if (u!=Null)  {
          db.Entry(u).State = EntityState.Deleted;
          db.SaveChanges();
        }

    }
}

From within your application, you'd now use the methods of your repo class instead of your DBContext.

Lastly, I often end up adding another layer of 'Service Classes' that interact with my repos which manage the internal data of the Business classes...or you could make your Business classes 'smarter' by adding the repo methods to them. My preference is to keep POCO's dumb and build service classes to get, set and edit properties.

Yes, there is a bunch of left-right mapping to "convert" one class to another, but it's a clean separation of internal business logic classes for later on. Straight table to POCO conversions seems silly at first, but just wait until your DBA want's to normalize a few fields or you decide to add a collection to those simple objects. Being able to manage your business objects without breaking the rest of you app is priceless.


Edit: Below there is generic version of the Repository, which makes creating new repositories a lot easier.

This is base class for all Business Logic Layer classes:

public class BaseEntity
{
    public int Id { get; set; }
}

This is base class for all Data Access Layer classes:

public class BaseEntityDAL
{
    [Key]
    [Column("Id")]
    public int Id { get; set; }
}

This is generic base class for repository (note, that we use AutoMapper here):

public abstract class BaseRepository<TDAL, TBLL> : IRepository<TBLL>
    where TDAL : BaseEntityDAL, new()
    where TBLL : BaseEntity, new()
{
    protected readonly MyDbContext context;
    protected readonly DbSet<TDAL> dbSet;

    protected virtual TDAL Map(TBLL obj)
    {
        Mapper.CreateMap<TBLL, TDAL>();
        return Mapper.Map<TDAL>(obj);
    }

    protected virtual TBLL Map(TDAL obj)
    {
        Mapper.CreateMap<TDAL, TBLL>();
        return Mapper.Map<TBLL>(obj);
    }

    protected abstract IQueryable<TBLL> GetIQueryable();            

    public BaseRepository(MyDbContext context, DbSet<TDAL> dbSet)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (dbSet == null)
            throw new ArgumentNullException(nameof(dbSet));

        this.context = context;
        this.dbSet = dbSet;
    }

    public TBLL Get(int id)
    {
        var entity = dbSet
            .Where(i => i.Id == id)
            .FirstOrDefault();

        var result = Map(entity);

        return result;
    }

    public IQueryable<TBLL> Get()
    {
        return GetIQueryable();
    }

    public TBLL Add(TBLL obj)
    {
        var entity = Map(obj);

        dbSet.Add(entity);
        context.SaveChanges();

        obj.Id = entity.Id;

        return obj;
    }

    public TBLL Update(TBLL obj)
    {
        var entity = Map(obj);

        context.Entry(entity).State = EntityState.Modified;
        context.SaveChanges();

        return obj;
    }

    public void Delete(TBLL obj)
    {
        TDAL entity = dbSet
            .Where(e => e.Id == obj.Id)
            .FirstOrDefault();

        if (entity != null)
        {
            context.Entry(entity).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}

Finally, when we got all above, this is sample implementation of repository, nice and clean:

public class ContractRepository : BaseRepository<ContractDAL, Contract>
{
    protected override IQueryable<Contract> GetIQueryable()
    {
        return dbSet
            .Select(entity => new Contract()
            {
                // We cannot use AutoMapper here, because Entity Framework
                // won't be able to process the expression. Hence manual
                // mapping.
                Id = entity.Id,
                CompanyId = entity.CompanyId,
                ProjectId = entity.ProjectId,
                IndexNumber = entity.IndexNumber,
                ContractNumber = entity.ContractNumber,
                ConclusionDate = entity.ConclusionDate,
                Notes = entity.Notes
            });
    }

    public ContractRepository(MyDbContext context)
        : base(context, context.Contracts)
    {

    }
}
11
10/9/2015 7:45:51 PM

Popular Answer

If you want to Use Plain Old Clr Objects in your Business Model Design and map them to your database tables, you may use the Code First Approach for Entity Framework. In Code first, nothing will be generated for you. However, you will have the responsability to map your Business Objects to your Database Tables and fields. You can basiscally do it with two ways:

The two methods will generate the same mapping for you, but I prefer Fluent Api method since it provides a stronger mapping API and keeps your BOs independant of any mapping logic that will be centralized in your datacontext.

But.. Once you generate classes, these will be binded and mapped for you, which is the Database first Approach. Hence, you can extend these classes since they are partial. You can find in this blog post details on the different workflows made on EF that will help you use the right one for your needs: http://blog.smartbear.com/development/choosing-the-right-entity-framework-workflow/



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow