【发布时间】:2016-08-04 18:55:49
【问题描述】:
我们有 ASP MVC Web 项目。在阅读了 stackoverflow 中关于正确架构的大量文章和讨论后,我们决定采用以下一种方式,虽然不仅有一种正确的做事方式,这是我们决定的方式,但我们仍然有一些疑问.
我们在此发布此内容不仅是为了获得帮助,也是为了展示我们所做的工作,以防它对某人有所帮助。
我们正在使用 ASP .NET MVC 项目,首先使用 MS SQL Server 进行 EF6 代码。 我们将项目分为 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 多个不同的实体。
型号
数据上下文
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”的使用是否正确(对于任何实体)。
网络
最后是我们接收 DTO 并将这些 DTO 转换为 ModelView 以显示在我们的视图中的 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) 我们使用 Entities->DTOs->ViewModels 的方法是否正确?这有点多,但我们把所有东西都分开了。
3) 在Service项目中,当我们需要引用与我们在服务中使用的主实体不同的实体时,调用Context.Entity是否正确? 例如,如果我们还需要从链接服务中的新闻实体中检索数据,那么调用“Context.News.Where...”是否正确?
4) Automapper 和 EF 代理确实有一点问题,因为当我们调用“Dbset”来检索数据时,它会得到一个“Dynamic proxies”对象,所以 Automapper 找不到合适的映射,为了工作,我们必须在 DataContext 定义中设置 ProxyCreationEnabled = false 。通过这种方式,我们可以获得一个实体,以便将其映射到 DTO。这会禁用 LazyLoading,我们不介意,但这是正确的方法还是有更好的方法来解决这个问题?
提前感谢您的 cmets。
【问题讨论】:
-
一个建议,多年来我发现所有这些关于项目引用和层的规则或多或少都是浪费时间。这些天来,我把精力集中在通过切片而不是层来推动新的发展。结果是相当简单的架构,imo vimeo.com/131633177
标签: asp.net-mvc architecture entity-framework-6 automapper n-tier-architecture