[译]使用S#arp Lite开发精心设计的ASP.NET MVC应用程序
By robot-v1.0
本文链接 https://www.kyfws.com/applications/ssharparp-lite-the-basics-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 43 分钟阅读 - 21289 个词 阅读量 0[译]使用S#arp Lite开发精心设计的ASP.NET MVC应用程序
原文地址:https://www.codeproject.com/Articles/285158/Ssharparp-Lite-The-Basics
原文作者:Billy McCafferty
译文由本站 robot-v1.0 翻译
前言
S#arp Lite is an architectural framework for the development of well-designed, custom-built, ASP.NET MVC applications using NHibernate for data access.
S#arp Lite是一个体系结构框架,用于使用NHibernate进行数据访问的开发精心设计,定制的ASP.NET MVC应用程序.
什么是S#arp Lite?(What is S#arp Lite?)
S#arp Lite是一个体系结构框架,用于使用NHibernate进行数据访问的开发精心设计,定制的ASP.NET MVC应用程序.(S#arp Lite is an architectural framework for the development of well-designed, custom-built, ASP.NET MVC applications using NHibernate for data access.)
ASP.NET MVC 3是用于交付基于Web的应用程序的绝佳平台.但是,类似于ASP.NET,它没有提供有关如何在不同项目上下文中最佳使用它的特定准则.这当然是重点;存在提供一种灵活的平台,该平台可以在各种情况下使用,而不会偏向于模型-视图-控制器的基础之外的一种或另一种架构.这样做的好处是,您几乎可以随意构建MVC项目.缺点是,即使组织的项目都使用ASP.NET MVC,它们之间也可能几乎没有一致性.(ASP.NET MVC 3 is a terrific platform for delivering web-based applications. But, similar to ASP.NET, it does not provide specific guidelines for how to best use it in different project contexts. That’s certainly the point; it exists to provide a flexible platform which may be used in a variety of situations without being biased towards one architecture or another, beyond the fundamentals of Model-View-Controller. The benefit of this is that you can structure MVC projects almost anyway you’d like; the drawback is that it’s possible to have almost no consistency among your organization’s projects, even if they’re all using ASP.NET MVC.)
那边(That’s where) S#arp Lite(S#arp Lite) 来玩. S#arp Lite随附了三项主要资产,以提供用于开发设计良好的MVC应用程序的交钥匙解决方案:(comes in to play. S#arp Lite comes packaged with three primary assets to provide a turnkey solution for developing well-designed, MVC applications:)
- 一个项目模板,用于创建新的S#arp Lite项目,该项目模板已预先配置为使用NHibernate与您的数据库进行通信;(A project template to facilitate the creation of new S#arp Lite projects, pre-configured to communicate with your database using NHibernate;)
- 一组可重用的类库,它封装了基础结构问题(包括基础存储库);和(A set of reusable class libraries which encapsulates infrastructural concerns (including a base repository); and)
- 有关如何构建S#arp Lite项目的体系结构指南.(Architectural guidance on how to build out a S#arp Lite project.) 除了本文之外,还通过S#arp Lite发行包中包含的示例项目演示了体系结构指南.通过项目层之间的依存关系的方向也可以实施体系结构准则. (这将在下面更详细地讨论.)(In addition to this article, the architectural guidance is demonstrated via the sample project which has been included in the S#arp Lite release package. Architectural guidelines are also enforced by the direction of dependencies among the project layers. (This will be discussed in more detail below.))
总体目标是使您的开发团队能够更轻松地开发遵循公认原则的ASP.NET MVC应用程序,例如(The overall objective is to allow your development team to more easily develop ASP.NET MVC applications which adhere to well founded principles, such as) 域驱动设计(domain-driven design) 和(and) 测试驱动的开发(test-driven development) ;不会因基础设施设置而陷入困境,也不会牺牲该解决方案的长期可维护性和可扩展性.(; without being bogged down with infrastructural setup and without sacrificing long-term maintainability and scalability of the solution.)
很快,S#arp Lite公开的基本存储库类故意非常简单.基本存储库仅包括以下方法:(As a quick side, the base repository class which S#arp Lite exposes is purposefully very simplistic. The base repository only includes the following methods:)
- Get(ID):从提供ID的数据库返回一个实体,(Get(id): returns an entity from the database having the Id provided,)
- GetAll():返回一个IQueryable <>,它可以通过LINQ进一步过滤/转换,(GetAll(): returns an IQueryable<> which may be further filtered/transformed via LINQ,)
- SaveOrUpdate(entity):将实体持久保存到数据库中,并且(SaveOrUpdate(entity): persists an entity to the database, and)
- Delete(entity):从数据库中删除一个实体.(Delete(entity): deletes an entity from the database.) 保持基础存储库非常轻便可以大大减少膨胀,并更加强调使用LINQ从GetAll()检索结果.此外,您可以使用自己的代码对其进行扩展,以使其尽可能膨胀.我们将在稍后详细讨论.(Keeping the base repository very light greatly reduces bloat and places greater emphasis on the use of LINQ for retrieving results from GetAll(). Besides, you can extend it with your own code to bloat it as much as you’d like. We’ll discuss this in more detail a bit later.)
这是给谁的?(Who is this intended for?)
S#arp Lite的动机来自与许多团队(包括我自己的团队)的合作,这些团队一直在与(The motivation for S#arp Lite came from working with many teams (including my own) who had been developing projects with) S#arp体系结构(S#arp Architecture) .对许多人来说,S#arp Architecture只是一个太大的体系结构框架而无法轻易动手.当我曾经与正在考虑使用S#arp的团队讨论S#arp体系结构时,我总是建议他们的开发人员经验丰富并且精通诸如依赖关系反转,低级NHibernate和域驱动设计等主题.(. To many, S#arp Architecture is simply too big of an architectural framework to easily get your head around. When I used to discuss S#arp Architecture with teams who were considering using it, I would always suggest that their developers be very experienced and well versed with topics such as dependency inversion, low-level NHibernate, and domain-driven design.)
S#arp Lite的动机来自与许多使用S#arp Architecture开发项目的团队(包括我自己的团队)的合作.对于许多人来说,S#arp Architecture只是一个太大的架构框架而无法轻易掌控,并且与NHibernate紧密耦合.当我曾经与正在考虑使用S#arp的团队讨论S#arp体系结构时,我总是建议他们的开发人员经验丰富并且精通诸如依赖关系反转,低级NHibernate和域驱动设计等主题.(The motivation for S#arp Lite came from working with many teams (including my own) who had been developing projects with S#arp Architecture. To many, S#arp Architecture is simply too big of an architectural framework to easily get your head and is too tightly coupled to NHibernate. When I used to discuss S#arp Architecture with teams who were considering using it, I would always suggest that their developers be very experienced and well versed with topics such as dependency inversion, low-level NHibernate, and domain-driven design.)
业务的现实是,您的团队不太可能由都是这些主题的专家的所有高级开发人员组成. S#arp Lite旨在概括S#arp体系结构的基本价值,力争在对大型项目具有同等可扩展性的同时,吸引更多的受众.换句话说,您应该能够拥有一支切合实际的技能团队,并且仍然能够成功交付S#arp Lite应用程序.(The reality of business is that it’s not likely that your team will be made up of all senior level developers who are all experts in these topics. S#arp Lite is intended to epitomize the underlying values of S#arp Architecture, strive to be equally scalable to large projects, all while being tractable to a larger audience. In other words, you should be able to have a realistically skill-balanced team and still be able to successfully deliver a S#arp Lite application.)
建议将S#arp Lite用于任何中型的ASP.NET MVC项目.如果您的母亲和流行商店较小,那么使用较低层的应用程序设置可能会更好. S#arp Lite可以很好地扩展到大型项目.我们目前正在与其他六种系统集成的应用程序上有效地使用它…因此,它无疑可以应对更多挑战.(S#arp Lite is recommended for any mid-to-large sized ASP.NET MVC project. If you have a small mom & pop store, you’d likely be better off using a less-tiered application setup. S#arp Lite scales well to very large projects; we’re currently using it effectively on applications which integrate with a half dozen other systems…so it certainly holds up to meatier challenges.)
S#arp Lite项目是什么样的?(What does a S#arp Lite project look like?)
创建一个新的S#arp Lite项目非常简单:(Creating a new S#arp Lite project is trivially simple:)
-
下载并解压缩(Download and unzip the) 来自GitHub的S#arp Lite发布包(S#arp Lite release package from GitHub) .(.)
-
请按照(Follow the instructions within the) README.txt(README.txt) 使用Templify(一个出色的小工具)创建S#arp Lite项目.(to creation your S#arp Lite project with Templify (a brilliant little tool).) 创建S#arp Lite项目后,您将在根文件夹下找到以下目录结构:(After you’ve created a S#arp Lite project, you’ll find the following directory structure under the root folder:)
-
应用程式(app):此文件夹保存项目的源;即您获得报酬编写的代码.(: This folder holds the source of the project; i.e., the code that you’re getting paid to write.)
-
建立(build):此最初为空的文件夹是与构建相关的工件的占位符,例如您的"发布"文件夹,NAnt或MSBuild工件等.(: This initially empty folder is a placeholder for your build-related artifacts, such as your “publish” folder, NAnt or MSBuild artifacts, etc.)
-
docs(docs):此最初为空的文件夹包含您项目的所有文档.将它们保存在此处可以使您的所有文档都通过代码签入.(: This initially empty folder contains all of the documents for your project. Keeping them here keeps all of your docs checked in with the code.)
-
LIB(lib):此文件夹包含项目的所有DLL依赖项,例如log4net.dll,SharpLite.Domain.dll,System.Web.Mvc.dll等.(: This folder contains all of the DLL dependencies for your project, such as log4net.dll, SharpLite.Domain.dll, System.Web.Mvc.dll, etc.)
-
日志(logs):此最初为空的文件夹旨在容纳所有生成的日志文件.生成的项目的web.config使用此文件夹转储log4net日志.(: This initially empty folder is intended to hold any generated log files. The generated project’s web.config used this folder for dumping out log4net logs.)
-
工具(tools):此最初为空的文件夹旨在容纳团队可能需要在项目上进行工作的任何第三方安装文件或其他依赖项.例如,在这里我们存储项目所使用的Telerik ASP.NET MVC和NUnit的最新安装.拥有所有可安装的依赖项,并通过代码进行检入,可以更加轻松地快速启动和运行"新手".(: This initially empty folder is intended to hold any third party install files or other dependencies which the team may need to work on the project. For example, this is where we store the latest installation of Telerik ASP.NET MVC and NUnit, used by the project. Having all of your installable dependencies, checked in with the code, makes it much easier to get “the new guy” up and running quickly.) 自动生成的文件夹结构只是帮助保持数字资产和解决方案文件井井有条的一种手段./app文件夹中包含更多有趣的内容,该文件夹中包含您的项目的源代码.但是,在深入研究S#arp Lite项目的/app文件夹中的每个项目层之前,我们首先要了解整个体系结构.(The auto-generated folder structure is just a means to help keep your digital assets and solution files organized. The more interesting stuff is in the /app folder which houses the source code of your project. But before we delve into each of the project layers in the /app folder of a S#arp Lite project, let’s take a birds eye view of the overall architecture.)
上图反映了S#arp Lite项目的各个层,这些项目实现为单独的类库和ASP.NET MVC Web项目.将层放在单独的类库中可以使您在它们之间强制执行依赖方向.例如,由于YourProject.Tasks取决于YourProject.Domain,因此YourProject.Domain不能直接依赖YourProject.Tasks中的校准.这种单向依赖性有助于增强体系结构的组织方式.(The diagram above reflects the layers of a S#arp Lite project, implemented as separate class libraries and an ASP.NET MVC Web Project. Having the tiers in separate class libraries allows you to enforce the direction of dependency among them. For example, because YourProject.Tasks depends on YourProject.Domain, YourProject.Domain cannot have any direct dependencies on a calss within YourProject.Tasks. This singled-directional dependency helps to enforce how the architecture is to remain organized.)
上面的图描述了每一层的基本目的,但最好看一个示例项目,以更清楚地了解每一层的职责范围.因此,让我们检查一下S#arp Lite发行包中包含的MyStore示例应用程序的层.(While the diagram above describes the basic purpose of each layer, it’s most assistive to look at an example project to have a clearer understanding of the scope of responsibilities of each layer. Accordingly, let’s examine the tiers of the MyStore example application which was included in the S#arp Lite release package.)
检查MyStore示例应用程序的层(Examining the Layers of MyStore Sample Application)
包含在发行版zip中的MyStore示例应用程序演示了S#arp Lite在相当典型的CRUD(创建/读取/更新/删除)应用程序中的用法.它包括管理数据和关系,例如一对一,一对多,许多:以及父/子.让我们仔细看一下关系模型.(The MyStore sample application, included in the release zip, demonstrates the use of S#arp Lite for a fairly typical CRUD (create/read/update/delete) application. It includes managing data and relationships such as one:one, one:many, many:many, and parent/child. Let’s take a closer look at the relational model.)
上图表示在SQL Server数据库中实现的关系对象模型.这是一个具有基本关系的非常简单的模型,但是当我们将这种关系模型转换为应用程序的面向对象设计时,它仍然带来了许多有趣的讨论要点.(The diagram above represents the relational object model, as implemented within a SQL Server database. It’s a very simple model with basic relationships, but it still brings up a lot of interesting discussion points when we go to translate this relational model into the object-oriented design of our application.)
例如,每个客户实体都包含地址信息(在"客户"表中存储为StreetAddress和ZipCode);在域中,我们希望将地址信息提取到一个名为Address的单独对象中.将此信息作为单独的对象更容易,使我们可以向Address对象添加行为,同时将这些关注点与Customer对象保持分离,例如与USPS地址验证服务集成. (可以说,这样的服务将在独立的服务类中,但是您可以理解.)(For example, each customer entity contains address information (stored as StreetAddress and ZipCode in the Customers table); in the domain, we want the address information pulled out into a separate object called Address. Having this information as a separate object more easily allows us to add behavior to the Address object while keeping those concerns separate from the Customer object, such as integration with a USPS address validation service. (Arguably, such a service would be in a stand-alone service class, but you get the idea.))
再举一个例子,在我们的对象模型中,many:many_products_productCategories表不应具有类似名称的类.相反,我们希望"产品"具有"产品类别"列表,并且/或者反之亦然.(As another example, the many:many Products_ProductCategories table shouldn’t have a similarly named class in our object model; instead, we would expect Products to have a listing of ProductCategories and/or vice-versa.)
该示例应用程序包括一些示例,这些示例说明了如何几乎完全通过以下方式实现所有这些以及类本身的映射(The sample application includes examples of how all of this and mappings of the classes themselves have been achieved almost entirely via) 会议编码(coding-by-convention) .现在,让我们逐层查看示例应用程序以及有趣的地方.(. Let’s now look at the sample application, layer-by-layer, and interesting points along the way.)
MyStore.Domain(MyStore.Domain)
应用程序的这一层包含应用程序的核心.它代表了我们产品的核心领域.所有其他层的存在仅仅是为了满足用户与域交互的需求.在S#arp Lite项目中,域层包含四种类型的对象:(This layer of the application contains the heart of the application; it represents the core domain of our product. All the other layers exist simply to support the user’s need to interact with the domain. In a S#arp Lite project, the domain layer contains four types of objects:)
- 域对象(Domain Objects)
- 查询对象(Query Objects)
- 查询接口(Query Interfaces)
- 定制验证器(Custom Validators) 让我们依次回顾一下.(Let’s review each in turn.)
域对象(Domain Objects)
好了,所以也许这是一堆对象,但是它们都有相同的目的-它们的存在是为了实现我们应用程序的领域.它们由实体,值对象,服务(例如计算器),工厂,集合…组成,所有这些都组织成模块,通常表示为单独的命名空间. (我强烈推荐埃里克埃文斯的(*Alright, so maybe this is a whole bunch of types of objects, but they all have the same purpose - they exist to implement the domain of our application. They consist of entities, value objects, services (e.g., calculators), factories, aggregates...all organized into modules, usually expressed as separate namespaces. (I highly recommend Eric Evans'*) [域驱动设计(*Domain-Driven Design*)](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) ,吉米
尼尔森(Jimmy Nilsson)的(, Jimmy Nilsson’s) 应用域驱动的设计和模式(Applying Domain Driven Design and Patterns) 和(, and) 敏捷软件开发(Agile Software Development) 示例项目中有两个名称空间:" ProductMgmt"名称空间包含与产品管理相关的所有内容,而" root"名称空间则包含其他所有内容.您的项目可能还会有其他项目.(by Robert Martin to help guide you.) There are two namespaces in the sample project: the “ProductMgmt” namespace which contains everything related to product management, and the “root” namespace for, well, everything else. Your project will likely have others.)
现在,让我们看一下示例项目中的Customer类,作为一个实体示例.有一些重要的注意事项:(Let’s now take a look at the Customer class within the sample project as an example of an entity. There are some important items to note:)
•客户从实体(从SharpLite.Domain.dll)继承.实体基类A)表示该类是一个持久对象,该持久对象在数据库中具有关联的表,B)提供Id属性(在那里没有大的作用),并且C)便于将两个实体相互比较.如果两个实体具有相同的类型并具有相同的ID,则您将知道它们是同一对象.但是,如果您要比较两个"瞬态"实体,该怎么办?即两个尚未持久保存到数据库中的实体.再举一个例子,您将如何比较瞬态实体和已持久化的实体.(• Customer inherits from Entity (which is from SharpLite.Domain.dll). The Entity base class A) signals that this class is a persisted object which has an associated table in the database, B) provides an Id property (no biggie there), and C) facilitates comparing two entities to each other. If two entities are of the same type and have the same Id, then you know they’re the same object. But what if you’re comparing two “transient” entities; i.e., two entities which have not yet been persisted to the database. As another example, how would you go about comparing a transient entity to entities that have been persisted.)
为此,我们需要比较"域签名".域签名是使实体从业务角度来看独特的指纹.换句话说,如果没有ID属性,对象的哪些属性将使其可识别?查看Customer类,我们看到有两个用属性" DomainSignature"修饰的属性.此外,该类本身还带有属性" HasUniqueDomainSignature".这意味着不能存在两个具有相同名字和姓氏的对象. (这并非在所有情况下都适用;但是应该在应用程序的上下文中反映对象的域签名.)描述的属性包含在SharpLite.Domain.dll中,并支持对类的域签名的自动验证.因此,如果您尝试添加与现有客户的名字和姓氏相同的新客户,则验证消息将告知您这是不允许的.(For this we need to compare “domain signatures.” A domain signature is the fingerprint of what makes the entity unique from a business perspective. In other words, which property(s) of an object would make it identifiable without having an Id property? Looking at the Customer class, we see that there are two properties decorated with the attribute “DomainSignature.” Furthermore, the class itself is decorated with the attribute “HasUniqueDomainSignature.” This means that no two objects may exist having the same first and last name. (This will not be appropriate in all scenarios; but should reflect the domain signature of the object in the context of the application.) The described attributes are included in SharpLite.Domain.dll and support automatic validation of the class' domain signature. So if you try to add a new customer with the same first and last name as an existing customer, a validation message will let you know this is not allowed.)
[HasUniqueDomainSignature(...
public class Customer : Entity
{
[DomainSignature]
...
public virtual string FirstName { get; set; }
•客户类将来自客户表的地址信息封装为一个单独的地址类.有趣的是,NHibernate的" lolocious"映射(又称惯例代码)自动将相关的表列映射到该" component"对象中.(• The Customer class pulls encapsulates the address information from the Customers table into a separate Address class. Interestingly, NHibernate “loquacious” mapping (aka - code by convention) automatically maps the related table columns into this “component” object.)
•客户拥有带有受保护设置者的订单清单.集合在构造函数中初始化.这样做有两个原因:1)根据设计,该集合永远不会为null(这避免了避免许多对象引用异常),以及2)我们不必担心NHibernate丢失指向加载的原始集合的指针从数据库中.该集合"模式"仅仅是暴露/保护集合的良好实践.(• Customer has an IList of Orders with a protected setter; the collection is initialized in the constructor. This is done for two reasons: 1) by design, the collection will never be null (which avoids a lot of object reference exception avoidance), and 2) we don’t have to worry about NHibernate losing its pointer to the original collection loaded from the database. This collection “pattern” is simply good practice for exposing/protecting the collection.)
public class Customer : Entity
{
public Customer() {
Orders = new List();
}
public virtual IList Orders { get; protected set; }
•使用标准的.NET数据注释强制执行验证.通过.NET库都可以使用NHibernate.Validator或其他验证机制.而且,每当遇到限制时,您都可以简单地创建一个自定义验证器.(• Validation is enforced with standard, .NET data annotations. No need for NHibernate.Validator or other validation mechanism when it’s all available via the .NET library. And whenever you run into limitations, you can simply create a custom validator.)
查询对象(Query Objects)
通常,我们需要过滤从数据库返回的信息.例如,我们可能只想返回"活动"客户与数据库中的所有客户.出于性能原因,最好将尽可能多的过滤工作放在数据库的肩膀上.在使用LINQ提供程序之前,很难在域侧的筛选或数据库侧的筛选之间找到适当的平衡.但是,使用LINQ和IQueryable,可以在仍在数据库上执行的同时在域内开发过滤.辉煌!为了简化此操作,每个存储库(例如IRepository )都公开了返回IQueryable <>的方法GetAll().(Regularly, we need to filter information returned from the database. For example, we may just want to return “active” customers vs. all the customers in the database. For performance reasons, it’s obviously better to put as much filtering work on the shoulders of the database. Before LINQ providers, it was difficult to find the appropriate balance between filtering on the domain side or filtering on the database side. But with LINQ and IQueryable, filtering can be developed within the domain while it’s still executed on the database. Brilliant! To facilitate this, every repository (e.g., IRepository) exposes the method GetAll() which returns IQueryable<>.)
这样的惊人的副作用是,我们可以避免使用专门的"存储库"方法,这些方法的存在仅仅是为了隐藏基础数据访问机制的细节. S#arp Lite项目中有两种查询对象:(The abso-friggin-spectacular side effect of this is that we can avoid having specialty “repository” methods which exist simply to hide away the details of the underlying data-access mechanism. There are two kinds of query objects in a S#arp Lite project:)
规范查询对象(Specification Query Objects):规范查询对象获取一个列表,并根据某些条件将结果过滤到较小的列表中.在示例项目中,规范查询类MyStore.Domain.FindActiveCustomers提供了IQueryable <>的扩展方法,并带有传入该方法的任何过滤器参数.或者,查询对象可以是POCO类,接受IQueryable <>并相应地进行过滤.将规范查询对象设置为扩展的好处是,尽管它具有附加的间接性,但是可以链接多个查询,同时利用IQuerable <>的延迟查询(即,仅将一个查询发送到数据库,即使您链接多个查询也是如此.(: Specification query objects take a list and filter the results down to a smaller list, based on some criteria. In the sample project, the specification query class, MyStore.Domain.FindActiveCustomers, provides an extension method to IQueryable<> with any filter parameters passed in to the method. Alternatively, the query object could be a POCO class, accepting an IQueryable<> and filtering, accordingly. The benefit to setting up the specification query object as an extension, albeit, with additional indirection, is that multiple queries may be chained, all while taking advantage of IQuerable<>’s delayed querying (i.e., only one query will be sent to the database even if you chain multiple queries.)
public static class FindActiveCustomersExtension
{
public static IQueryable FindActiveCustomers(this IQueryable customers) {
return customers.FindActiveCustomers(MINIMUM_ORDERS_TO_BE_CONSIDERED_ACTIVE);
}
public static IQueryable FindActiveCustomers(this IQueryable customers, int minimumOrders) {
return customers.Where(c =>
c.Orders.Count >= minimumOrders);
}
private const int MINIMUM_ORDERS_TO_BE_CONSIDERED_ACTIVE = 3;
}
对我而言,真正的好处是查询对象可以生活在域中,并且可以作为域的一等公民进行测试,而不会对基础数据访问层(无论是NHibernate,Entity Framework)引入任何依赖关系等).(To me, the real beauty in this is that the query object may live in the domain, and be tested as a first class citizen of the domain, without introducing any dependencies to the underlying data-access layer (whether that be NHibernate, Entity Framework, etc.).)
报表查询对象(Report Query Objects):报表查询对象包含一个列表,必要时进行过滤,将结果转换为DTO或DTO列表并返回.假设您的应用程序中有一个摘要仪表板;例如,一个页面,显示每个客户下了多少个订单以及每个客户最常购买的产品是什么.在这种情况下,我们最终希望获得一个DTO列表,其中包含每个客户的姓名,他/她的订单数以及他/她喜欢的产品.有几种解决方法:(: Report query objects take a list, filtering if necessary, transforming and returning the results as a DTO or list of DTOs. Imagine that you have a summary dashboard in your application; e.g., a page which shows how many orders each customer has placed and what is each customer’s most frequently purchased product. In this scenario, we’d ultimately like a list of DTOs containing each customer’s name, his/her order count, and his/her favorite product. There are a few options to tackling this:)
- 创建一个数据库存储过程,将结果绑定到DTO列表(从而将处理逻辑放到数据库中),(Create a DB stored procedure, binding the results to the DTOs list (and thus put processing logic onto the DB),)
- 使用NHibernate标准,HQL或命名查询来检索结果(并将您的数据访问代码与NHibernate紧密耦合),(Use NHibernate Criteria, HQL or named query to retrieve the results (and tightly couple your data-access code to NHibernate),)
- 在域侧遍历对象模型以整理信息(您可以说n + 1吗?),或者(Traverse the object model on the domain side to collate the information (can you say n+1?), or)
- 使用干净,简单,与数据访问无关的LINQ(并将其保留在域中).(Use clean and simple, data-access agnostic LINQ (and keep it in the domain).) 如果您猜得出我倾向于哪一个,则得2分.让我们来看一个MyStore.Domain.Queries.QueryForCustomerOrderSummariesExtension示例:(2 points if you guess which one I’m leaning towards. Let’s look MyStore.Domain.Queries.QueryForCustomerOrderSummariesExtension for an example:)
public static class QueryForCustomerOrderSummariesExtension
{
public static IQueryable QueryForCustomerOrderSummaries(this IQueryable customers) {
return from customer in customers
select new CustomerOrderSummaryDto() {
FirstName = customer.FirstName,
LastName = customer.LastName,
OrderCount = customer.Orders.Count
};
}
}
再次,这样做的好处是它可以驻留在域层中并充当可重用的报告查询,而不会对基础数据访问层引入依赖性.(Again, the advantage of this is that it can live within the domain layer and act as a reusable reporting query without introducing dependencies to the underlying data-access layer.)
在如何使用查询对象以及它们在哪里时,您具有很大的灵活性.例如,您可以使用临时报告查询(即未由类封装的LINQ查询),该查询位于任务层的方法内.尽管我不建议这样做,但您甚至可以在控制器的方法中使用即席查询.因此,提供的示例仅仅是特定方法的示例.最重要的是作为一个团队达成共识,即如何封装和组织查询对象.在示例项目中,查询被封装为查询对象,并存储在"查询"文件夹中-每个命名空间一个文件夹.(You have a lot of flexibility on how you use query objects and where they live. For example, you could use an ad-hoc report query (i.e., a LINQ query not encapsulated by a class) which lives within a method in the tasks layer. Although I’d advise against it, you could even use an ad-hoc query within a controller’s method. So the provided samples are just that, samples of a particular approach. What’s most important is to agree as a team how you’ll encapsulate and organize query objects. In the sample project, queries are encapsulated as query objects and stored within a “Queries” folder - one folder per namespace.)
查询接口(Query Interfaces)
在里面(In the)**非常(very)**您需要直接利用数据访问机制而不是LINQing IQueryable的不太可能发生的事件,域层也可能包含任何查询接口,这些接口定义了要由数据访问层实现的查询. (这类似于在S#arp体系结构中创建自定义存储库接口.)(unlikely event that you need to leverage the data-access mechanism directly, instead of LINQing IQueryable, the domain layer may also contain any query interfaces which define a query to be implemented by the data-access layer. (This is akin to creating custom repository interfaces in S#arp Architecture.))
这种方法的缺点有三方面:(The disadvantages to this approach are three-fold:)
-
它为开发人员引入了间接层,(It introduces a layer of indirection to the developer,)
-
它更紧密地将您的代码耦合到基础数据访问层(因为您的某些应用程序逻辑现在位于数据访问层中),并且(It more tightly couples your code to the underlying data-access layer (since some of your application’s logic is now in the data-access layer), and)
-
单元测试查询变得更加棘手,因为您需要内存或实时数据库来测试实现.(It becomes trickier to unit test the query since you need an in-memory or live database to test the implementation.) 但是,我可以预见,如果您有一个非常复杂的查询,需要利用NHibernate分离查询,或者根本无法通过LINQ进行操作,那么可能有必要这样做.因此,需要采取三个步骤来支持查询:(But, I can foresee that there may be a situation where this is necessary if you have a very complicated query, need to leverage NHibernate detached queries, or simply can’t do what needs to be done via LINQ. Accordingly, three steps would need to be taken to support the query:)
-
在YourAppProject.Domain(例如,MyStore.Domain.ProductMgmt.Queries.IQueryForProductOrderSummaries.cs)中定义查询接口,(Define the query interface in YourAppProject.Domain (e.g., MyStore.Domain.ProductMgmt.Queries.IQueryForProductOrderSummaries.cs),)
-
在YourAppProject.NHibernateProvider中实现具体的查询类(例如,MyStore.NHibernateProvider.Queries.QueryForProductOrderSummaries.cs),以及(Implement the concrete query class in YourAppProject.NHibernateProvider (e.g., MyStore.NHibernateProvider.Queries.QueryForProductOrderSummaries.cs), and)
-
向IoC注册实现以解析对接口的请求(例如MyStore.Init.DependencyResolverInitializer).(Register the implementation with the IoC to resolve requests to the interface (e.g., MyStore.Init.DependencyResolverInitializer).) 显然,它不如使用"规范"和"报表查询对象"那样干净,但在绝对必要时可用.(Obviously, not as clean as using Specification and Report Query Objects, but available if absolutely necessary.)
定制验证器(Custom Validators)
如前所述,S#arp Lite使用.NET的数据注释来支持验证. (您可以使用其他东西,例如(As discussed previously, S#arp Lite uses .NET’s data annotations for supporting validation. (You could use something else, like) NHibernate.Validator(NHibernate.Validator) 如果需要,则将数据注释直接添加到实体类,但是如果您认为实体也不应充当表单验证对象,则可以将其添加到表单DTO.(if you prefer.) The data annotations are added directly to entity classes, but could instead be added to form DTOs if you feel that entities shouldn’t act as form validation objects as well.)
有时,数据注释的功能不足以满足您的域需求;例如,如果您要比较两个属性.因此,您可以开发(Sometimes, data annotations aren’t powerful enough for the needs of your domain; e.g., if you want to compare two properties. Accordingly, you can develop) 定制验证器(custom validators) 并将它们存储在" Validators"文件夹中.如果自定义验证器是特定于某个类的,并且从未重用,那么我通常将自定义验证器类作为私有子类添加到使用它的类中.这样,特定于类的验证器被巧妙地隐藏起来,只有需要它的类才能访问. S#arp Lite使用自定义验证器通过其域签名确定对象是否与现有对象重复:\ SharpLiteSrc \ app \ SharpLite.Domain \ Validators \ HasUniqueDomainSignatureAttribute.cs.(and store them within a “Validators” folder. If the custom validator is specific to a class, and never reused, then I’ll usually just add the custom validator class as a private subclass to the class which uses it. In this way, the class-specific validator is neatly tucked away, only accessible by the class which needs it. S#arp Lite uses a custom validator to determine if an object is a duplicate of an existing object, using its domain signature: \SharpLiteSrc\app\SharpLite.Domain\Validators\HasUniqueDomainSignatureAttribute.cs.)
MyStore.Tasks(MyStore.Tasks)
应用程序的这一层包含任务协调逻辑,对例如来自表示层中的控制器发送的命令做出反应. (此层也称为(This layer of the application contains the task-coordination logic, reacting to commands sent from, e.g., a controller in the presentation layer. (This layer is also described as a) 服务层(Service Layer) 在马丁`福勒(Martin Fowler)的(in Martin Fowler’s) PoEAA(PoEAA) .)例如,假设您的应用程序已与许多其他应用程序集成.该层将与所有其他应用程序通信(最好通过接口),整理信息,并将其传递给域层,以对数据执行域逻辑.举一个简单的例子,如果您的域层包含某种FinancialCalculator类,则任务层将从存储库或其他来源收集计算器所需的信息,并将数据通过方法传递给FinancialCalculator.(.) For example, let’s assume that your application integrates with a number of other applications. This layer would communicate with all of the other applications (preferably via interfaces), collating the information, and handing it off to the domain layer for performing domain logic on the data. As a simpler example, if your domain layer contains some kind of FinancialCalculator class, the tasks layer would gather the information needed by the calculator, from repositories or other sources, and pass the data via a method to FinancialCalculator.)
作为次要职责,任务层将数据(如视图模型,DTO或实体)返回到表示层.例如,如果登录用户具有足够的权限,则表示层可能需要显示客户列表以及"创建/编辑/删除"按钮.任务层将获得要显示的客户列表,并确定用户具有哪些安全性访问权限;然后,它将返回一个视图模型,该视图模型包含客户列表以及描述用户是否有权修改数据的bool(或安全对象).(As a secondary responsibility, the tasks layer returns data, as view-models, DTOs, or entities, to the presentation layer. For example, the presentation layer may need to show a listing of customers along with Create/Edit/Delete buttons if the logged in user has sufficient rights to do so. The tasks layer would get the listing of customers to show and would determine what security access the user has; it would then return a view model containing the customers listing along with a bool (or security object) describing if the user has rights to modify the data.)
重要的是要注意在任务层中发现的逻辑与在域层中发现的逻辑之间的区别.任务层应包含最小的逻辑,以协调服务(例如,存储库,Web服务等)和域层(例如,计算器服务)之间的活动.可以将任务层视为有效的老板(存在吗?)^老板有助于促进团队之间的沟通,并告诉团队成员该做什么,但自己却不做.(It’s important to note the difference between the logic found within the tasks layer and that found within the domain layer. The tasks layer should contain minimal logic to coordinate activities among services (e.g., repositories, web services, etc.) and the domain layer (e.g., calculator services). Think of the tasks layer as an effective boss (does that exist?)…the boss helps to facilitate communications among the team and tells the team members what to do, but doesn’t do the job itself.)
任务层包含两种对象:(The tasks layer contains two kinds of objects:)
- 任务对象(Task Objects)
- 查看模型(View Models)
任务对象(Task Objects)
这些都是任务本身.最常见的任务是协调CUD逻辑(CRUD不读取).实际上,它是如此普遍,以至于S#arp Lite项目都包含一个(完全可自定义的)BaseEntityCudTasks类来封装这种常见需求.查看示例项目,您将看到如何扩展和使用BaseEntityCudTasks.例如,在MyStore.Tasks.ProductMgmt.ProductCudTasks中.(These are the tasks themselves. The most common kind of task is coordinating CUD logic (CRUD without the read). It’s so common, in fact, that S#arp Lite projects includes a (completely customizable) BaseEntityCudTasks class to encapsulate this common need. Looking at the sample project, you can see how BaseEntityCudTasks is extended and used; e.g., within MyStore.Tasks.ProductMgmt.ProductCudTasks.)
随着项目的发展,任务层的职责也将不可避免地增长.例如,在与多个外部应用程序集成的当前项目中,任务类从Primavera 6中提取计划信息,从Prism中提取成本信息,并通过存储库从数据库中提取本地数据.然后,它将所有这些信息传递到位于域中的MasterReportGenerator类.因此,尽管任务类并非无关紧要,但它只是从各种来源提取数据,而将其留给域进行繁重的数据处理.(As the project grows, the task-layer responsibilities will inevitably grow as well. For example, on a current project which integrates with multiple external applications, a task class pulls schedule information from Primavera 6, cost information from Prism, and local data from the database via a repository. It then passes all of this information to a MasterReportGenerator class which resides in the domain. Accordingly, although the task class is non-trivial, it’s simply pulling data from various sources, leaving it up to the domain to the heavy processing of the data.)
重要的是要注意,任务对象的服务依赖项(存储库,Web服务,查询接口等)应通过以下方式注入(It’s important to note that a task object’s service dependencies (repositories, web services, query interfaces, etc.) should be injected via) 依赖注入(dependency injection) .这有助于对任务对象进行单元测试的能力(. This facilitates the ability to unit test the task objects with) 存根/嘲笑(stubbed/mocked) 服务.使用MVC 3,设置依赖项注入非常简单,并使定义任务对象依赖项变得轻而易举:(services. With MVC 3, setting up dependency injection is very simple and makes defining your task objects dependencies a breeze:)
public ProductCategoryCudTasks(IRepository<ProductCategory> productCategoryRepository)
: base(productCategoryRepository) {
_productCategoryRepository = productCategoryRepository;
}
在这里,我们看到ProductCategoryCudTasks类需要将IRepository 注入其中,该类将在运行时由IoC容器或由您在进行单元测试时提供.(Here we see that the ProductCategoryCudTasks class requires a IRepository injected into it, which will be provided at runtime by the IoC container or by you when unit testing.)
查看模型(View Models)
视图模型类封装了要显示给用户的信息.没说(A view model class encapsulates information to be shown to the user. It doesn’t say)**怎么样(how)**仅应显示数据(the data should be displayed, only)**什么(what)**数据应显示.通常,它还将包括表示层的支持信息,然后决定(data should be displayed. Frequently, it’ll also include supporting information for the presentation layer to then decide)**怎么样(how)**显示信息;例如,权限信息.(the information is displayed; e.g., permissions information.)
关于视图模型类应该放置在何处的争论很多.在我的项目中,我将它们保留在任务层中,每个命名空间都有一个ViewModels文件夹.但是可以说,视图模型类可以存在于单独的类库中.这是由您的团队在项目开始时决定的.(There’s a lot of debate about where view model classes should reside. In my projects, I keep them in the tasks layer, with one ViewModels folder per namespace. But arguably, view model classes could live in a separate class library; that’s for your team to decide at the beginning of a project.)
MyStore.Web(MyStore.Web)
这里没有太多要说的. S#arp Lite项目将所有现成的MVC 3功能用于表示层,默认使用Razor视图引擎,如果需要,可以更改.该层中唯一的S#arp Lite-ism(总共一个词)如下:(There’s not much to say here. A S#arp Lite project uses all out-of-the-box MVC 3 functionality for the presentation layer, defaulting to Razor view engines, which you may change if preferred. The only S#arp Lite-isms (totally a word) in this layer are as follows:)
- MyStore.Web.Global.asax调用(MyStore.Web.Global.asax invokes)
DependencyResolverInitializer.Initialize();
初始化IoC容器(如下所述),(to initialize the IoC container (discussed below),) - MyStore.Web.Global.asax使用SharpModelBinder充当首选的表单/模型绑定器,并且(MyStore.Web.Global.asax uses SharpModelBinder to act as the preferred form/model binder, and)
- Web.config包含一个HttpModule以利用(Web.config includes an HttpModule to leverage a) 每个请求会话,NHibernate HTTP模块(session-per-request, NHibernate HTTP module) ,位于S#arp Lite源中的\ SharpLiteSrc \ app \ SharpLite.NHibernateProvider \ Web \ SessionPerRequestModule.cs中.(, found within the S#arp Lite source at \SharpLiteSrc\app\SharpLite.NHibernateProvider\Web\SessionPerRequestModule.cs.) SharpModelBinder扩展了基本表单/模型绑定,并具有填充关系的功能.例如,假设您有一个Product类,它与ProductCategory的关系为多:多.编辑产品时,该视图可能包含用于将产品与一个或多个产品类别相关联的复选框列表. SharpModelBinder在表单中查找此类关联,并在发布到控制器时填充关系.即,发布到控制器的产品将填充其ProductCategories,其中每个选中的复选框都包含一个ProductCategory.您可以以MyStore.Web/Areas/ProductMgmt/Views/Products/Edit.cshtml为例.(SharpModelBinder extends the basic form/model binding with capabilities to populate relationships. For example, suppose you have a Product class with a many:many relationship to ProductCategory. When editing the Product, the view could include a list of checkboxes for associating the product with one or more product categories. SharpModelBinder looks for such associations in the form and populates the relationships when posted to the controller; i.e., the Product which gets posted to the controller will have its ProductCategories populated, containing one ProductCategory for each checkbox that was checked. You can take a look at MyStore.Web/Areas/ProductMgmt/Views/Products/Edit.cshtml as an example.)
像任务对象一样,控制器也通过注入来接受依赖关系.例如MyStore.Web.Areas.ProductMgmt.Controllers.ProductsController.cs:(Like task objects, controllers also accept dependencies via injection; e.g. MyStore.Web.Areas.ProductMgmt.Controllers.ProductsController.cs:)
public ProductsController(IRepository<Product> productRepository,
ProductCudTasks productMgmtTasks,
IQueryForProductOrderSummaries queryForProductOrderSummaries) {
_productRepository = productRepository;
_productMgmtTasks = productMgmtTasks;
_queryForProductOrderSummaries = queryForProductOrderSummaries;
}
在上面的示例中,控制器需要从IoC容器传递到其构造函数的IRepository ,ProductCudTasks和IQueryForProductOrderSummaries实例. IQueryForProductOrderSummaries是使用在域中定义的查询接口满足数据访问层特定需求的示例.这是一个非常例外的情况,仅出于幻想目的而被包括在内.您几乎总是能够改为使用规范和报告查询对象^或者直接从IRepository .GetAll()开始使用LINQ.(In the example above, the controller requires an instance of IRepository, ProductCudTasks, and IQueryForProductOrderSummaries passed to its constructor from the IoC container. IQueryForProductOrderSummaries is an example of using a query interface, defined in the domain, for providing data-access layer specific needs. It’s a very exceptive case and has only been included for illustrive purposes. You’d almost always be able to use specification and report query objects instead…or simply LINQ right off of IRepository.GetAll().)
如果您想了解有关ASP.NET MVC 3中的依赖注入的更多信息,请查看Brad Wilson的(If you’d like to learn more about dependency injection in ASP.NET MVC 3, check out Brad Wilson’s) 系列文章(series of posts) 就此主题而言.为了进一步了解Web层开发的基础知识,Steve Sanderson的(on the subject. And for learning more about the basics of developing in the web layer, Steve Sanderson’s) Pro ASP.NET MVC 3框架(Pro ASP.NET MVC 3 Framework) 是一本好书.(is a great read.)
MyStore.Init(MyStore.Init)
这个几乎贫乏的层有一个职责:执行通用的应用程序初始化逻辑.具体来说,S#arp Lite项目随附的初始化代码会初始化IoC容器((This nearly anemic layer has one responsibility: perform generic, application initialization logic. Specifically, the initialization code included with a S#arp Lite project initializes the IoC container () 结构图(StructureMap) )并调用NHibernate会话工厂的初始化.可以说,这一层是如此之薄,以至于MyStore.Web可以轻松地承担其责任.将初始化代码提取到单独的类库中的最大好处是MyStore.Web需要的依赖项少得多.请注意,MyStore没有引用NHibernate.dll或StructureMap.dll.因此,从Web层到这些依赖项的耦合很少(即没有)…我们喜欢这样.除其他外,这可以防止任何人从控制器调用NHibernate特定功能.反过来,这也使控制器与底层数据访问机制之间也保持高度分离.() and invokes the initialization of the NHibernate session factory. Arguably, this layer is so thin that its responsibilities could easily be subsumed by MyStore.Web. The great advantage to pulling the initialization code out into a separate class library is that MyStore.Web requires far fewer dependencies. Note that MyStore has no reference to NHibernate.dll nor to StructureMap.dll. Accordingly, there is very little coupling (i.e., none) to these dependencies from the web layer…we like that. Among other things, this prevents anyone from invoking an NHibernate-specific function from a controller. This, in turn, keeps the controllers very decoupled from the underlying data-access mechanism as well.)
MyStore.NHibernateProvider(MyStore.NHibernateProvider)
NHibernate提供程序层是我们游览S#arp Lite项目各层的下一站.使用S#arp体系结构,使用自定义存储库和命名查询,该层通常会变得相当大.通过在IQueryable <>上交替使用查询对象和LINQ,此类库应保持精简.该类库包含三种对象:(The next stop on our tour of the layers of a S#arp Lite project is the NHibernate provider layer. With S#arp Architecture, this layer would frequently get quite sizable with custom repositories and named queries. With the alternative use of query objects and LINQ on IQueryable<>, this class library should remain very thin. This class library contains three kinds of objects:)
- NHibernate初始化程序,(NHibernate Initializer,)
- NHibernate约定,(NHibernate Conventions,)
- 映射覆盖,以及(Mapping Overrides, and)
- (偶尔)查询实现.((very occasionally) Query Implementations.) 让我们依次看一下.(Let’s look at each in turn.)
NHibernate初始化器(NHibernate Initializer)
NHibernate 3.2.0引入了用于配置和映射类的内置fluent API,绰号为(NHibernate 3.2.0 introduces a built-in fluent API for configuration and mapping classes, nicknamed) NHibernate的" Loquacious" API(NHibernate’s “Loquacious” API) .这是直接的(. This is a direct) 对流利的NHibernate的侮辱(affront to Fluent NHibernator) (我真的很喜欢它^对James Gregory表示衷心的感谢)我感觉这些功能已经过时了,而NHibernate已经内置了这些功能. NHibernate 3.2的Loquacious API尚不如Fluent NHibernate强大,但随着更多的(which (as much as I have truly loved it…a sincere thank you to James Gregory) I feel is headed for obsolescence with these capabilities now being built right in to NHibernate. NHibernate 3.2’s Loquacious API isn’t yet as powerful as Fluent NHibernate, but will get there soon as more of) 符合(ConfORM) 已移植到Loquacious API.继续演出…(is ported over to Loquacious API. On with the show…)
有一个带有S#arp Lite项目的NHibernate初始化类.例如MyStore.NHibernateProvider.NHibernateInitializer.cs.此类设置连接字符串(来自web.config),设置方言,告诉NHibernate在哪里找到映射的类,并调用约定设置(下面讨论).初始化NHibernate非常昂贵,应仅在应用程序启动时执行一次.因此,如果决定使用另一个IoC容器切换出IoC初始化代码(位于MyStore.Init中),请注意这一点.(There is one NHibernate initialization class with a S#arp Lite project; e.g., MyStore.NHibernateProvider.NHibernateInitializer.cs. This class sets the connection string (from web.config), sets the dialect, tells NHibernate where to find mapped classes, and invokes convention setup (discussed next). Initializing NHibernate is very expensive and should only be performed once when the application starts. Accordingly, take heed of this if you decide to switch out the IoC initialization code (in MyStore.Init) with another IoC container.)
NHibernate约定(NHibernate Conventions)
约定的好处在于,我们不再需要包含用于将类映射到数据库的类映射(HBM或其他方式).我们只定义约定,遵守这些约定,NHibernate知道去哪一个表/列. S#arp Lite项目预先包装了以下内容,(The beauty of conventions is that we no longer need to include a class mapping (HBM or otherwise) for mapping classes to the database. We simply define conventions, adhere to those conventions, and NHibernate knows which table/columns to go to for what. S#arp Lite projects come prepackaged with the following,)**可定制的(customizable)**约定:(conventions:)
- 表名是实体名称的复数形式.例如,如果实体是客户,则表是客户.(Table names are a plural form of the entity name. E.g., if the entity is Customer, the table is Customers.)
- 每个实体都有一个ID属性映射到" Id"身份列(可以轻松更改为HiLo,Guid或其他方式).(Every entity has an Id property mapped to an “Id” identity column (which can easily be changed to HiLo, Guid, or otherwise).)
- 基本类型列名称与属性相同.例如,如果属性为FirstName,则列名称为FirstName.(Primitive type column names are the same name as the property. E.g., if the property is FirstName, the column name is FirstName.)
- 外键(关联)是后缀为" Fk"的属性的名称.例如,如果属性为Order.WhoPlacedOrder,则列名(在Orders表中)为WhoPlacedOrderFk,并带有相应类型表的外键(例如,Customers).(Foreign keys (associations) are the name of the property suffixed with “Fk.” E.g., if the property is Order.WhoPlacedOrder, the column name (in the Orders table) is WhoPlacedOrderFk with a foreign key to the respective type’s table (e.g., Customers).) 在S#arp Lite项目中,通常只有一个约定设置类.例如MyStore.NHibernateProvider.Conventions. “开箱即用"的唯一不受支持的约定是多对多关系,我们将在下文中讨论更多关系.(There is typically just one convention-setup class in a S#arp Lite project; e.g., MyStore.NHibernateProvider.Conventions. The only convention that isn’t supported “out of the box” is a many:many relationship, which we’ll discuss more below.)
映射替代(Mapping Overrides)
有时候,惯例没有成立.示例包括:(There are times when conventions don’t hold up. Examples include:)
- 多对多关系(Many-to-many relationships,)
- 枚举作为属性类型,(Enum as a property type,)
- 不遵守(您的)约定的旧版数据库,以及(Legacy databases which don’t stick to (your) conventions, and)
- 任何时候由于某种原因而没有遵守约定.(Anytime a convention is not followed for one reason or another.) 从好的方面来看,这不是很多情况…但是我们需要能够处理异常.约定的任何例外都定义为"映射替代”.替代的示例可以在MyStore.NHibernateProvider/Overrides中找到.为了使事情变得容易,如果需要添加替代,只需实现MyStore.NHibernateProvider.Overrides.IOverride并包括替代代码即可. MyStore.NHibernateProvider.Conventions类在程序集中查找实现IOverride的所有类,然后依次应用它们.通常,我为每个需要重写的实体创建一个重写类.(On the upside, this isn’t too many cases…but we need to be able to handle the exceptions. Any exceptions to the conventions are defined as “mapping overrides.” Examples of overrides may be found in MyStore.NHibernateProvider/Overrides. To make things easy, if an override needs to be added, simply implement MyStore.NHibernateProvider.Overrides.IOverride and include your override code. The MyStore.NHibernateProvider.Conventions class looks through the assembly for any classes which implements IOverride and applies them in turn. As a rule, I create one override class for each respective entity which requires an override.)
查询实施(Query Implementations)
最后,.NHibernateProvider层包含任何特定于NHibernate的查询,它们是.Domain层中定义的各个查询接口的实现.在97.6831%的时间中,这是不必要的,因为通过LINQ进行查询(首选IQueryable <>)是首选的查询方法.但是在极少数情况下,您需要使用NHibernate Criteria,HQL或其他方式实现查询,这是容纳它的层. MyStore.NHibernateProvider.Queries.QueryForProductOrderSummaries中包含一个示例.(Lastly, the .NHibernateProvider layer contains any NHibernate-specific queries, which are implementations of respective query interfaces defined in the .Domain layer. 97.6831% of the time, this will not be necessary as querying via LINQ, off of IQueryable<>, is the preferred approach to querying. But in the rare case that you need to implement a query using NHibernate Criteria, HQL, or otherwise, this is the layer to house it. A sample has been included as MyStore.NHibernateProvider.Queries.QueryForProductOrderSummaries.)
MyStore.Tests(MyStore.Tests)
在我们对S#arp Lite项目的各层浏览的最后,是.Tests层.该层包含该项目的所有单元测试.开箱即用两个单元测试生成S#arp Lite项目:(At the end of our tour of the layers of a S#arp Lite project is the .Tests layer. This layer holds all of the unit tests for the project. S#arp Lite projects are generated with two unit tests out of the box:)
- MyStore.Tests.NHibernateProvider.MappingIntegrationTests.CanGenerateDatabaseSchema():此单元测试将初始化NHibernate并生成SQL以反映这些映射.这是一个很好的测试,可以验证类是否按预期映射到数据库.即,您可以查看生成的SQL(在NUnit的"文本输出"选项卡中)以验证如何映射类.作为附带好处,您可以复制生成的SQL并运行它以对数据库进行修改.最后,单元测试将生成的SQL保存到/app/MyStore.DB/Schema/UnitTestGeneratedSchema.sql中,以供其他参考.(MyStore.Tests.NHibernateProvider.MappingIntegrationTests.CanGenerateDatabaseSchema(): This unit tests initializes NHibernate and generates SQL to reflect those mappings. This is a great test to run to verify that a class is being mapped to the database as expected; i.e., you can look at the generated SQL (in the Text Output tab in NUnit) to verify how a class is being mapped. As a side-benefit, you can copy the generated SQL and run it to make modifications to the database. Finally, the unit tests saves the generated SQL into /app/MyStore.DB/Schema/UnitTestGeneratedSchema.sql for additional reference.)
- MyStore.Tests.NHibernateProvider.MappingIntegrationTests.CanConfirmDatabaseMatchesMappings():此单元测试将初始化NHibernate并验证每个实体是否成功映射到数据库.如果您缺少一列,该测试将让您知道.它并不能测试所有内容,例如多对多关系,但肯定可以测试其他所有内容的97.6831%.(MyStore.Tests.NHibernateProvider.MappingIntegrationTests.CanConfirmDatabaseMatchesMappings(): This unit tests initializes NHibernate and verifies that every entity maps successfully to the database. If you have a missing column, this test will let you know. It doesn’t test everything, such as many-to-many relationships, but certainly 97.6831% of everything else.) 有关测试驱动开发的介绍,请阅读Kent Beck的(For an introduction to test-driven development, read Kent Beck’s) 测试驱动开发:通过示例(Test Driven Development: By Example) .更进一步,与Gerard Meszaros'(. Going a step further, go with Gerard Meszaros') xUnit测试模式(xUnit Test Patterns) 和迈克尔`费瑟斯(Michael Feathers)(and Michael Feathers') 与旧版代码有效合作(Working Effectively with Legacy Code) .(.)
在S#arp体系结构中,SQLLite用于提供用于测试自定义存储库方法的内存数据库.由于自定义存储库大多数都已被废弃,因此将SQLLite测试内置到S#arp Lite中是过大的,为了使事情更简单而被删除. (此外,如果需要,您始终可以查看该功能的S#arp体系结构代码.)(In S#arp Architecture, SQLLite was used to provide an in-memory database for testing custom repository methods. Since custom repositories have been mostly relegated to obsolescence, keeping SQLLite testing built-in to S#arp Lite would have been overkill and has been removed to keep things simpler. (Besides, you can always look at the S#arp Architecture code for that functionality if needed.))
S#arp Lite库中有什么?(What’s in the S#arp Lite Libraries?)
在遍历示例项目时,已经讨论了S#arp Lite类库中的大多数相关内容,但是让我们花点时间看一下可复用的S#arp Lite类库中的所有内容.(Most of what’s relevant in the S#arp Lite class libraries has already been discussed while going through the sample project, but let’s take a moment to see what all is in the reusalbe, S#arp Lite class libraries.)
SharpLite.Domain(SharpLite.Domain)
此类库为S#arp Lite项目的域层提供支持.(This class library provides support for the domain layer of your S#arp Lite project.)
- ComparableObject.cs:为从其继承的任何对象提供健壮的哈希码生成器. (实现起来比您想象的要棘手得多.)(ComparableObject.cs: Provides robust, hash code generator for any object which inherits from it. (It’s much trickier to implement than you think. ;))
- DomainSignatureAttribute.cs:用于装饰构成类的域签名的属性的属性.(DomainSignatureAttribute.cs: An attribute used to decorate properties which make up a class' domain signature.)
- EntityWithTypedId(在Entity.cs中定义):为您的实体提供(非强制性)通用基类,包括当比较类似对象时的Id属性和域签名属性.它需要一个通用的参数来声明Id属性的类型.例如int或Guid.(EntityWithTypedId (defined in Entity.cs): Provides a (non-mandatory) generic base class for your entities, including an Id property and the inclusion of domain signature properties when comparing like objects. It takes a single, generic parameter declaring the type of the Id property; e.g., int or Guid.)
- Entity.cs:提供一个EntityWithTypedId基类,其ID假定为int类型.(Entity.cs: Provides an EntityWithTypedId base class with an Id assumed to be of type int.)
- IEntityWithTypedId.cs:提供一个接口,该接口可用于实现您自己的实体基类.(IEntityWithTypedId.cs: Provides an interface which may be used to implement your own entity base class.)
- /DataInterfaces/IDbContext.cs:公开用于控制事务的接口.(/DataInterfaces/IDbContext.cs: Exposes an interface for controlling transactions.)
- /DataInterfaces/IEntityDuplicateChecker.cs:公开一个接口,用于检查实体是否与数据库中已有的实体重复.(/DataInterfaces/IEntityDuplicateChecker.cs: Exposes an interface for checking if an entity is a duplicate of one already in the database.)
- /DataInterfaces/IRepository.cs:公开一个非常基本的存储库,包括Get,GetAll(返回IQueryable),SaveorUpdate和Delete.(/DataInterfaces/IRepository.cs: Exposes a very basic repository including Get, GetAll (returning IQueryable), SaveorUpdate, and Delete.)
- /Validators/HasUniqueDomainSignatureAttribute.cs:可以用来修饰类的属性,以确保在具有相同域签名的数据库中不存在重复项.(/Validators/HasUniqueDomainSignatureAttribute.cs: An attribute which may be used to decorate a class to ensure that duplicates do not exist in the database having the same domain signature.)
SharpLite.EntityFrameworkProvider(SharpLite.EntityFrameworkProvider)
如果团队选择,此库的想法是提供NHibernateProvider的可插拔替代品(接下来讨论).该库尚未完全开发.但是,如果您有兴趣为此付出努力,请告诉我!(The idea of this library is to provide a pluggable replacement for the NHibernateProvider (discussed next), if the team so chooses. This library has not been fully developed yet. But let me know if you’re interested in contributing with this effort!)
SharpLite.NHibernateProvider(SharpLite.NHibernateProvider)
这个基础结构类库提供了S#arp Lite项目通过NHibernate与数据库进行通信所需的一切.(This infrastructural class library provides everything necessary for S#arp Lite projects to communicate with the database via NHibernate.)
- DbContext.cs:实现用于事务管理的IDbContext.(DbContext.cs: Implements IDbContext for transaction management.)
- EntityDuplicateChecker.cs:实现IEntityDuplicateChecker以检查实体重复项.(EntityDuplicateChecker.cs: Implements IEntityDuplicateChecker for checking for entity duplicates.)
- LazySessionContext.cs:支持NHibernate开放会话视图;(LazySessionContext.cs: Supports NHibernate open-session-in-view;) 最初由Jose Romaniello撰写(originally written by Jose Romaniello) .(.)
- Repository.cs:提供了一个极简的基本存储库…可以根据需要随意扩展.(Repository.cs: Provides a minimalist base repository…feel free to extend as needed.)
- /Web/SessionPerRequestModule.cs:提供HTTP模块以支持NHibernate的open-session-in-view.(/Web/SessionPerRequestModule.cs: Provides the HTTP module in support of NHibernate open-session-in-view.)
SharpLite.Web(SharpLite.Web)
此类库为S#arp Lite项目提供了特定于MVC的需求…完全由SharpModelBinder.cs组成.对于在S#arp Lite项目中使用,可以将其视为完全可选的.(This class library provides MVC-specific needs for S#arp Lite projects…which consists entirely of SharpModelBinder.cs. This may be viewed as completely optional for your use in S#arp Lite projects.)
- /Mvc/ModelBinder/EntityCollectionValueBinder.cs:SharpModelBinder用于将来自表单的输入集合转换为绑定到包含对象的实体的集合.(/Mvc/ModelBinder/EntityCollectionValueBinder.cs: Used by SharpModelBinder for translating a collection of inputs from the form into a collection of entities bound to the containing object.)
- /Mvc/ModelBinder/EntityRetriever.cs:与SharpModelBinder相关的类使用该类,以在不知道要使用先验知识的存储库的情况下从数据库中检索实体.(/Mvc/ModelBinder/EntityRetriever.cs: Used by SharpModelBinder related classes for retrieving an entity from the database without knowing which repository to use a priori.)
- /Mvc/ModelBinder/EntityValueBinder.cs:SharpModelBinder用于将下拉选择转换为绑定到包含对象的实体关联.(/Mvc/ModelBinder/EntityValueBinder.cs: Used by SharpModelBinder for translating a drop-down selection into an entity association bound to the containing object.)
- /Mvc/ModelBinder/SharpModelBinder.cs:使用其他功能扩展ASP.NET MVC模型绑定程序,以填充与数据库中实体的关联.(/Mvc/ModelBinder/SharpModelBinder.cs: Extends the ASP.NET MVC model binder with additional capabilities for populating associations with entities from the database.)
好吧,现在简单来说就是这样.我衷心希望S#arp Lite对您和您的团队在开发设计良好,可维护的ASP.NET MVC应用程序方面有所帮助,这些应用程序可以随着项目的发展而扩展.该架构框架反映了多年的经验教训(Well, that’s it in a nutshell for now. I sincerely hope that S#arp Lite will prove helpful to you and your team in developing well-designed, maintainable ASP.NET MVC applications that scale well as the project evolves. This architectural framework reflects lessons learned from years of)经验(experience)鲜血,汗水,眼泪和无数想法被比我聪明得多的人无耻地偷走了.(blood, sweat, & tears and countless ideas shamelessly stolen from those much smarter than I.)
请享用!(Enjoy!) 比利`麦卡菲蒂(Billy McCafferty) http://devlicio.us/blogs/billy_mccafferty(http://devlicio.us/blogs/billy_mccafferty)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET MVC ASP.NET Dev Architect web_development 新闻 翻译