ASP MVC EF6架构

时间:2016-08-04 06:32:59

标签: asp.net-mvc architecture entity-framework-6 automapper n-tier-architecture

我们有ASP MVC网络项目。在阅读了关于正确架构的stackoverflow中的大量文章和讨论之后,我们决定采用以下方法,尽管不仅有一种正确的做法,这就是我们决定的方式,但我们仍有一些疑问

我们在这里发布这个不仅是为了帮助,而且是为了展示我们所做的事情,以防它对某人有所帮助。

我们正在使用ASP .NET MVC项目,EF6 Code首先使用MS SQL Server。 我们将项目划分为3个主要层,我们将这些层分成3个项目:模型,服务和Web。

  • 模型创建实体并为数据库设置DataContext。
  • 该服务对数据库进行查询并将这些实体转换为DTO以将其传递到Web层,因此Web层对数据库一无所知。
  • Web使用AutoFac进行DI(依赖注入)来调用服务层中的服务,并获取DTO以将这些DTO转换为模型视图,以便在视图中使用它们。

在阅读了很多文章之后,我们决定不实施存储库模式和工作单元,因为总的来说,我们已经将EF作为一个工作单元本身。所以我们在这里简化一些事情。 https://cockneycoder.wordpress.com/2013/04/07/why-entity-framework-renders-the-repository-pattern-obsolete/

这是我们项目的总结。现在,我将浏览每个项目以显示代码。我们将只显示几个实体,但我们的项目有100多个不同的实体。

MODEL

数据上下文

public interface IMyContext
{
    IDbSet<Language> Links { get; set; }
    IDbSet<Resources> News { get; set; }
    ...

    DbSet<TEntity> Set<TEntity>() where TEntity : class;
    DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
}

public class MyDataContext : DbContext, IMyContext
{
    public MyDataContext() : base("connectionStringName") 
    { 

    }

    public IDbSet<Language> Links { get; set; }
    public IDbSet<Resources> News { get; set; }
    ...

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
    }
}   

以下是我们如何宣布实体

public class Link
{
    public int Id{ get; set; }
    public string Title { get; set; }
    public string Url { get; set; }
    public bool Active { get; set; }
}

服务

这些是我们用于所有服务的通用类。 如您所见,我们使用DTO从Web层获取数据。我们还使用Dbset = Context.Set()

连接到数据库
public interface IService
{
}

public interface IEntityService<TDto> : IService where TDto : class
{
    IEnumerable<TDto> GetAll();
    void Create(TDto entity);
    void Update(TDto entity);
    void Delete(TDto entity);

    void Add(TDto entity);
    void Entry(TDto existingEntity, object updatedEntity);
    void Save();
}

public abstract class EntityService<T, TDto> : IEntityService<TDto> where T : class where TDto : class
{
    protected IClientContext Context;
    protected IDbSet<T> Dbset;

    protected EntityService(IClientContext context) { Context = context; Dbset = Context.Set<T>(); }


    public virtual IEnumerable<TDto> GetAll()
    {
        return Mapper.Map<IEnumerable<TDto>>(Dbset.AsEnumerable());
    }

    public virtual void Create(TDto entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException(nameof(entity));
        }

        Dbset.Add(Mapper.Map<T>(entity));
        Context.SaveChanges();
    }

    public virtual void Update(TDto entity)
    {
        if (entity == null) throw new ArgumentNullException(nameof(entity));
        Context.Entry(entity).State = EntityState.Modified;
        Context.SaveChanges();
    }

    public virtual void Delete(TDto entity)
    {
        if (entity == null) throw new ArgumentNullException(nameof(entity));
        Dbset.Remove(Mapper.Map<T>(entity));
        Context.SaveChanges();
    }


    public virtual void Add(TDto entity)
    {
        Dbset.Add(Mapper.Map<T>(entity));
    }

    public virtual void Entry(TDto existingEntity, object updatedEntity)
    {


        Context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity);
    }

    public virtual void Save()
    {
        Context.SaveChanges();
    }
}

我们在这个项目中声明了DTO(这是一个非常简单的例子,所以我们不必在这里放置所有代码):

public class LinkDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Url { get; set; }
    public bool Active { get; set; }
}

然后我们的服务之一:

public interface ILinkService : IEntityService<LinkDto>
{
    IPagedList<LinkDto> GetAllLinks(string searchTitle = "", bool searchActive = false, int pageNumber = 1, int pageSize = 10);
    LinkDto FindById(int id);
    LinkDto Test();
}

public class LinkService : EntityService<Link, LinkDto>, ILinkService
{
    public LinkService(IClientContext context) : base(context) { Dbset = context.Set<Link>(); }

    public virtual IPagedList<LinkDto> GetAllLinks(bool searchActive = false, int pageNumber = 1, int pageSize = 10)
    {
        var links = Dbset.Where(p => p.Active).ToPagedList(pageNumber, pageSize);
        return links.ToMappedPagedList<Link, LinkDto>();
    }

