The next installment of the Summer of NHibernate Screencast series is now available for general download!
In this session we explore the NHibernate SchemaExport class and start to exercise its ability to generate our database schema directly from our hbm.xml mapping files. In addition, we touch briefly on some of the relative advantages of DDD (Domain Driven Design) and start to understand how this aspect of NHibernate can help us avoid having to place the database front-and-center in our development mind as we are refining our Domain Model in our overall software development process.
Some of the highlights from this session include:
- Overview of the SchemaExport class and its role in our development process
- Understanding the somewhat awkward API methods exposed by the SchemaExport class
- Interpreting the somewhat arcane arguments to methods of the SchemaExport class
- Controlling the names of foreign keys in our generated schema
- Understanding the limitations of the DROP capabilities exposed by the SchemaExport class
- ‘Tricking’ the SchemaExport class into creating VIEWs from our mapping files instead of TABLEs
- defining a database INDEX in our mapping files
- applying unique constraints to the database from our mapping files
As usual, comments, suggestions, feedback and constructive criticism are appreciated~!
i was waiting for the video… i’m still downloaidng 🙂
thanks Steve
Steve,
i’m wondering what are the choice in term retriving my data after i do SchemaExport (drop the database and recreate it again).?
i’m on the half of screen cast, thought to ask you.
thanks again.
Steve,
fyi: you can delete all your tables/views at one shot, just do SHIFT + object and hit the delete
i see that you are deleting every single object one at a time… thought to share with you.
thanks.
@Nisar:
Are you suggesting that SHIFT + DEL in sql mgt studio will drop the table even if there are FK dependencies on it? That’s why I was doing that in the order you saw in the screencast (to avoid the “sorry, you cannot drop table xyz because it will violate FK contraints…” message).
Can you advise? If this is true, its certainly news to me (but also a very useful technique).
Hi steve,
In your previous screen cast, you have a many to many relationship on the OrderProduct Table and you created a mapping file without a class. My question is how do you insert into the mapping table.
thanks
@Lookman:
In the case of a m:n mapping table where the intermedial mapping table contains ONLY the pair of foreign key values (as is the case in this very simple model), there is no need to explicitly perform any CRUD operations against the actual ‘mapping’ table. This is one of the ‘geniuses’ of NHibernate — its capable of inferring what you need it to do from the mapping files that describe both ends of the relationship as being m:n.
If you find that you need to store additional info in the intermediate mapping table (OrderProduct in the case of this model) other than just a pair of FK values then you actually DO have to define a class (an ‘entity’) that relates to that entry in the mapping table. In this case, you would define a pair of 1:m relationships to the entry in the mapping table rather than a m:n relationship that simply traverses THROUGH the intermediate table.
An obvious example of this kind of need would be something that I’m actually planning to do in an uncoming session: add a ‘quantity’ field to the OrderProduct table to represent how many of each Product item is in each Order for each Customer. As soon as this change is made, this whole 3-table relation becomes a set of EXPLICIT 1:m relations rather than a m:n relation with IMPLICIT 1:m relations.
Does this help…?
@Steve,
i did not realize that other tbls have FK sorry about that …my bad…. but one thing is that if your tbls having no FK then you can delete all at on shot by doing like this: click on the TABLE and on the right hand side you will see a SUMMARY window where you can select multiple objects and delete hit delete button … oh this technique you can apply for views/SP also.
please see my question #2
thanks
I do understand this for 1:m relationship,
Parent p = session.Load(typeof(Parent),parentid);
Child c = new Child();
c.parent = p;
p.Children.Add(c);
but in case as an example am adding both order and product at the same time mapping the id to the foreign key in the database mapping table.
you said:there is no need to explicitly perform any CRUD operations against the actual ‘mapping’ table.
at what point does create the mapping and how, i don’t get it.
thanks
@Nisar:
No issue; that’s sort of what I thought re: the FK issues in SSMS.
Re: retrieving your data after the schema DROP, there really isn’t a good recommendation in this regard. DROP is (of course) a destructive operation and so running it will lose your data as well as your schema elements (tables, views, etc.).
In the case of a developer working with unit tests (as is the case here), this doesn’t matter since the testdata.xml file contains our persisted test data that the DatabaseUnitTestBase class’ methods will simply reload into the database when it comes time to run the actual unit tests against the DB.
In a real-world (production) situation with data you cared about you would (of course) never do what’s demonstrated here but for the support of the unit testing/development process its fine to just reload the data from the testdata.xml file that’s available in the code downloads for the session(s) since this is the data that the unit tests are ulitmately based upon.
Steve,
what is your take on “Database-Driven Modeling” vs “Model-Driven Schemas” which one you preference?
thanks
@Nisar:
Snipped from an e-mail response I sent just last evening to another person who asked the same quesion…
–snip–
As for myself in this area, I tend to work in one of two ways (depending on the appropriate context):
1) use code-gen to slave the DTO + mapping files to the DB; in this ‘mode’ I use partial classes and/or construct a higher-order domain model to manipulate the DTOs as needed and when I make a DB schema change I completely regen the DTO + mapping files again to keep them in-sync. This tends to work in cases where the DB schema is set by an outside force/external constraints or already exists
2) hand-code the domain model and the mapping files and then use SchemaExport to push that to the DB initially; later I will make mods to both the domain + mapping files AND the db by hand to keep them in-sync (i.e., your suggestion of doing ShemaExport once). Later, if a significant refactor happens to the domain, I will consider re-running SchemaExport against the DB but I will often use a technique that actually ISN’T in the screencast: writing the DDL from SchemaExport to an external file (there is an override for the .Execute(…) method that takes a streamwriter object) and then clip out relevant parts of the captured DDL to paste into SSMS and then run ‘by hand’ after additional tweaking. Imperfect and error-prone (not easy to reproduce) but it works.
–snip–
Hope this helps and thanks for your continued feedback and interest~!
@Lookman:
Inspect the Order.hbm.xml and Product.hbm.xml mapping files in the code download. Both of those contain the mapping that supports this; inspect the mapping of the [bag]…[/bag] element in both mapping files (square-brackets used here b/c WordPress will strip out xml-style angle brackets).
Hope this helps.
-Steve B.
Hi Steve,
thanks for the great screen casts.
I just want to have some feedback regarding the column check.
I did test the column check using NHibernate 2.0 (the latest release… just yesterday) and guess what it worked just fine and it indeed generated the desirable DDL.
Cost REAL null check( Cost > 0) ,
thanks
Ali
@Ali:
Thanks for the update; nice to know that this is one of the 1.2 bugs fixed in 2.0~!.
Hi Steve, thanks for your ongoing great work! Truly enjoy every minute of your screen casts!
Boy, this “work around” for dealing with views is pretty whacked – hasn’t anyone on the NHibernate team every thought of introducing something like a “table-type” property on the tag, with values of “table” or “view” ?? Having to let NHibernate create a “wrong” table, then whacking it out again and having to write a SQL statement (which needs to be kept up and adapted every time I add a new field!) seems more like an awful hack than anything…….
C’mon – views ARE being used quite a bit in the database world – why can’t NHibernate support them in a better fashion? Has anything changed in the 2.0 code base in this regard?
Hi, Steve
First of all a BIG thanks for these videos, they are wonderful and really informative (I started using some tools after watching you efficiently working in VS2008 !!!). Any ways…
Having tried the SchemaExport class I am facing some problem. The Schemaexport class of mine somehow also creates a table called ProductOrder and fails with the following message.
TestCase ‘M:DataAccessTest.NHibernateDataProviderTest.CreateDatebase’
failed: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors.
NHibernate.HibernateException: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors. —> System.Data.SqlClient.SqlException: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async)
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean format, Boolean throwOnError, TextWriter exportOutput, IDbCommand statement, String sql)
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format, IDbConnection connection, TextWriter exportOutput)
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format)
— End of inner exception stack trace —
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format)
C:\Development\RnD\DineAndDiscussNHibernate_Session9\DataAccessTest\NHibernateDataProviderTest.cs(28,0): at DataAccessTest.NHibernateDataProviderTest.CreateDatebase()
Am I missing something here ?
Thanks
I am sorry I forgot to paste the entire script. Here is the full SQL Script output.
***************************************************************************
Starting the MbUnit Test Execution
Exploring DataAccessTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 2.4.2.130 Addin
No tests found
alter table ProductOrder drop constraint FK_ProductOrders
alter table ProductOrder drop constraint FK_OrderProducts
alter table [Order] drop constraint FK_CustomerOrders
alter table viewCustomersWithYoyo drop constraint FKADD890B370C8035
alter table viewCustomersWithYoyo drop constraint FKADD890B313C3E8FD
alter table OrderProduct drop constraint FK_ProductOrders
alter table OrderProduct drop constraint FK_OrderProducts
if exists (select * from dbo.sysobjects where id = object_id(N’ProductOrder’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table ProductOrder
if exists (select * from dbo.sysobjects where id = object_id(N’Product’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table Product
if exists (select * from dbo.sysobjects where id = object_id(N'[Order]’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table [Order]
if exists (select * from dbo.sysobjects where id = object_id(N’viewCustomersWithYoyo’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table viewCustomersWithYoyo
if exists (select * from dbo.sysobjects where id = object_id(N’Customer’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table Customer
if exists (select * from dbo.sysobjects where id = object_id(N’OrderProduct’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table OrderProduct
create table ProductOrder (
Product INT not null,
[Order] INT not null
)
create table Product (
ProductId INT IDENTITY NOT NULL,
Name NVARCHAR(50) not null,
Cost REAL not null,
primary key (ProductId)
)
create table [Order] (
OrderId INT IDENTITY NOT NULL,
OrderDate DATETIME not null,
Customer INT not null,
primary key (OrderId)
)
create table viewCustomersWithYoyo (
CustomerId INT IDENTITY NOT NULL,
Firstname NVARCHAR(255) null,
Lastname NVARCHAR(255) null,
OrderId INT null,
OrderDate DATETIME null,
primary key (CustomerId)
)
create table Customer (
CustomerId INT IDENTITY NOT NULL,
Version INT not null,
Firstname NVARCHAR(50) null,
Lastname NVARCHAR(50) null,
primary key (CustomerId)
)
create table OrderProduct (
[Order] INT not null,
Product INT not null
)
alter table ProductOrder add constraint FK_ProductOrders foreign key (Product) references Product
alter table ProductOrder add constraint FK_OrderProducts foreign key ([Order]) references [Order]
alter table [Order] add constraint FK_CustomerOrders foreign key (Customer) references Customer
alter table viewCustomersWithYoyo add constraint FKADD890B370C8035 foreign key (CustomerId) references Customer
alter table viewCustomersWithYoyo add constraint FKADD890B313C3E8FD foreign key (OrderId) references [Order]
alter table OrderProduct add constraint FK_ProductOrders foreign key (Product) references Product
TestCase ‘M:DataAccessTest.NHibernateDataProviderTest.CreateDatebase’
failed: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors.
NHibernate.HibernateException: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors. —> System.Data.SqlClient.SqlException: There is already an object named ‘FK_ProductOrders’ in the database.
Could not create constraint. See previous errors.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async)
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean format, Boolean throwOnError, TextWriter exportOutput, IDbCommand statement, String sql)
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format, IDbConnection connection, TextWriter exportOutput)
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format)
— End of inner exception stack trace —
at NHibernate.Tool.hbm2ddl.SchemaExport.Execute(Boolean script, Boolean export, Boolean justDrop, Boolean format)
C:\Development\RnD\DineAndDiscussNHibernate_Session9\DataAccessTest\NHibernateDataProviderTest.cs(28,0): at DataAccessTest.NHibernateDataProviderTest.CreateDatebase()
0 passed, 1 failed, 0 skipped, took 2.25 seconds.
***************************************************************************
Thanks
Sorry Steve,
My Bad….I had accidently written following in Product.hbm.xml
……
@Marc:
Yes, I agree this seems like a hack — and I will admit that its one that I arrived at all by myself after some fiddling so for all I know there may indeed be a better way (though I am unaware of it myself).
To my knowledge, it doesn’t get any better in this area w/Nhib 2.0
@Mrunal:
Glad you got it solved~!
I am trying to download the screen cast but this link always times out. Interestingly, I am able to download other sessions with .avi extension for example Session 06. Can anyone please guide me.
I have managed to download it.
Thanks
Syed
Hi Steve–I am using TestDriven.Net with MbUnit in a setup similar to yours. I tried using TestDriven.Net’s ability to run any arbitrary method in a class, but TD.N reports back that “NO TESTS WERE RUN (No tests found)”, and it doesn’t hit the method I have selected (verified with the “Test With… Debugger” option.
Any thoughts on why this may be happening, and what I can do to fix it? Thanks.
Full TestDriven.Net output:
—— Test started: Assembly: NHibernateRepository.Test.dll ——
Gallio TestDriven.Net Runner – Version 3.0.5 build 546
Test Assemblies:
C:\Users\David Veeneman\Documents\Visual Studio 2008\Projects\NHibernateRepository\NHibernateRepository.Test\bin\Debug\NHibernateRepository.Test.DLL
Start time: 5:39 PM
Verifying assembly names.
Initializing the test runner.
Loading the test package.
Exploring the tests.
Running the tests.
Unloading the test package.
Disposing the test runner.
Stop time: 5:39 PM (Total execution time: 2.093 seconds)
Test Report: file:///C:/Users/David%20Veeneman/AppData/Local/Temp/Gallio.TDNetRunner/NHibernateRepository.Test.DLL.html
** NO TESTS WERE RUN (No tests found) **
@David:
Yup, I know 100% exactly what it is.
Sadly, when I mentioned (and demonstrated) in the screencasts that its possible to use TD.NET to run arbitrary public methods in your class even if they weren’t attributed with the [Test] attribute, this was apparently classified as a ‘bug’ by Jamie Cansdale (the author of TestDriven.NET) and thus ‘corrected’ in a subsequent release.
This means that the strategy identified in the screencasts to run a simple method to grab the contents of an existing DB and save it to a TestData.xml file (which is probably what you are attempting to emulate as its the most-common use for that TD.NET ‘feature’) is no longer viable.
Instead, if you look at the Autumn of Agile screencasts where I get to writing unit tests against the DB using a more-recent (latest beta, in fact) version of TestDriven.NET, you will see that what I am doing is assigning a [Test] attribute to the method I want to execute and then (yes, this is an annoying hack), commenting in and commenting out that method or the entire fixture as I need to execute it.
My new pattern (as you can see in the Autumn of Agile screencasts) is to create a completely separate test fixture full of just the ‘db-maintenance convenience methods’ that I need periodically, comment that whole TestFixtute our (using the MbUnit [Ignore] attribute, and then remove the [Ignore] as needed when I need/want to run any of the maintenance methods in there. Then I quickly comment back in the [Ignore] so that when I run “all tests in the solution” or “all tests in the assembly”, etc. I don’t inadvertently end up running any of these maintenance methods ‘by accident’.
Yes, its a messy hack that relies on developers rememebering to comment in and out the [Ignore] attribute, but with the loss of the TD.NET ‘feature’ (nee ‘bug’), I sort of have no other choice.
HTH,
-Steve B.
Steve,
Maybe you’ve addressed this elsewhere, but I noticed the following in the OrderProduct table.
In the .ddl file from the download for Session 1, a composite primary key is put on the Order and Product fields (as is usually the case with mapping tables).
The OrderProduct table finally generated in session 9 with SchemaExport has the foreign keys, but no primary keys.
To add this to OrderProduct, would we need to go ahead and create a mapping file for OrderProduct (and lose the genius of NHib)? Or add to one of the existing mapping files any manually add the composite primary key?
Thanks,
Jason
@Jason (self)
One solution for “no composite-key in OrderProduct”:
– Change from IList to ISet in Order and Product classes for the respective collections.
– Change mapping files for Order and Product to use instead of .
– Change one of the mapping files (I changed Order.hbm.xml), and expand the tags to declare each of the columns as not-null.
When I run SchemaExport, now I get the composite primary key on OrderProduct. All of the unit tests complete successfully (29 as of session 9).
Reference: Author/Work example in Chap. 19 of NHibernate 1.2 docs.
Was the issue that IList / allows duplicates in the collection, and we weren’t declaring the columns not-null?
Any downside to using ISet / here for these collections?
Thanks,
Jason
Of course I forget that posts strip html.
Second point should read …”to use ‘set’ instead of ‘bag'” (tags).
After fourth point, I had posted full xml of expanded tags for ‘key’ and ‘many-to-many’ for Order.hbm.xml. Basically, inside the ‘key’ and ‘many-to-many’ tags, put a ‘column’ self-closing tag with the column name and ‘not-null’.
TY
Jason
@Jason:
Yes, that’s EXACTLY the trouble — and the point of the ISet vs IList as the collection type. I guess I missed that nuance when I progressed from the model in the earlier sessions to the one that finally ended up being the sample in Session 09.
ISets don’t allow dupes whereas ILists do and *generally* you nearly always find that you want to model your collections as sets since this is certainly the more common relational model in your database. I guess I screwed up and overlooked that in the model + the corresponding mapping.
Thanks for both catching this and offering up the solution!
@Steve:
The check constraint only works if you define it at the class tab level:
0″>
Result:
create table Product (
ProductId INT IDENTITY NOT NULL,
Name NVARCHAR(50) not null,
Cost REAL not null,
primary key (ProductId),
check (Cost > 0)
)
Thanks for the screencast series,
AC
@Steve:
Correction to the post:
class name=”DataTransfer.Product, DataTransfer” table=”Product” check=”Cost > 0″
AC
Steve,
A few questions and discoveries.
I am using 2.0.1GA on XP(SP3)
1) The Check constraint Count > 0 on the Cost column is working fine and generating this script:
create table Product (
ProductId INT IDENTITY NOT NULL,
Name NVARCHAR(50) not null,
Cost REAL null check( Cost > 0) ,
primary key (ProductId)
)
2) Mapped columns of type string are creating nvarchar fields. So the view filter of =’Yo-Yo’ gets converted to ‘N Yo-Yo’. All string columns get converted to nvarchar. Can this default be changed to varchar?
3) Creating the unique, unique-key and PK keys using schemaExport.Executegenerate keys with values ending in numbers like PK__Customer__09746778 and UC__Customer__0A688BB1. Can this naming be changed like the foreign-keys?
Thanks,
Mark