1

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

我们在这里发布这不仅是为了帮助,而且要显示我们已经做了什么,以防万一它有助于某人。

我们正在开发ASP .NET MVC项目,首先使用MS SQL Server进行EF6代码。 我们已经将项目划分为3个主要层,我们将其划分为3个项目:模型,服务和网络。

  • 该模型创建实体并为数据库设置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个不同的实体。

模型

数据上下文

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的数据。 我们的疑惑之一是如果使用“Dbset.Find”或“Dbset.FirstOrDefault”是正确的,并且如果使用“Context.Links”(用于任何实体)。

WEB

最后,我们收到的DTO和改变这些的DTO到ModelViews在我们的视图来显示Web项目。

我们需要在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)是否正确,我们已经完成了我们的实体 - > DTOs-> ViewModels?这是一个更多的工作,但我们有一切分开。

3)在Service项目中,当我们需要引用与我们在服务中使用的主实体不同的实体时,调用Context.Entity是否正确? 例如,如果我们还需要从链接服务中的新闻实体中检索数据,调用“Context.News.Where ...”是否正确?

4)我们对Automapper和EF proxy有一些问题,因为当我们调用“Dbset”来检索数据时,它会得到一个“动态代理”对象,所以Automapper无法找到合适的地图,所以按顺序为了工作,我们必须在DataContext定义中设置ProxyCreationEnabled = false。这样我们可以得到一个实体,以便将其映射到DTO。这会禁用我们不介意的LazyLoading,但这是一种正确的方法,还是有更好的方法来解决这个问题?

在此先感谢您的意见。

+0

一个建议,多年来我发现所有这些围绕项目参考和图层的规则,或多或少是浪费时间。现在,我把精力集中在通过切片而不是分层来推动新的发展。结果相当简单的架构,imo https://vimeo.com/131633177 –

回答

1

对于问题编号。 2

实体 - > DTOs-> ViewModels?是很好的方法 因为你正在做干净的分离,程序员可以轻松地一起工作。 设计ViewModels,Views和Controller的人不必担心服务层或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方法不会将实体添加或附加到上下文对象。

另外我读了一些地方,如果你设置ProxyCreationEnabled = false子元素将不会加载一些父对象,除非Include方法在父对象上调用。