    public virtual LinkDto FindById(int id)
    {
        var link = Dbset.FirstOrDefault(p => p.Id == id);
        return Mapper.Map<LinkDto>(link);
    }

    public LinkDto Test()
    {
        var list = (from l in Context.Links
                    from o in Context.Other.Where(p => p.LinkId == l.Id)
                    select new OtherDto
                    { l.Id, l.Title, l.Url, o.Other1... }).ToList();

        return list;
    }
}

如您所见,我们使用AutoMapper(版本5稍微改变了一点)从实体转换为DTO数据。 我们怀疑的一个问题是如果使用&#34; Dbset.Find&#34;或&#34; Dbset.FirstOrDefault&#34;是正确的,如果使用&#34; Context.Links&#34; (对于任何实体)。

网络

最后我们接收DTO的Web项目,并将这些DTO转换为ModelViews以显示在我们的视图中。

我们需要在Global.asax Application_Start中调用AutoFac来执行DI,以便我们可以使用我们的服务。

protected void Application_Start()
{
    ...
    Dependencies.RegisterDependencies();
    AutoMapperBootstrapper.Configuration();
    ...
}

public class Dependencies
{
    public static void RegisterDependencies()
    {
        var builder = new ContainerBuilder();

        builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();

        builder.RegisterModule(new ServiceModule());
        builder.RegisterModule(new EfModule());

        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    }
}

public class ServiceModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(Assembly.Load("MyProject.Service")).Where(t => t.Name.EndsWith("Service")).AsImplementedInterfaces().InstancePerLifetimeScope();
    }

}

public class EfModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType(typeof(MyDataContext)).As(typeof(IMyContext)).InstancePerLifetimeScope();
    }
}

如您所见,我们还调用AutoMapper来配置不同的地图。

然后在我们的控制器中我们有了这个。

public class LinksController : Controller
{
    private readonly ILinkService _linkService;
    public LinksController(ILinkService linkService)
    {
        _linkService = linkService;
    }

    public ActionResult Index()
    {
        var links = _linkService.GetAllLinks();
        return View(links.ToMappedPagedList<LinkDto, LinksListModelAdmin>());
    }

...

   public ActionResult Create(LinksEditModelAdmin insertedModel)
    {
        try
        {
            if (!ModelState.IsValid) return View("Create", insertedModel);

            var insertedEntity = Mapper.Map<LinkDto>(insertedModel);
            _linkService.Create(insertedEntity);

            return RedirectToAction("Index");
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

嗯,就是这样......我希望这对某些人有用......而且我希望我们可以对我们的问题有所帮助。

1)虽然我们将数据库与Web项目分开,但我们确实需要Web项目中的引用来初始化数据库并注入依赖项,这是正确的吗?

2)我们采用实体 - &gt; DTOs-&gt; ViewModels的方法是否正确?它的工作要多一些,但我们把所有东西分开了。

3)在Service项目中,当我们需要引用一个不同于我们在服务中使用的主实体的实体时,调用Context.Entity是否正确? 例如,如果我们需要从链接服务中的新闻实体中检索数据,那么调用&#34; Context.News.Where是否正确...&#34;?

4)我们确实在Automapper和EF代理方面遇到了一些问题,因为当我们调用&#34; Dbset&#34;为了检索数据,它获得了一个动态代理&#34;因此,Automapper无法找到合适的映射,因此,为了工作,我们必须在DataContext定义中设置ProxyCreationEnabled = false。这样我们就可以获得一个实体,以便将它映射到DTO。这会禁用LazyLoading,我们不介意,但这是一种正确的方法还是有更好的方法来解决这个问题?

提前感谢您的意见。

1 个答案:

答案 0 :(得分:1)

问题编号。 2

实体 - &GT; DTOs-&GT;的ViewModels?是好方法 因为你正在进行清洁分离,所以程序员可以轻松地协同工作。 设计ViewModel,Views和Controllers的人不必担心服务层或DTO实现,因为他将在其他开发人员完成实现时进行映射。

问题编号。 4

当标志ProxyCreationEnabled设置为false时,将不会使用创建实体的新实例来创建代理实例。这可能不是问题,但我们可以使用DbSet的Create方法创建代理实例。

using (var Context = new MydbEntities())  
{  
    var student = Context.StudentMasters.Create();  
}  

Create方法有一个重载版本,它接受泛型类型。这可用于创建派生类型的实例。

using (var Context = new MydbEntities())  
{  
    var student = Context.StudentMasters.Create<Student>();  
}  

如果实体的代理类型没有值(它与代理无关),Create方法只会创建实体类型的实例。 Create方法不会使用上下文对象添加或附加实体。

另外我读了一些内容,如果设置ProxyCreationEnabled = false,除非在父对象上调用Include方法,否则不会为某些父对象加载子元素。