如何将实体框架中的数据访问映射到业务逻辑对象

时间:2014-11-24 15:55:36

标签: c# asp.net entity-framework

我在ASP.NET C#MVC应用程序中使用Entity Framework。

我在数据访问层中有EF生成的对象:

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>();
        }
        //...

    }
}

现在,我想创建业务逻辑对象,然后使用数据访问对象映射它们:

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

我在数据库中有非常少量的表。我需要使用Automapper吗?如果不是,我该如何实现映射?

4 个答案:

答案 0 :(得分:9)

我一直使用EF6从MSSQL表生成我的数据访问层,然后创建一组对象来表示我希望如何与我的代码(或显示它)进行POCO交互。 &#34;映射&#34;通过实现存储库模式来处理。下面是一个通用界面,可以帮助我确保所有的repo类都遵循相同的形状。

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

然后,我创建这样的repo类。 (使用您的UserBusiness和UserDAL类)

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();
        }

    }
}

从您的应用程序中,您现在使用repo类的方法而不是DBContext。

最后,我经常最终添加另一层服务类&#39;与我的回购交互,管理商业类的内部数据...或者你可以使你的商务课更聪明&#39;通过向他们添加repo方法。我倾向于保持POCO的愚蠢并构建服务类来获取,设置和编辑属性。

是的,有一堆左右映射到&#34;转换&#34;一个类到另一个类,但它是后来内部业务逻辑类的清晰分离。 POCO转换的直接表一开始看起来很愚蠢,但只要等到DBA想要规范几个字段或者你决定为这些简单对象添加一个集合。能够管理您的业务对象而不会破坏您的其余应用程序是无价的。


编辑:下面是存储库的通用版本,这使得创建新存储库变得更加容易。

这是所有业务逻辑层类的基类:

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

这是所有数据访问层类的基类:

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

这是存储库的通用基类(注意,我们在这里使用AutoMapper):

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();
        }
    }
}

最后,当我们完成上述所有操作时,这是存储库的示例实现,非常干净:

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)
    {

    }
}

答案 1 :(得分:1)

如果您的项目相对较小,我建议您根本不使用DTO - 相反,您可以使用Entity Framework Code First并在多个层中重用您的业务实体(只需确保将Code First实体放置到某个公共库中) )。

否则,您可以创建自己的转换方法或使用AutoMapper等库。

答案 2 :(得分:1)

如果要在Business Model Design中使用Plain Old Clr Objects并将它们映射到数据库表,则可以使用Code First Approach for Entity Framework。 在Code中,首先不会为您生成任何内容。但是,您将负责将Business Objects映射到数据库表和字段。您可以通过两种方式进行基础测试:

这两种方法将为您生成相同的映射,但我更喜欢Fluent Api方法,因为它提供了更强的映射API,并使您的BO独立于将集中在您的datacontext中的任何映射逻辑。

但是..一旦你生成了类,这些将被绑定并映射给你,这是数据库的第一个方法。因此,您可以扩展这些类,因为它们是部分的。 您可以在此博客中找到有关在EF上制作的不同工作流程的详细信息,这些工作流程将帮助您根据需要使用正确的工作流程:http://blog.smartbear.com/development/choosing-the-right-entity-framework-workflow/

答案 3 :(得分:0)

如果您的DAL和BLL User对象完全相同,您可以使用这样的函数进行映射:

public void SetProperties(object source, object target)
{
    var type = target.GetType();
    foreach (var prop in source.GetType().GetProperties())
    {
        var propGetter = prop.GetGetMethod();
        var propSetter = type.GetProperty(prop.Name).GetSetMethod();
        var valueToSet = propGetter.Invoke(source, null);
        propSetter.Invoke(target, new[] { valueToSet });
    }
}

但是,为什么你需要在DAL和BLL中使用不同但又完全相同的User个对象?如果您需要更改User对象的属性,会发生什么?您必须在每个实例中,在每个层中,在整个应用程序中进行更改(紧密耦合),并且首先要破坏DAL和BLL的目的。

这是一个使用泛型和接口非常有用的实例。因此,给定对象User示例:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

...你可以创建一个通用的&#34;存储库&#34;从数据访问接口继承的对象的类或其他数据访问方法(DAL)

public class DALMethods<T> : IDALMethods<T> where T : class
    {
        private UserContext _db; 
        private DbSet<T> _set;

        public DALMethods(UserContext db)
        {
            _db = db;
            _set = _db.Set<T>();
        }

        public void Create(T entity)
        {
            _set.Add(entity);
            _db.SaveChanges();
        }

        //... Expressly dispose context method needed.
    }

..然后您的 BLL 会关注User业务逻辑:

public class UserBLL : IBLLMethods<User>
{
    private DALMethods<User> _repository;
    private UserContext _db;

    public UserBLL()
    {
        _db = new UserContext();
        _repository = new DALMethods<User>(_db);
    }

    public bool CreateUserIfNameIsBob(User user)
    {
        // Create bob if bob
        if (user.Name == "Bob")
        {
            _repository.Create(user);
            return true;
        }

        // Not bob
        return false;
    }
}

上面的例子是有目的的通用,但我认为它们说明了这一点。如果您的User对象发生变化,则无法阻止您的BLL和DAL图层正常工作。您可以使用IDALMethods<T>之类的接口来强制实施约束,或者使用IoC容器进一步解耦代码。

HTH