在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
http://www.codeproject.com/aspnet/NHibernateBestPractices.asp Preface to the 1.2nd EditionIn March of 2006 I published my initial thoughts on NHibernate best practices with ASP.NET, generics and unit tests. I've been delighted to learn that these ideas have been implemented in a number of real-world scenarios with strong success. Since then, I've worked with many people to refine these ideas, learn from mistakes and leverage a more powerful version of NHibernate. Accordingly, although only a modest, yet important, amount of modification has been made to the underlying architecture, some other important factors have been updated and addressed in this article:
A quick thanks goes out to those who have implemented my ideas in their own work and have given plenty of ideas for improvement and consideration! Now onto the 1.2nd edition... Article Contents
IntroductionWhy Use an ORM?[Author's Note: The following is an excerpt taken from a book I tinker with in my "spare" time.] "As we look to the horizon of a decade hence, we see no silver bullet. There is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in productivity, in reliability, in simplicity." These prophetic words were put forth by Frederick Brooks' in his now legendary paper, No Silver Bullet, in 1986. Heeding Brooks' words to a tee, it wasn't until more than a decade later, in 1997, that the software world was presented with a hint of an upcoming silver bullet in the form of NeXT's Enterprise Object Framework...one of the first object-relational mappers (ORM). Although not regularly conspicuously stated – often to avoid being seen as a heretic of software dogma – it is commonly accepted by many that ORM technologies, when used correctly, are, in fact, a silver bullet for software development. With the maturation of NHibernate, the ORM silver bullet has been formally introduced to the world of .NET. The most common dissenters of ORM technologies, in general, are developers using Microsoft technologies. (As I've placed myself squarely into this realm for the past decade or so, I feel quite comfortable bringing us up first!) There seems to be an unwritten rule that "if it wasn't invented by Microsoft, then wait until Microsoft puts out the right way to do it." Stepping up to the plate, Microsoft intends to do just that with "LINQ to Entities" in the upcoming C# 3.0. (Yes, officially discard the use of "ObjectSpaces" and "DLINQ.") Developers may continue to wait for this technology or, alternatively, start realizing the benefits of ORM immediately. To be fair, the not-invented-by-Microsoft toolset for .NET developers used to be sparse but has been steadily growing since the advent of .NET. Circa 2004, the "not created by the mothership" toolset of open source tools finally began to reach a respectable level of maturity and should be seriously considered for any .NET endeavor. (And since, statistically, the majority of software projects fail, it sounds like the consideration of an expanded toolset is certainly warranted.) The impending introduction of LINQ certainly brings great benefits to flexible querying. Fortunately, LINQ is not exclusive to Microsoft data-access layers and LINQ for NHibernate is already well underway by Ayende Rahien. Other dissenters of these technologies suggest that ORMs kill application performance and only provide a significant improvement to productivity in the initial stages of development. Furthermore, the argument continues, is that the use of an ORM becomes a serious detriment to project success only later in the project, when issues of performance and maintainability begin to have a more noticeable effect. Three obvious retorts come to mind in response to these protests. First and foremost, in support of ORMs, using a framework such as NHibernate increases your performance as a developer. If you can spend 90% less time (yes, I said "90% less time") on developing data access code, then quality time can be spent improving the domain model and tuning performance – assuming it becomes necessary. Furthermore, leveraging a simple profiling tool goes a long way towards implicating the 5% of code that's causing 95% of the performance bottleneck. And in the cases in which the data access layer is the bottleneck, simple adjustments can usually be made to reap substantial improvements. Incidentally, this is no different than when not using an ORM. (Be sure to check out Peter Weissbrod's introductory article to profiling NHibernate applications.) And in the very few situations in which the ORM framework is the bottleneck and can't be adjusted for improvement, it's trivially simple to bypass the ORM altogether if the data access layer has been properly abstracted. Secondly, NHibernate, specifically, provides an incredible amount of control over all aspects of data-loading behavior. This has positive effects on developer productivity, application scalability, and application stability. Caching is certainly available – but this is readily available in non-ORM solutions as well. Other out-of-the-box features include lazy loading, support for inheritance, declaration of immutable classes, loading of read-only properties, support for generics, stored procedures, blah blah blah. The point is that all these powerful features have been proven in real-world scenarios and, therefore, have removed many hours of developing, debugging and tweaking a custom data access layer. (And if you really feel the need to get into the code, that's just fine since NHibernate's an open source project.) Finally, I would argue that those who feel that ORMs like NHibernate become maintenance headaches later in a project are working with a coding architecture that would inhibit the maintenance of any data access layer. Just because I've suggested that NHibernate is a silver bullet doesn't imply that it eliminates the need for proper application design. With the proper amount of judicious, architectural forethought, NHibernate is quite possibly the most maintainable solution for tying a .NET application to a database. Needless to say, NHibernate, like other ORM tools, has alleviated the maintenance of thousands of lines of code and stored procedures, thus allowing developers to focus more attention on the core of a project: its domain model and business logic. Even if you automatically generate your ADO.NET data-access layer using a tool such as CodeSmith or LLBLGen Pro, NHibernate provides the flexibility in decoupling your domain model from your relational model. Your database should be an "implementation detail" that is defined to support your domain model, not the other way around. The remainder of this article focuses on describing best practices for integrating NHibernate into ASP.NET using well established design patterns and "lessons learned from the field". Goals and Overview of ArticleThis article assumes a good understanding of C# and NHibernate, knowledge of the Data Access Object / Repository pattern, and at least a basic familiarity with Generics. Note that this article does not focus on using NHibernate but, instead, on integrating NHibernate into ASP.NET applications. If you're just getting acquainted with NHibernate, I'd recommend reading these two great introductions at TheServerSide.net: Part 1 and Part 2. (Also keep an eye out for Pierre Kuate's forthcoming NHibernate in Action.) For an extensive overview of the Data Access Object pattern, which is leveraged heavily within the samples, go to J2EE's BluePrints catalog. Although I use the phrase "Data Access Object" (or "DAO") throughout, it is interchangeable with Eric Evans' "Repository" in Domain-Driven Design. I just find "DAO" more convenient to type! In building solid data integration within an ASP.NET 2.0 application, we aim to achieve the following objectives:
Two sample applications have been included, demonstrating the use of NHibernate with ASP.NET while meeting the above objectives:
What follows now is a description of how each of the aforementioned design objectives has been tackled in the sample applications. But before getting into the implementation details, let's skip right to the chase and get the sample up and running. Running the Basic NHibernate SampleThe sample application, at the risk of being terribly cliché, utilize the Northwind database within SQL Server 2005 to view and update a listing of Northwind customers. To demonstrate the use of lazy-loading, the application also displays the orders that each customer has made. All you need to run the samples locally is IIS with the .NET 2.0 Framework installed, and SQL Server 2005 (or 2000) containing the Northwind database. (Since SQL Server 2005 doesn't come with Northwind by default, you can simply backup the Northwind DB from 2000 and restore it into 2005.) The samples also port to SQL Server 2000...simply modify the NHibernate configuration settings, accordingly. To get the basic NHibernate sample application up and running:
Steps for getting the "enterprise" sample up and running are discussed in Extending the Basics to an "Enterprise" Solution. But before that, now that you're able to follow along with the basic sample in front of you, we'll examine how the application was developed to meet our design objectives... NHibernate Integration BasicsWhen developing an application, my primary goals are to:
This section discusses using these design principles for the integration of NHibernate into ASP.NET applications. We'll do this by dissecting the internals of the Basic NHibernate Sample. Architectural NotesThe basic sample should not, necessarily, be seen as a reusable framework for your own ASP.NET application. The focus within this example application is on presenting NHibernate integration in a simple and concise manner. If you are looking for a "ready for the real-world" architecture, be sure to take a look at Extending the Basics to an "Enterprise" Solution after reviewing this section. With that said, the basic sample does present a number of best practice techniques and design patterns: Separated InterfaceSeparated Interface, also known as Dependency Inversion, is a technique for establishing a clean separation of concerns between application tiers. This technique is described by Martin Fowler, by Object Mentor, and in further detail in Robert Martin's Agile Software Development. The technique is often used for cleanly separating a data access layer from a domain layer. For example, assume a Customer object - in the domain layer - needs to communicate with a data access object (DAO) to retrieve a number of past Orders. (Whether or not the domain layer should ever communicate directly with a DOA is left for another discussion.) Consequently, the Customer object has a dependency on the Order DAO - in the data layer. But for the Order DAO to return orders, the DAO requires a dependency back to the domain layer. The simplest solution is to put the data layer, containing the DAOs, into the same physical assembly as the domain layer. To maintain a "virtual" separation of concerns, the containing project could include two folders, one named Domain and the other named Data. (I've actually used this approach on a good-sized project, in a former life, with regrettable consequences.) This approach brings with it a number of ill effects:
Alternatively, the domain and data layers could be placed into physically separate assemblies; e.g., Project.Core and Project.Data, respectively. The domain layer (the Project.Core assembly) would contain domain objects and DAO interfaces. The data layer (the Project.Data assembly) would contain concrete implementations of the DAO interfaces defined in the domain layer. This is shown in the package diagram below. Note that the arrow signifies a uni-directional dependency from the data layer to the domain layer.
An implementation example of Separated Interface is included in the sample applications and is discussed further below. Dependency InjectionSeparated Interface, as described above, introduces a dilemma: how are the concrete DAO implementations given to the domain layer which only "knows" about interfaces? Dependency Injection, also known as Inversion of Control (IoC), describes the technique for doing this. With Dependency Injection, it's possible to remove many direct instantiations of concrete objects along with the inflexibility that comes along with calling the
I've found that using an IoC Container is most useful outside of unit tests for enabling ASPX pages to be given dependencies, to avoid ever having to call the Design by ContractDesign-by-Contract (DBC) is quite simply the best way to never have to use the debugger again. Although infrequently discussed, this technique should be given the same amount of praise that test-driven development receives. (Not that I'm saying that DBC gets jealous, but it should.) It increases quality, reduces checks for null, reduces debugging time, and greatly improves the overall stability of an application. To be more technically descriptive, DBC is a technique for contractually obligating users of your code (which is usually you, yourself) to use methods and properties in a particular way and for those methods and properties to promise to successfully carry out the given request. If the contract is not followed, an exception is thrown. It may seem a bit drastic at first, but it goes a long way to improving code quality. With DBC, "pre-conditions" assert what contractual obligations must be adhered to when invoking a method or property. "Post-conditions" assert what contractual obligations have been ensured. I highly recommend reading an introduction to Design-by-Contract; you'll be hooked on using it very quickly. The sample projects included with this article use a modified DBC class originally written by Kevin McFarlane. The original allows conditional compilation for turning contracts on for debug mode and off for release mode while the variation included in this article's samples maintains contractual obligations regardless of compilation mode. In my opinion, a contract is always a contract and the behavior should never vary. In the code, you'll find that pre-conditions are declared with The Basic Sample ApplicationSo what are we working with here? The basic application fulfills the following user stories:
Regardless of how valuable this application may or may not be, the above user stories are enough to demonstrate the primary aspects of NHibernate integration. To assist with keeping logical tiers loosely coupled, the included sample application is split into four projects: Tests, Web, Core and Data. As a policy, I use <ProjectName>.<LayerName> for naming projects; e.g., BasicSample.Data. Although this simple separation of concerns will work for now, a more realistic architecture will be discussed later. In peeling the layers of the onion, let's begin with the testing layer and work our way towards the simple presentation layer. BasicSample.Tests with NUnit, NUnitAsp, Rhino Mocks and FitI'll assume you can probably guess what this project is for. In the first edition of this article, unit testing was only lightly discussed. As industry, and personal, experience has proven, test-driven development is a pivotal factor in producing high quality products which are more adaptable to change. Furthermore, taking a test-driven approach tends to produce better designs and, as a side effect, creates a lot of perfectly valid technical documentation. For a terrific beginner's introduction to a "day in the life" of test-driven development, take a look at Kent Beck's Test-Driven Development: By Example. Examining the unit tests of the sample application provides an overview of how the application is structured and what functionality is available. After taking a look at the unit tests, we'll delve further into the code they test. A Note on Unit Test PerformanceIt's imperative for unit tests to be blazing fast. If a suite of unit tests takes too long to run, developers stop running them - and we want them running unit tests all the time! In fact, if a test takes more than 0.1 second to run, the test is probably too slow. Now, if you've done unit testing in the past, you know that any unit test requiring access to a live database takes much longer than this to run. With NUnit, you can put tests into categories, making it easy to run different groups of tests at a time, thus excluding the tests that connect to a database most of the time. But at the very least, these "slow" tests should be run every night within a Continuous Integration environment. Here's a quick example of categorizing an NUnit, unit test: [TestFixture]
[Category("Database Tests")]
public class SomeTests
{
[Test]
public void TestSomethingThatDependsOnDb() { ... }
}
Domain Layer Unit TestsFor simplicity, the domain layer of this application is light, to say the least. But even in the lightest of domain layers, it's important to have, at a minimum, every non-exception path covered by a unit test. The domain layer tests may be found in the namespace To run the unit tests, open NUnit, go to File/Open Project and open BasicSample.Tests/bin/Debug/BasicSample.Tests.dll. To prevent the more time-consuming tests from running, go to the Categories tab within NUnit and double-click both "Database Tests" and "Web Smoke Tests." Additionally, click "Exclude these categories" at the bottom. Now, when you run the unit tests, only the domain logic tests will run and not be slowed down by HTTP and database access tests. For such a small application, the added overhead of the "slow" tests is almost negligible, but can add many minutes to running the unit tests of larger apps. Using Test Doubles for the Data Access LayerBefore getting into simulating the database layer, it should be noted that a nomenclature exists for describing different types of simulated services. Dummies, fakes, stubs and mocks are all used to describe different types of simulated behavior. An overview of the differences highlights a few which will be included in Gerard Meszaros' upcoming XUnit Test Patterns. Meszaros offers "test double" to generically describe any of these behaviors. Stubs and mocks are two such test doubles demonstrated in the sample code. Unless you're specifically testing DAO classes, you usually don't want to run unit tests that are dependent on a live database. They're slow and volatile by nature; i.e., if the data changes, the tests break. And when testing domain logic, unit tests shouldn't break if the database changes. But the major obstacle is that domain objects themselves may depend on DAOs. Using the abstract factory pattern that is in place in the sample (discussed later), and the associated DAO interfaces, we can inject DAO test doubles into the domain objects, thereby simulating communications with the database. An example is included in the test Customer customer = new Customer("Acme Anvils");
customer.ID = "ACME";
customer.OrderDao = new OrderDaoStub();
As an alternative to writing DAO stubs, which are generally static by nature and often amount to quite a bit of "not implemented" code, it is also possible to mock the DAO using a tool such as Rhino Mocks or NMock. Either selection works perfectly well, but Rhino Mocks invokes methods in a strongly typed manner as opposed to using strings, as NMock does. This makes its usage compile-time checked and assists with renaming properties and methods. The test public IOrderDao CreateMockOrderDao() {
MockRepository mocks = new MockRepository();
IOrderDao mockedOrderDao = mocks.CreateMock<IOrderDao>();
Expect.Call(mockedOrderDao.GetByExample(null)).IgnoreArguments()
.Return(new TestOrdersFactory().CreateOrders());
mocks.Replay(mockedOrderDao);
return mockedOrderDao;
}
Unfortunately, more often than not, you're maintaining legacy code that has no semblance of the ideal "code-to-interface" that allows for such test-double injection. Usually, there are many explicit dependencies to concrete objects, and it is difficult to replace data-access objects with test doubles to simulate a live database. In these situations, your options are to either refactor the legacy code to fit within a test harness, or to use an object mocking tool such as TypeMock. With TypeMock, it is even possible to mock sealed and singleton classes - an impressive feat to pull off without such a tool. Albeit powerful, TypeMock is best left alone unless absolutely needed; using TypeMock prematurely makes it tempting to not code to interface. The more appropriate course to take when working with legacy code - time and budget permitting - is to refactor the code for greater flexibility. Working Effectively with Legacy Code by Michael Feathers is full of great ideas for refactoring legacy code into a test harness. Unit Testing NHibernate DAOsIn the previous edition of this article, NHibernate's As demonstrated in the sample, it's possible to create a generic DAO that works for any persistent object. (Details of which will be discussed later.) This leads to the discussion of what should be tested and how should it be done? Should each concrete DAO be fully tested? How is the test data maintained? Personal experience suggests these guidelines:
Using Fit Test Doubles for the Presentation LayerWhen the domain layer was tested, test doubles were used to simulate NHibernate communications with the database. Similarly, it's handy at times to use this same approach when testing the presentation layer. Suppose you're working on an ASP.NET project with a dedicated "creative" team. The creative folks, with their black turtlenecks, are in charge of developing the look and feel of the application. While they're working on the graphical layout, you shouldn't be hindered to develop a presentation layer for viewing domain-logic & data-access results and for getting client feedback, while still being able to put off decisions such as master-page setup, security enforcement, and other presentation-specific decisions. In another scenario, suppose you're working on a number of complicated business rules which you'd like the client to be able to verify without having to write a few dozen unit tests to encapsulate each minor variation. FIT (Framework for Integrated Test) is a tool for developers to fake the presentation layer very quickly and provide for a more collaborative effort between developers and project stakeholders. As the Fit site states, this is done "to learn what the software should do and what it does do. It automatically compares customers' expectations to actual results." Arguably, a tool such as this isn't "basic" and isn't required for testing NHibernate; but the importance of test-driven development needs to be emphasized and a tool such as Fit, when used appropriately, is just as applicable to software quality as NUnit. For viewing Fit test results, you can use WinFITRunnerLite, which runs Fit tests in a Windows client similar to NUnit, or FitNesse, which provides a web-based wiki for modifying test inputs and viewing Fit test results. Although it takes a little more setup, FitNesse provides a very flexible framework for allowing clients to participate in validating coding logic and application workflow. The following screenshot shows a simple example of the output you'd expect to see from running a calculator test with FitNesse: Although implementation examples of Fit tests are beyond the scope of this article, my hope is that you'll get interested in learning more about this powerful framework. In addition to the websites listed previously, extensive information concerning the use of Fit and FitLibrary, an extension to Fit, may be found in Fit for Developing Software by Rick Mugridge and Ward Cunningham. Running ASPX "Smoke Tests" with NUnitAspAt this point, we've unit tested the domain layer and the data-access layer and learned about testing with a rough presentation layer, using Fit, for getting clients more involved. It's now time to test the ASPX pages themselves. NUnitAsp is a class library for performing these types of unit tests. Although you can get rather sophisticated using NUnitAsp with your WebForms testing, I find that NUnitAsp is best for running "smoke tests" from the continuous integration server to verify that no page is blatantly breaking. Taking NUnitAsp further than this tends to result in a lot of maintenance of the associated unit testing code. Since these HTTP unit tests are slow by nature, they're rarely run and, consequently, lightly maintained; therefore, they should be kept as simple as possible. BasicSample.Tests/Web/WebSmokeTests.cs demonstrates a sampling of these unit tests. Although trivially simple, these smoke tests go a long way towards verifying that your presentation layer is responsive, that database communications are working correctly, and that NHibernate HBMs are, for the most part, error-free. As an added bonus, if the smoke tests are directed at the production environment immediately after a deployment, they serve to pre-load all the ASPX pages for a more responsive experience for the very next visitor. You should include a smoke test for every URL-accessible web page in your application. To help organize them, create a separate test class for each grouping of smoke tests. For example, all the smoke tests for an admin section of the website would be found in a file called AdminSmokeTests.cs. BasicSample.Core for Defining the Domain LayerThe BasicSample.Core project contains the domain model and NHibernate HBM files. This project also contains interfaces, describing the data access objects, in the Separated Interface, ImplementedYou'll notice that the BasicSample.Core project does not contain implementation details of data access objects, only interfaces describing the services it needs. The concrete DAO classes, which implement these interfaces, are found within BasicSample.Data. As described previously, this technique is called Separated Interface. If you consider BasicSample.Core to be an "upper-level layer" and BasicSample.Data to be a "lower-level layer", then, as Robert Martin describes, "each of the upper-level layers declares an abstract interface for the services that it needs. The lower-level layers are then realized from these abstract interfaces. ... Thus, the upper layers do not depend on the lower layers. Instead, the lower layers depend on abstract service interfaces declared in the upper layers". To see this in action, the data interfaces are described in the namespace Collection Generics ExaminedBy far, one of the greatest benefits that C# 2.0 has brought to the table is the inclusion of generics. With generics, more code reuse can be effectively realized while still enforcing strongly typed coding "contracts". In the previous edition of this article, Ayende's very useful, but deprecated, NHibernate.Generics was used to integrate NHibernate with .NET generics. But now that NHibernate 1.2 natively supports generics, this class library is no longer necessary. If you've used Ayende's library in the past, you've got a bit of work ahead of you to completely migrate away from it, especially if you used the automatic wiring for managing parent/child relationships. But don't let this stop you as you can still upgrade to NHibernate 1.2 without having to immediately refactor out the automatic wiring...but you'll still want to do so sooner rather than later. More information about the steps required to refactor away from Ayende's NHibernate.Generics is found below in Migrating from NHibernate 1.0x to 1.2. A drawback to Ayende's NHibernate.Generics was that internal collections needed to be exposed with both getters and setters. This broke encapsulation and allowed collections to be manipulated or modified in unintended ways - think of it as collection harassment. Now that NHibernate supports generics natively, better collection encapsulation techniques may be employed. The following code from Customer.cs and Customer.hbm.xml shows a better encapsulation of generic collections. public IList<Order> Orders {
get { return new List<Order>(orders).AsReadOnly(); }
protected set { orders = value; }
}
public void AddOrder(Order order) {
if (order != null && !orders.Contains(order)) {
orders.Add(order);
}
}
public void RemoveOrder(Order order) {
if (orders.Contains(order)) {
orders.Remove(order);
}
}
private IList<Order> orders = new List<Order>();
<bag name="orders" table="Orders" inverse="true" cascade="all">
<key column="CustomerID" />
<one-to-many class="BasicSample.Core.Domain.Order, BasicSample.Core" />
</bag>
Setting the orders collection setter to As a simple example of using a custom collection, the // Within Supplier.cs...
public Products Products {
get {
if (productsWrapper == null) {
productsWrapper = new Products(products);
}
return productsWrapper;
}
}
private Products productsWrapper;
// NHibernate binds directly to this member by the access="field" setting
private IList
Although simplistic, the example code should cover most of your custom collection needs. But in some cases, a full-blown generic, custom collection is required. One of the most common scenarios includes creating a custom collection which implements Generic IDs and Object ComparisonsIn BasicSample.Core, each persistable domain object inherits from public class Customer : DomainObject<string>, IHasAssignedId<string>
{
public void SetAssignedIdTo(string assignedId) {
Check.Require(!string.IsNullOrEmpty(assignedId),
"assignedId may not be null or empty");
// As an alternative to Check.Require,
the Validation Application Block could be used for the following
Check.Require(assignedId.Trim().Length == 5,
"assignedId must be exactly 5 characters");
ID = assignedId.Trim().ToUpper();
}
...
}
An obvious drawback to not exposing a public ID setter is that this property becomes essentially unusable to the unit testing framework, unless NHibernate is used to load the object. To skirt this problem, the class BasicSample.Tests/Domain/DomainObjectIdSetter.cs enables you to set the ID of domain objects, even if they do not implement Mapping the Domain to the DatabaseNHibernate provides two means of mapping domain objects to the database: HBMs using XML and mapping attributes. The primary advantage of HBM-XML files is that they are physically separated from the domain objects they describe. This enables the domain objects to remain as POCOs (plain old C# objects), relatively oblivious to how they are associated with the database. But keeping the mapping information separated from the domain objects may also be seen as the primary disadvantage of HBMs in that it requires additional maintenance effort to keep switching between HBMs and the classes they map. (Some people also loathe using XML.) Mapping attributes, on the other hand, are intimately connected to the domain objects and are generally less verbose than their HBM equivalents. Using mapping attributes makes the domain objects more akin to Active Records than POCOs. (For true Active Record support, consider using Castle Project's ActiveRecord.) Besides "dirtying" up the domain objects, mapping attributes require a reference to NHibernate.Mapping.Attributes which makes the domain layer a bit less data-access-provider agnostic. On the other hand, do you often find yourself switching out data-access layers completely, anyway? But as a general rule, the domain layer should remain as data-access-provider agnostic as is practical for the design goals of the application. When it comes right down to it, it's a matter of personal preference when deciding between HBMs or mapping attributes. Regardless, when starting a new project, it should be decided which technique will be used; mixing the techniques may lead to confusion as it may not be clear which objects are mapped and which are not. The sample application demonstrates using HBMs for the mapping solution. The following snippet - not found in the sample application - shows an example of using mapping attributes instead of HBMs. Additional information concerning mapping attributes may be found in the NHibernate docs. [NHibernate.Mapping.Attributes.Class]
public class Customer
{
[NHibernate.Mapping.Attributes.Property]
public string FirstName { ... }
...
}
NHibernate Support for NullablesA new capability that NHibernate 1.2 brings to the table is support for nullable types. Previously, a reference to Nullables.NHibernate supported nullable types, but it is no longer needed. There's no need to treat nullable property mappings, within the HBMs, any differently than other property mappings; simply map the nullable property as you would any other. NHibernate is smart enough to transfer |
请发表评论