diff --git a/docs/logo.png b/docs/logo.png
new file mode 100644
index 000000000..a27e9be75
Binary files /dev/null and b/docs/logo.png differ
diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs
new file mode 100644
index 000000000..54f812397
--- /dev/null
+++ b/src/CommonAssemblyInfo.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.1433
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+[assembly: AssemblyVersionAttribute("0.1.0.0")]
+[assembly: AssemblyCopyrightAttribute("Released by Jeremy D. Miller under the Apache 2.0 License")]
+[assembly: AssemblyProductAttribute("FluentNHibernate")]
+[assembly: AssemblyCompanyAttribute("FluentNHibernate")]
+[assembly: AssemblyConfigurationAttribute("debug")]
+[assembly: AssemblyInformationalVersionAttribute("0.1.0.0")]
+
diff --git a/src/FluentNHibernate.Testing/Debugging.cs b/src/FluentNHibernate.Testing/Debugging.cs
new file mode 100644
index 000000000..246f391ad
--- /dev/null
+++ b/src/FluentNHibernate.Testing/Debugging.cs
@@ -0,0 +1,14 @@
+using NUnit.Framework;
+
+namespace ShadeTree.Testing
+{
+ [TestFixture]
+ public class Debugging
+ {
+ [Test]
+ public void Break()
+ {
+ //Assert.Fail("Make it break");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DeclarativeTesting.htm b/src/FluentNHibernate.Testing/DeclarativeTesting.htm
new file mode 100644
index 000000000..e095ebc47
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DeclarativeTesting.htm
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
Yesterday,
+ David Laribee related some problems he experienced with refactorings in his
+ domain model leading to some breaking problems with NHibernate mappings.
+ Specifically, the issues are:
+
+
Changing the property names of a domain model can break the NHibernate mapping
+
Changing the database fields can break the NHibernate mappings
+
+
+ David went on to bemoan the absence of a quick way to validate NHibernate
+ mappings. Ayende popped in with the suggestion that the presence of
+ integrated tests around the NHibernate usage would spot mapping problems.
+ Other folks mentioned that there's a new ReSharper plugin to validate and
+ refactor NHibernate mappings. I'll circle back to the refactoring plugin
+ in a while. First I want to talk about quick ways to validate NHibernate
+ mappings. Ayende is right of course about the integrated tests against the
+ individual queries, but I'm going to suggest that that isn't the most efficient
+ answer. The bigger integration tests will tell you that something is
+ wrong, but from experience they'll be harder to diagnose because there is so
+ much more going on than simple property checking, and they provide a slow
+ feedback cycle because of how much stuff is going on. What would be nice
+ is a way to walk right up to a mapping and specify which properties on a class
+ are supposed to be persisted and how.
+
+ I thought I would come out of my blogging retirement and show an example of the
+ Chad and Jeremy
+ approach to testing NHibernate mappings:
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ // Ensure that the StructureMap configuration is bootstrapped
+ // In our case, this includes everything we need setup to
+ // execute NHibernate (mappings + ISessionFactory configuration)
+ // This will be pretty application specific here
+ Bootstrapper.RestartStructureMap();
+ }
+
+ [Test]
+ public void SaveAndLoadCustomerContact()
+ {
+ new PersistenceSpecification<CustomerContact>()
+ .CheckProperty(x => x.Name, "Frank")
+ .CheckProperty(x => x.Email, "Email")
+ .CheckProperty(x => x.Extension, 123)
+ .CheckProperty(x => x.FaxNumber, "111-111-1111")
+ .CheckProperty(x => x.TelephoneNumber, "222-222-2222")
+ .CheckProperty(x => x.Title, "Mr.")
+ .VerifyTheMappings();
+ }
+
+
+ All this test does is ensure that the 6 properties of the CustomerContact class
+ (Name, Email, Extension, FaxNumber, TelephoneNumber, Title) are mapped in
+ NHibernate. We have some other methods for checking to many and many to
+ one type relationships.
+
+ Behind the scenes the PersistenceSpecification<T> class:
+
+
Creates a new instance of T
+
Uses the lambda expressions and suggested values in the calls to CheckProperty
+ to load values into the new instance of T
+
Grabs our IRepository out of StructureMap (of course), and saves the object
+
Grabs a second IRepository out of StructureMap
+
Fetches a second copy of the same T out of the second IRepository
+
Verifies that all of the specified properties in the specification were saved
+ and loaded. If any single property does not match between the first T and
+ the second T, the test will fail.
+
+
Here's the implementation of the PersistenceSpecification.VerifyTheMappings() method:
+
+ public void VerifyTheMappings()
+ {
+ // Create the initial copy
+ var first = new T();
+
+ // Set the "suggested" properties, including references
+ // to other entities and possibly collections
+ _allProperties.ForEach(p => p.SetValue(first));
+
+ // Save the first copy
+ _repository.Save(first);
+
+ // Get a completely different IRepository
+ var secondRepository = createRepository();
+
+ // "Find" the same entity from the second IRepository
+ var second = secondRepository.Find<T>(first.Id);
+
+ // Validate that each specified property and value
+ // made the round trip
+ // It's a bit naive right now because it fails on the first failure
+ _allProperties.ForEach(p => p.CheckValue(second));
+ }
+
+
+
The advantage of this testing is that it gives a (relatively) fast feedback cycle
+ focused specifically on the mappings. Tools that check the hbm.xml
+ mappings can only verify that what's there is correctly formed. The
+ mapping tests above will catch missing mappings and desired behavior. At a
+ bare minimum, you really should have at least one smoke test in your CI build
+ that simply tries to create an NHibernate ISession object from your
+ configuration. Let that test run and possibly fail before any integrated
+ tests run.
+
Now, the ReSharper plugin for NHibernate sounds pretty cool. I definitely
+ want little or no friction in renaming or adding properties in my Domain Model
+ classes (why I absolutely despise codegen your business object solutions).
+ We beat the refactoring problem by eliminating HBM.XML. As part of my New
+ Year's resolution to eliminate my exposure to angle bracket hell, we've created
+ the beginning of a Fluent Interface API to express NHibernate mappings.
+ Using copious amounts of Generics (I guess .Net code just "wants" to have lots
+ of angle brackets) and lambda expressions, we're able to express NHibernate
+ mappings in a completely compiler-checked, ReSharper-able way. Since we
+ switched to the FI, we've experienced far less trouble with mapping problems.
+ Here's a couple of examples:
+
+
+ // Simple class with properties and a single "to-many" relationship
+ public class CustomerContactMap : ClassMap<CustomerContact>
+ {
+ public CustomerContactMap()
+ {
+ Map(x => x.Name);
+ Map(x => x.Email);
+ Map(x => x.Extension);
+ Map(x => x.FaxNumber);
+ Map(x => x.TelephoneNumber);
+ Map(x => x.Title);
+ References(x => x.Customer);
+ }
+ }
+
+ // Class with a "Component"
+ public class CustomerDeliveryAddressMap : ClassMap<CustomerDeliveryAddress>
+ {
+ public CustomerDeliveryAddressMap()
+ {
+ Map(x => x.Name);
+ References(x => x.Customer);
+ Component<Address>(x => x.Address, m =>
+ {
+ m.Map(x => x.AddressLine1);
+ m.Map(x => x.AddressLine2);
+ m.Map(x => x.AddressLine3);
+ m.Map(x => x.CityName);
+ m.Map(x => x.CountryName);
+ m.References(x => x.State);
+ m.References(x => x.PostalCode);
+ });
+ }
+ }
+
+ // Class with some "has many" relationships
+ public class CustomerMap : ClassMap<Customer>
+ {
+ public CustomerMap()
+ {
+ HasMany<CustomerContact>(x => x.Contacts).CascadeAll();
+ HasMany<CustomerJob>(x => x.Jobs).CascadeAll();
+ HasMany<CustomerDeliveryAddress>(x => x.DeliveryAddresses).CascadeAll();
+
+ Map(x => x.Name);
+ Map(x => x.LookupName);
+ Map(x => x.IsGeneric);
+ Map(x => x.RequiresPurchaseOrder);
+ Map(x => x.Retired);
+ }
+ }
+
+
+
+
+
With this approach, and backed up with the little PersistenceSpecification tests,
+ we can happily change class names and property names with relative confidence.
+ Besides, the simple usage of Intellisense plus compiler safe code cuts down on
+ the number of mapping errors. We're more or less greenfield at the moment, so we can get away with generating
+ the database from our NHibernate mappings on demand, but you can specify
+ specific table and column names in the language above for brownfield scenarios.
+ I'd very confidently say that we're faster with this approach than we would be
+ with HBM.XML.
+
If you're interested, the complete code for everything I talked about is in the ShadeTree.DomainModel project in the StoryTeller codebase (and effectively released under the Apache 2.0 license). The code is at
+ http://storyteller.tigris.org/svn/storyteller/trunk/. Use the src\ShadeTree.sln for this stuff. I don't know that we'll ever get around to a fully supported release of this stuff, but I wanted to throw out the idea anyway. At this point I'm only extending this code when we need something new for our current project.
+ A lot of the advantages of this approach are tied to application specific
+ conventions and also by tieing the forward generation of the database structure
+ to validation rules.
+
+
+
As for IoC container testing...
+
I'll overlook the fact that my friend David implicitly implied that an
+ IoC container not named StructureMap was the de facto standard.
+ Bil Simser posted a little snippet of code to smoke test the configuration of one of those other IoC containers. StructureMap has had quite a bit
+ of diagnostic support since version 0.85, but StructureMap 2.5 will add the
+ ability to do this:
This will attempt to build every possible configured instance in StructureMap, perform any designated
+ environment tests (like trying to connect to a database), and generate a complete report of all errors encountered by StructureMap.
+ If you're aggressive about putting all services into your IoC container, this
+ test can help measure the correctness of an installation.
+
+
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/ConnectedTester.cs b/src/FluentNHibernate.Testing/DomainModel/ConnectedTester.cs
new file mode 100644
index 000000000..aff0f0c76
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/ConnectedTester.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using NHibernate;
+using NUnit.Framework;
+using ShadeTree.DomainModel;
+using ShadeTree.DomainModel.Fixtures;
+using ShadeTree.DomainModel.Mapping;
+using NHibernate.Linq;
+using ShadeTree.DomainModel.Query;
+using StructureMap;
+
+namespace ShadeTree.Testing.DomainModel
+{
+ [TestFixture, Explicit]
+ public class ConnectedTester
+ {
+ private SessionSource _source;
+
+ [SetUp]
+ public void SetUp()
+ {
+ IDictionary props = new Dictionary();
+ props.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
+ props.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver");
+ props.Add("dialect", "NHibernate.Dialect.MsSql2000Dialect");
+ props.Add("hibernate.dialect", "NHibernate.Dialect.MsSql2000Dialect");
+ props.Add("use_outer_join", "true");
+ props.Add("connection.connection_string", "Data Source=.;Initial Catalog=ShadeTree;Integrated Security=True;Pooling=False");
+ //props.Add("show_sql", showSql);
+ props.Add("show_sql", true.ToString());
+
+ _source = new SessionSource(props, new TestModel());
+
+ _source.BuildSchema();
+
+ ObjectFactory.Inject(_source);
+ DomainObjectFinder.ClearAllFinders();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ ObjectFactory.ResetDefaults();
+ }
+
+
+ [Test]
+ public void Spin_up_the_Linq_stuff()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record{Name = "Jeremy", Age = 34});
+ session.SaveOrUpdate(new Record{Name = "Jessica", Age = 29});
+ session.SaveOrUpdate(new Record{Name = "Natalie", Age = 25});
+ session.SaveOrUpdate(new Record{Name = "Hank", Age = 29});
+ session.SaveOrUpdate(new Record{Name = "Darrell", Age = 34});
+ session.SaveOrUpdate(new Record{Name = "Bill", Age = 34});
+ session.SaveOrUpdate(new Record{Name = "Tim", Age = 35});
+ session.SaveOrUpdate(new Record{Name = "Greg", Age = 36});
+
+ //ISession session2 = _source.CreateSession();
+ //var query = from record in session2.Linq() where record.Age < 30 select record;
+ //foreach (Record record in query.ToList())
+ //{
+ // Debug.WriteLine(record.Name);
+ //}
+
+ //return;
+
+ Repository repository = new Repository(_source.CreateSession());
+ Record[] records = repository.Query(record => record.Age == 29);
+ //records.Length.ShouldEqual(2);
+
+ foreach (var record in records)
+ {
+ Debug.WriteLine(record.Name);
+ }
+ }
+
+ [Test]
+ public void QueryBy_test()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jessica", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Natalie", Age = 25 });
+ session.SaveOrUpdate(new Record { Name = "Hank", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Darrell", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Bill", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Tim", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Greg", Age = 36 });
+
+ Repository repository = new Repository(_source.CreateSession());
+ Record record = repository.FindBy(r => r.Name, "Hank");
+ record.Name.ShouldEqual("Hank");
+ }
+
+ [Test]
+ public void Query_by_filters()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Jessica", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Natalie", Age = 25 });
+ session.SaveOrUpdate(new Record { Name = "Hank", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Darrell", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Bill", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Tim", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Greg", Age = 36 });
+
+ Repository repository = new Repository(_source.CreateSession());
+ Record record = repository.FindBy(r => r.Age == 34 && r.Name == "Jeremy");
+ record.Name.ShouldEqual("Jeremy");
+ record.Age.ShouldEqual(34);
+ }
+
+ [Test]
+ public void Query_with_multiple_expressions()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 35 });
+
+ Repository repository = new Repository(_source.CreateSession());
+ Record[] records = repository.Query(r => r.Age >= 35 && r.Name == "Jeremy");
+
+ records.ShouldHaveCount(1);
+ records[0].Name.ShouldEqual("Jeremy");
+ records[0].Age.ShouldEqual(35);
+ }
+
+ [Test]
+ public void Try_out_EntityQuery()
+ {
+ var queryDef = new EntityQueryDefinitionBuilder()
+ .AllowFilterOn(r => r.Age)
+ .AllowFilterOn(r => r.Name)
+ .QueryDefinition;
+
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 35 });
+
+ Repository repository = new Repository(_source.CreateSession());
+
+ var query = new EntityQueryBuilder(queryDef);
+ query.AddFilter(new BinaryFilterType { FilterExpressionType = ExpressionType.Equal }, queryDef.GetFilterPropertyForKey("Age"), "35");
+ query.AddFilter(new StringFilterType { StringMethod = s => s.EndsWith("") }, queryDef.GetFilterPropertyForKey("Name"), "emy");
+
+ Record[] records = repository.Query(query.FilterExpression);
+
+ records.ShouldHaveCount(1);
+ records[0].Name.ShouldEqual("Jeremy");
+ records[0].Age.ShouldEqual(35);
+ }
+
+ [Test]
+ public void Try_out_DomainObjectFinder()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Jessica", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Natalie", Age = 25 });
+ session.SaveOrUpdate(new Record { Name = "Hank", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Darrell", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Bill", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Chad", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Earl", Age = 36 });
+
+ Repository repository = new Repository(_source.CreateSession());
+ ObjectFactory.Inject(repository);
+
+
+
+
+ DomainObjectFinder.Type().IsFoundByProperty(r => r.Name);
+
+
+
+
+ Record record = DomainObjectFinder.Find("Chad");
+ record.Name.ShouldEqual("Chad");
+ }
+
+
+ [Test]
+ public void Try_out_DomainObjectFinder2()
+ {
+ ISession session = _source.CreateSession();
+
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Jeremy", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Jessica", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Natalie", Age = 25 });
+ session.SaveOrUpdate(new Record { Name = "Hank", Age = 29 });
+ session.SaveOrUpdate(new Record { Name = "Darrell", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Bill", Age = 34 });
+ session.SaveOrUpdate(new Record { Name = "Chad", Age = 35 });
+ session.SaveOrUpdate(new Record { Name = "Earl", Age = 36 });
+
+ Repository repository = new Repository(_source.CreateSession());
+ ObjectFactory.Inject(repository);
+
+
+
+
+ DomainObjectFinder.Type().IsFoundBy(name =>
+ {
+ return new Record{Name = name};
+ });
+
+
+ Record record = DomainObjectFinder.Find("Chad");
+ record.Name.ShouldEqual("Chad");
+ }
+
+ [Test]
+ public void Save_and_find()
+ {
+ Repository repository1 = new Repository(_source.CreateSession());
+ var record1 = new Record { Name = "Jeremy", Age = 34 };
+ repository1.Save(record1);
+
+ Repository repository2 = new Repository(_source.CreateSession());
+ Record record2 = repository2.Find(record1.Id);
+
+ record1.ShouldNotBeTheSameAs(record2);
+ record2.Name.ShouldEqual("Jeremy");
+ }
+
+ [Test]
+ public void MappingTest1()
+ {
+ new PersistenceSpecification()
+ .CheckProperty(r => r.Age, 22)
+ .CheckProperty(r => r.Name, "somebody")
+ .CheckProperty(r => r.Location, "somebody")
+ .VerifyTheMappings();
+
+ }
+ }
+
+ public class TestModel : PersistenceModel
+ {
+ public TestModel()
+ {
+ addMappingsFromThisAssembly();
+ }
+ }
+
+ public class RecordMap : ClassMap
+ {
+ public RecordMap()
+ {
+ UseIdentityForKey(x => x.Id, "id");
+ Map(x => x.Name);
+ Map(x => x.Age);
+ }
+ }
+
+ public class Record : Entity
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ public string Location { get; set; }
+ }
+}
diff --git a/src/FluentNHibernate.Testing/DomainModel/EntityEquality.cs b/src/FluentNHibernate.Testing/DomainModel/EntityEquality.cs
new file mode 100644
index 000000000..fbd9d16cd
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/EntityEquality.cs
@@ -0,0 +1,92 @@
+using NUnit.Framework;
+using ShadeTree.DomainModel;
+
+namespace ShadeTree.Testing.DomainModel
+{
+ [TestFixture]
+ public class EntityEquality
+ {
+ [Test]
+ public void Two_entities_with_the_same_Id_should_equal_each_other()
+ {
+ var first = new Entity{Id = 99};
+ var second = new Entity { Id = 99 };
+
+ first.Equals(second).ShouldBeTrue();
+ second.Equals(first).ShouldBeTrue();
+
+
+ Equals(first, second).ShouldBeTrue();
+ Equals(second, first).ShouldBeTrue();
+
+ first.GetHashCode().ShouldEqual(second.GetHashCode());
+
+ (first == second).ShouldBeTrue();
+ (second == first).ShouldBeTrue();
+
+ (first != second).ShouldBeFalse();
+ (second != first).ShouldBeFalse();
+ }
+
+ [Test]
+ public void Two_entities_with_different_Ids_should_not_equal_each_other()
+ {
+ var first = new Entity { Id = 66 };
+ var second = new Entity { Id = 77 };
+
+ first.Equals(second).ShouldBeFalse();
+ second.Equals(first).ShouldBeFalse();
+
+
+ Equals(first, second).ShouldBeFalse();
+ Equals(second, first).ShouldBeFalse();
+
+ first.GetHashCode().ShouldNotEqual(second.GetHashCode());
+
+ (first == second).ShouldBeFalse();
+ (second == first).ShouldBeFalse();
+
+ (first != second).ShouldBeTrue();
+ (second != first).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Subclassed_entities_should_equal_each_other_with_same_Id()
+ {
+ var first = new TestSubEntity {Id = 99};
+ var second = new TestSubEntity { Id = 99 };
+
+ first.Equals(second).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Subclassed_entities_should_not_equal_entities_of_a_different_type_even_if_the_Id_is_the_same()
+ {
+ var first = new TestSubEntity { Id = 99 };
+ var second = new AnotherSubEntity { Id = 99 };
+
+ first.Equals(second).ShouldBeFalse();
+ }
+
+ [Test]
+ public void Deep_subclassed_entities_should_not_equal_their_parent_classed_entities_even_if_the_Id_is_the_same()
+ {
+ var first = new TestSubEntity { Id = 99 };
+ var second = new DeepSubEntity { Id = 99 };
+
+ first.Equals(second).ShouldBeFalse();
+ }
+
+ public class TestSubEntity : Entity
+ {
+ }
+
+ public class AnotherSubEntity : Entity
+ {
+ }
+
+ public class DeepSubEntity : TestSubEntity
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Generation/CodeFileSmokeTester.cs b/src/FluentNHibernate.Testing/DomainModel/Generation/CodeFileSmokeTester.cs
new file mode 100644
index 000000000..b81bbad06
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Generation/CodeFileSmokeTester.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using ShadeTree.DomainModel.Generation;
+
+namespace ShadeTree.Testing.DomainModel.Generation
+{
+ [TestFixture]
+ public class CodeFileSmokeTester
+ {
+ [Test]
+ public void SmokeTest_Code_File()
+ {
+ CodeFile file = new CodeFile("Fixtures.cs", "DomainFixtureGeneration");
+ file.AddDomainType();
+ file.AddDomainType();
+
+ file.WriteToConsole();
+ file.WriteToFile();
+ }
+ }
+}
diff --git a/src/FluentNHibernate.Testing/DomainModel/InMemoryRepositoryTester.cs b/src/FluentNHibernate.Testing/DomainModel/InMemoryRepositoryTester.cs
new file mode 100644
index 000000000..a178d2376
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/InMemoryRepositoryTester.cs
@@ -0,0 +1,85 @@
+using System.Diagnostics;
+using NUnit.Framework;
+using ShadeTree.DomainModel;
+
+namespace ShadeTree.Testing.DomainModel
+{
+ [TestFixture]
+ public class InMemoryRepositoryTester
+ {
+ [Test]
+ public void Store_and_find_two_different_types_of_objects()
+ {
+ InMemoryRepository repository = new InMemoryRepository();
+ repository.Save(new Case {Id = 2});
+ repository.Save(new Case {Id = 3});
+ repository.Save(new Contact {Id = 2});
+ repository.Save(new Contact {Id = 3});
+
+ repository.Find(2).Id.ShouldEqual(2);
+ repository.Find(3).ShouldBeOfType(typeof(Contact));
+ repository.Find(3).ShouldBeOfType(typeof(Case));
+ }
+
+ [Test]
+ public void Query_by_expression()
+ {
+ InMemoryRepository repository = new InMemoryRepository();
+ repository.Save(new Case{Name = "Jeremy", Number = 10});
+ repository.Save(new Case{Name = "Darth Vader", Number = 5});
+ repository.Save(new Case{Name = "Darth Maul", Number = 6});
+ repository.Save(new Case{Name = "Luke", Number = 12});
+ repository.Save(new Case{Name = "Han Solo", Number = 6});
+ repository.Save(new Case{Name = "Eric", Number = 5});
+ repository.Save(new Case{Name = "Corwin", Number = 4});
+
+ repository.Query(c => c.Name.StartsWith("Darth")).Length.ShouldEqual(2);
+ repository.Query(c => c.Number == 6).Length.ShouldEqual(2);
+ Case[] cases = repository.Query(c => c.Number > 5);
+ cases.Length.ShouldEqual(4);
+ }
+
+ [Test]
+ public void Find_by_property()
+ {
+ InMemoryRepository repository = new InMemoryRepository();
+ repository.Save(new Case { Name = "Jeremy", Number = 10 });
+ repository.Save(new Case { Name = "Darth Vader", Number = 5 });
+ repository.Save(new Case { Name = "Darth Maul", Number = 6 });
+ repository.Save(new Case { Name = "Luke", Number = 12 });
+ repository.Save(new Case { Name = "Han Solo", Number = 6 });
+ repository.Save(new Case { Name = "Eric", Number = 5 });
+ repository.Save(new Case { Name = "Corwin", Number = 4 });
+
+ repository.FindBy(c => c.Name, "Jeremy").Name.ShouldEqual("Jeremy");
+ }
+
+ [Test]
+ public void Find_by_query()
+ {
+ InMemoryRepository repository = new InMemoryRepository();
+ repository.Save(new Case { Name = "Jeremy", Number = 10 });
+ repository.Save(new Case { Name = "Darth Vader", Number = 5 });
+ repository.Save(new Case { Name = "Darth Maul", Number = 6 });
+ repository.Save(new Case { Name = "Luke", Number = 12 });
+ repository.Save(new Case { Name = "Han Solo", Number = 6 });
+ repository.Save(new Case { Name = "Eric", Number = 5 });
+ repository.Save(new Case { Name = "Corwin", Number = 4 });
+
+ repository.FindBy(c => c.Name == "Corwin").Name.ShouldEqual("Corwin");
+ repository.FindBy(c => c.Number == 10).Name.ShouldEqual("Jeremy");
+ repository.FindBy(c => c.Number == 6 && c.Name == "Han Solo").Name.ShouldEqual("Han Solo");
+ }
+
+ }
+
+ public class Case : Entity
+ {
+ public long Number { get; set; }
+ public string Name { get; set; }
+ }
+
+ public class Contact : Entity
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs
new file mode 100644
index 000000000..c7ec4964f
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs
@@ -0,0 +1,390 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Xml;
+using NUnit.Framework;
+using ShadeTree.DomainModel;
+using ShadeTree.DomainModel.Mapping;
+using ShadeTree.Validation;
+
+namespace ShadeTree.Testing.DomainModel.Mapping
+{
+ [TestFixture]
+ public class ClassMapXmlCreationTester
+ {
+ #region Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ }
+
+ #endregion
+
+ private XmlDocument document;
+
+ private XmlElement elementForProperty(string propertyName)
+ {
+ string xpath = string.Format("class/property[@name='{0}']", propertyName);
+ return (XmlElement) document.DocumentElement.SelectSingleNode(xpath);
+ }
+
+ [Test, Ignore("Needs to be rewritten at some point")]
+ public void BasicManyToManyMapping()
+ {
+ var map = new ClassMap();
+ map.HasManyToMany(x => x.Children);
+
+ document = map.CreateMapping(new MappingVisitor());
+ var element = (XmlElement) document.DocumentElement.SelectSingleNode("class/bag[@name='Children']");
+
+ element.AttributeShouldEqual("name", "Children");
+ element.AttributeShouldEqual("cascade", "none");
+
+ element["key"].AttributeShouldEqual("column", "MappedObject_Fk");
+ element["many-to-many"].AttributeShouldEqual("class", typeof (ChildObject).AssemblyQualifiedName);
+ element["many-to-many"].AttributeShouldEqual("table", typeof (ChildObject).Name);
+ element["many-to-many"].AttributeShouldEqual("column", "ChildObject_Fk");
+ }
+
+ [Test]
+ public void BasicOneToManyMapping()
+ {
+ var map = new ClassMap();
+ map.HasMany(x => x.Children);
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ var element =
+ (XmlElement) document.DocumentElement.SelectSingleNode("class/bag[@name='Children']");
+
+ element.AttributeShouldEqual("name", "Children");
+ element.AttributeShouldEqual("cascade", "none");
+
+ element["key"].AttributeShouldEqual("column", "MappedObject_id");
+ element["one-to-many"].AttributeShouldEqual("class", typeof (ChildObject).AssemblyQualifiedName);
+ }
+
+ [Test]
+ public void AdvancedOneToManyMapping()
+ {
+ var map = new ClassMap();
+ map.HasMany(x => x.Children).LazyLoad().IsInverse();
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ var element =
+ (XmlElement)document.DocumentElement.SelectSingleNode("class/bag[@name='Children']");
+
+ element.AttributeShouldEqual("lazy", "true");
+ element.AttributeShouldEqual("inverse", "true");
+ }
+
+ [Test]
+ public void BuildTheHeaderXmlWithAssemblyAndNamespace()
+ {
+ var map = new ClassMap();
+ document = map.CreateMapping(new MappingVisitor());
+
+ document.DocumentElement.GetAttribute("assembly").ShouldEqual(typeof (MappedObject).Assembly.GetName().Name);
+ document.DocumentElement.GetAttribute("namespace").ShouldEqual(typeof (MappedObject).Namespace);
+ }
+
+ [Test]
+ public void CascadeAll_with_many_to_many()
+ {
+ var map = new ClassMap();
+ map.HasManyToMany(x => x.Children).CascadeAll();
+
+ document = map.CreateMapping(new MappingVisitor());
+ var element = (XmlElement) document.DocumentElement.SelectSingleNode("class/bag[@name='Children']");
+
+ element.AttributeShouldEqual("cascade", "all");
+ }
+
+ [Test]
+ public void CascadeAll_with_one_to_many()
+ {
+ var map = new ClassMap();
+ map.HasMany(x => x.Children).CascadeAll();
+
+ document = map.CreateMapping(new MappingVisitor());
+ var element =
+ (XmlElement) document.DocumentElement.SelectSingleNode("class/bag[@name='Children']");
+
+ element.AttributeShouldEqual("cascade", "all");
+ }
+
+ [Test]
+ public void Create_a_component_mapping()
+ {
+ var map = new ClassMap();
+ map.Component(x => x.Component, c =>
+ {
+ c.Map(x => x.Name);
+ c.Map(x => x.Age);
+ });
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ var componentElement =
+ (XmlElement) document.DocumentElement.SelectSingleNode("class/component");
+
+ componentElement.AttributeShouldEqual("name", "Component");
+ componentElement.AttributeShouldEqual("insert", "true");
+ componentElement.AttributeShouldEqual("update", "true");
+
+ componentElement.ShouldHaveChild("property[@name='Name']");
+ componentElement.ShouldHaveChild("property[@name='Age']");
+ }
+
+
+ [Test]
+ public void
+ Create_a_component_mapping_for_a_component_that_is_not_required_and_the_fields_for_the_component_should_not_be_non_null
+ ()
+ {
+ var map = new ClassMap();
+ map.Component(x => x.Parent, c => { c.Map(x => x.Name); });
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ var element =
+ (XmlElement) document.DocumentElement.SelectSingleNode("class/component/property[@name='Name']");
+
+ Debug.WriteLine(element.OuterXml);
+
+ element.HasAttribute("not-null").ShouldBeFalse();
+ }
+
+ [Test]
+ public void Create_a_component_mapping_for_a_required_child_and_set_the_required_fields_of_component_to_non_null
+ ()
+ {
+ var map = new ClassMap();
+ map.Component(x => x.Component, c =>
+ {
+ c.Map(x => x.Name);
+ c.Map(x => x.Age);
+ });
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ var componentElement =
+ (XmlElement) document.DocumentElement.SelectSingleNode("class/component");
+
+ var propertyElement = (XmlElement) componentElement.SelectSingleNode("property[@name = 'Age']");
+ propertyElement.AttributeShouldEqual("not-null", "true");
+ }
+
+ [Test]
+ public void CreateDiscriminator()
+ {
+ var map = new ClassMap();
+ map.DiscriminateSubClassesOnColumn("Type");
+
+ document = map.CreateMapping(new MappingVisitor());
+ var element = (XmlElement) document.DocumentElement.SelectSingleNode("class/discriminator");
+ element.AttributeShouldEqual("column", "Type");
+ element.AttributeShouldEqual("type", "String");
+ }
+
+ [Test]
+ public void CreateTheSubClassMappings()
+ {
+ var map = new ClassMap();
+
+ map.DiscriminateSubClassesOnColumn("Type")
+ .SubClass().IsIdentifiedBy("red")
+ .MapSubClassColumns(m => { m.Map(x => x.Name); });
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ Debug.WriteLine(document.OuterXml);
+
+ var element = (XmlElement) document.DocumentElement.SelectSingleNode("//subclass");
+ element.AttributeShouldEqual("name", "SecondMappedObject");
+ element.AttributeShouldEqual("discriminator-value", "red");
+
+ XmlElement propertyElement = element["property"];
+ propertyElement.AttributeShouldEqual("column", "Name");
+ }
+
+ [Test]
+ public void Creating_a_many_to_one_reference()
+ {
+ var map = new ClassMap();
+ map.References(x => x.Parent);
+
+ document = map.CreateMapping(new MappingVisitor());
+ var element = (XmlElement) document.DocumentElement.SelectSingleNode("class/many-to-one");
+
+ element.AttributeShouldEqual("name", "Parent");
+ element.AttributeShouldEqual("cascade", "all");
+ element.AttributeShouldEqual("column", "Parent_id");
+ }
+
+ [Test]
+ public void Creating_a_many_to_one_reference_sets_the_column_overrides()
+ {
+ var map = new ClassMap();
+ map.References(x => x.Parent);
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ Debug.WriteLine(document.DocumentElement.OuterXml);
+
+ var element = (XmlElement)document.DocumentElement.SelectSingleNode("class/many-to-one");
+
+ element.AttributeShouldEqual("foreign-key", "FK_MappedObjectToParent");
+ }
+
+ [Test]
+ public void DetermineTheTableName()
+ {
+ var map = new ClassMap();
+ map.TableName.ShouldEqual("MappedObject");
+
+ map.TableName = "Different";
+ map.TableName.ShouldEqual("Different");
+ }
+
+ [Test]
+ public void DomainClassMapAutomaticallyCreatesTheId()
+ {
+ var map = new ClassMap();
+ map.UseIdentityForKey(x => x.Id, "id");
+ document = map.CreateMapping(new MappingVisitor());
+
+ XmlElement idElement = document.DocumentElement["class"]["id"];
+ idElement.ShouldNotBeNull();
+
+ idElement.GetAttribute("name").ShouldEqual("Id");
+ idElement.GetAttribute("column").ShouldEqual("id");
+ idElement.GetAttribute("type").ShouldEqual("Int64");
+ idElement.GetAttribute("unsaved-value").ShouldEqual("0");
+
+ XmlElement generatorElement = idElement["generator"];
+ generatorElement.ShouldNotBeNull();
+ generatorElement.GetAttribute("class").ShouldEqual("identity");
+ }
+
+ [Test]
+ public void Map_an_enumeration()
+ {
+ var map = new ClassMap();
+ map.Map(x => x.Color);
+
+ document = map.CreateMapping(new MappingVisitor());
+ XmlElement element = elementForProperty("Color");
+
+ Debug.WriteLine(element.OuterXml);
+
+ element.AttributeShouldEqual("type", typeof (GenericEnumMapper).AssemblyQualifiedName);
+ element["column"].AttributeShouldEqual("name", "Color");
+ element["column"].AttributeShouldEqual("sql-type", "string");
+ element["column"].AttributeShouldEqual("length", "50");
+ }
+
+ [Test]
+ public void MapASimplePropertyWithNoOverrides()
+ {
+ var map = new ClassMap();
+ map.Map(x => x.Name);
+
+ document = map.CreateMapping(new MappingVisitor());
+ XmlElement element = elementForProperty("Name");
+
+ element.AttributeShouldEqual("name", "Name");
+ element.AttributeShouldEqual("column", "Name");
+ element.AttributeShouldEqual("type", "String");
+ }
+
+ [Test]
+ public void SimpleProperty_picks_up_maximum_length_for_string_fields()
+ {
+ var map = new ClassMap();
+ map.Map(x => x.Name);
+ map.Map(x => x.NickName);
+
+ document = map.CreateMapping(new MappingVisitor());
+
+ Debug.WriteLine(document.DocumentElement.OuterXml);
+
+ elementForProperty("Name").AttributeShouldEqual("length", "100");
+ elementForProperty("NickName").AttributeShouldEqual("length", "10");
+ }
+
+ [Test]
+ public void SimpleProperty_picks_up_not_null_for_required()
+ {
+ var map = new ClassMap();
+ map.Map(x => x.Name);
+ map.Map(x => x.NickName);
+
+ document = map.CreateMapping(new MappingVisitor());
+ elementForProperty("Name").AttributeShouldEqual("not-null", "true");
+ elementForProperty("NickName").DoesNotHaveAttribute("not-null");
+ }
+
+ [Test]
+ public void WriteTheClassNode()
+ {
+ var map = new ClassMap();
+ document = map.CreateMapping(new MappingVisitor());
+
+ XmlElement classElement = document.DocumentElement["class"];
+ classElement.ShouldNotBeNull();
+
+ classElement.AttributeShouldEqual("name", typeof (MappedObject).Name);
+ classElement.AttributeShouldEqual("table", map.TableName);
+ }
+ }
+
+ public class SecondMappedObject
+ {
+ [Required]
+ public string Name { get; set; }
+ public long Id { get; set; }
+ }
+
+ public class ComponentOfMappedObject
+ {
+ public string Name { get; set; }
+
+ [Required]
+ public int Age { get; set; }
+ }
+
+ public enum ColorEnum
+ {
+ Blue,
+ Green,
+ Red
+ }
+
+ public class MappedObject
+ {
+ public ColorEnum Color { get; set; }
+
+ [Required]
+ public ComponentOfMappedObject Component { get; set; }
+
+ public SecondMappedObject Parent { get; set; }
+
+ [Required]
+ public string Name { get; set; }
+
+ [MaximumStringLength(10)]
+ public string NickName { get; set; }
+
+
+ public IList Children { get; set; }
+
+ public long Id { get; set; }
+ }
+
+ public class ChildObject
+ {
+ public int Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ConventionsTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ConventionsTester.cs
new file mode 100644
index 000000000..1e265aafb
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ConventionsTester.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Reflection;
+using NUnit.Framework;
+using Rhino.Mocks;
+using ShadeTree.Core;
+using ShadeTree.Core.Validation;
+using ShadeTree.DomainModel;
+using ShadeTree.DomainModel.Mapping;
+
+namespace ShadeTree.Testing.DomainModel.Mapping
+{
+ [TestFixture]
+ public class ConventionsTester
+ {
+ [Test]
+ public void Set_the_convention_for_foreign_keys()
+ {
+ new Conventions().GetForeignKeyNameOfParent(typeof (Invoice)).ShouldEqual("Invoice_id");
+ }
+
+ [Test]
+ public void Get_the_convention_for_a_relationship()
+ {
+ new Conventions().GetForeignKeyName(typeof (Site).GetProperty("Primary")).ShouldEqual("Primary_id");
+ }
+
+ [Test]
+ public void add_property_convention_for_type_of_attribute()
+ {
+ MockRepository mocks = new MockRepository();
+ IProperty property = mocks.DynamicMock();
+
+
+ using (mocks.Record())
+ {
+ PropertyInfo propertyInfo = ReflectionHelper.GetProperty(s => s.Name);
+ Expect.Call(property.Property).Return(propertyInfo).Repeat.Any();
+ Expect.Call(property.PropertyType).Return(typeof(string)).Repeat.Any();
+
+ property.SetAttributeOnColumnElement("My", "true");
+ }
+
+ using (mocks.Playback())
+ {
+ var conventions = new Conventions();
+ conventions.ForAttribute((a, p) => p.SetAttributeOnColumnElement("My", "true"));
+ conventions.AlterMap(property);
+ }
+ }
+
+ [Test]
+ public void add_unique_property_for_Unique_attribute_by_default()
+ {
+ MockRepository mocks = new MockRepository();
+ IProperty property = mocks.DynamicMock();
+
+
+ using (mocks.Record())
+ {
+ PropertyInfo propertyInfo = ReflectionHelper.GetProperty(s => s.LastName);
+ Expect.Call(property.Property).Return(propertyInfo).Repeat.Any();
+ Expect.Call(property.PropertyType).Return(typeof(string)).Repeat.Any();
+
+ property.SetAttributeOnColumnElement("unique", "true");
+ }
+
+ using (mocks.Playback())
+ {
+ var conventions = new Conventions();
+ conventions.AlterMap(property);
+ }
+ }
+ }
+
+ public class Invoice{}
+
+ public class Site
+ {
+ [My]
+ public string Name { get; set; }
+
+ [Unique]
+ public string LastName { get; set; }
+
+ public Address Primary { get; set; }
+ public Address Secondary { get; set; }
+ }
+ public class Address{}
+
+ public class MyAttribute : Attribute
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyMapTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyMapTester.cs
new file mode 100644
index 000000000..47c77486b
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyMapTester.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Xml;
+using NUnit.Framework;
+using ShadeTree.Core;
+using ShadeTree.DomainModel;
+using ShadeTree.DomainModel.Mapping;
+
+namespace ShadeTree.Testing.DomainModel.Mapping
+{
+ [TestFixture]
+ public class PropertyMapTester
+ {
+ [Test]
+ public void SetAttributeOnColumnElement()
+ {
+ PropertyInfo property = ReflectionHelper.GetProperty(x => x.Name);
+ var map = new PropertyMap(property, false, property.Name, typeof(PropertyTarget));
+ map.SetAttributeOnColumnElement("unique", "true");
+
+ var document = new XmlDocument();
+ XmlElement classElement = document.CreateElement("root");
+ map.Write(classElement, new MappingVisitor());
+
+ var columnElement = (XmlElement) classElement.SelectSingleNode("property/column");
+ columnElement.AttributeShouldEqual("unique", "true");
+ }
+ }
+
+ public class PropertyTarget
+ {
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Query/EntityQueryDefinitionTester.cs b/src/FluentNHibernate.Testing/DomainModel/Query/EntityQueryDefinitionTester.cs
new file mode 100644
index 000000000..c5a04fc94
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Query/EntityQueryDefinitionTester.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using NUnit.Framework;
+using ShadeTree.DomainModel;
+using ShadeTree.DomainModel.Query;
+
+namespace ShadeTree.Testing.DomainModel.Query
+{
+ public class FooClass
+ {
+ public string foo { get; set; }
+ }
+
+ [TestFixture]
+ public class EntityQueryDefinitionTester
+ {
+ [Test]
+ public void AddQuickFilterFor_should_add_new_FilterableProperty_to_the_quick_filters_collection_as_well_as_the_general_filter_collection()
+ {
+ var query = new EntityQueryDefinition();
+ var filterProp = FilterableProperty.Build(x => x.foo);
+
+ query.AddQuickFilterProperty(filterProp);
+
+ query.QuickFilterProperties.ShouldHaveCount(1);
+ query.FilterableProperties.ShouldHaveCount(1);
+ }
+
+ [Test]
+ public void AllowFilterOn_should_add_new_FilterableProperty_to_the_query_filter_collection()
+ {
+ var query = new EntityQueryDefinition();
+ var filterProp = FilterableProperty.Build(x => x.foo);
+
+ query.AddFilterProperty(filterProp);
+
+ query.FilterableProperties.ShouldHaveCount(1);
+ }
+ }
+
+ [TestFixture]
+ public class EntityQueryDefinitionBuilderTester
+ {
+ [Test]
+ public void
+ AddQuickFilterFor_should_add_new_FilterableProperty_to_the_quick_filters_collection_as_well_as_the_general_filter_collection
+ ()
+ {
+ var query = new EntityQueryDefinitionBuilder();
+
+ query.AddQuickFilterFor(t => t.Name);
+
+ query.QueryDefinition.QuickFilterProperties.ShouldHaveCount(1);
+ query.QueryDefinition.FilterableProperties.ShouldHaveCount(1);
+ }
+
+ [Test]
+ public void AllowFilterOn_should_add_new_FilterableProperty_to_the_query_filter_collection()
+ {
+ var query = new EntityQueryDefinitionBuilder();
+
+ query.AllowFilterOn(t => t.Name);
+
+ query.QueryDefinition.FilterableProperties.ShouldHaveCount(1);
+ }
+ }
+
+ [TestFixture]
+ public class EntityQueryBuilderTester
+ {
+ #region Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ _queryDef = new EntityQueryDefinition();
+ _query = new EntityQueryBuilder(_queryDef);
+
+ _nameProp = FilterableProperty.Build(t => t.Name);
+ _nameProp.PropertyType.ShouldEqual(typeof (string));
+
+ _ageProp = FilterableProperty.Build(t => t.Age);
+
+ FilterTypeRegistry.ResetAll();
+ }
+
+ #endregion
+
+ private EntityQueryBuilder _query;
+ private FilterableProperty _nameProp;
+ private FilterableProperty _ageProp;
+ private EntityQueryDefinition _queryDef;
+
+ [Test]
+ public void EntityQueryBuilder_AddFilter_should_add_a_new_filter_and_add_it_to_the_previous_filter_using_the_AndAlso_operator()
+ {
+ _query.AddFilter(new StringFilterType {StringMethod = s => s.StartsWith("")}, _nameProp, "Fo");
+ _query.AddFilter(new BinaryFilterType {FilterExpressionType = ExpressionType.Equal}, _ageProp, "35");
+
+ _query.FilterExpression.ToString().Contains("&&").ShouldBeTrue();
+ }
+
+ [Test]
+ public void EntityQueryBuilder_AddFilter_should_change_the_type_if_the_value_type_is_not_a_string()
+ {
+ _query.AddFilter(new BinaryFilterType {FilterExpressionType = ExpressionType.Equal}, _ageProp, "35");
+
+ _query.FilterExpression.ToString().Contains("ChangeType").ShouldBeTrue();
+ }
+
+ [Test]
+ public void EntityQueryBuilder_AddFilter_should_not_change_the_type_if_the_value_type_is_already_a_string()
+ {
+ _query.AddFilter(new StringFilterType {StringMethod = s => s.StartsWith("")}, _nameProp, "Fo");
+
+ _query.FilterExpression.ToString().Contains("ChangeType").ShouldBeFalse();
+ }
+
+ [Test]
+ public void EntityQueryBuilder_AddFilter_SmokeTest()
+ {
+ _query.AddFilter(new StringFilterType {StringMethod = s => s.StartsWith("")}, _nameProp, "Fo");
+ _query.AddFilter(new BinaryFilterType {FilterExpressionType = ExpressionType.Equal}, _ageProp, "35");
+
+ var itemList = new List
+ {
+ new QueryDefTestEntity {Name = "Bar", Age = 99},
+ new QueryDefTestEntity {Name = "Foo", Age = 35},
+ new QueryDefTestEntity {Name = "Baz", Age = 1}
+ };
+
+ QueryDefTestEntity[] values = itemList.AsQueryable().Where(_query.FilterExpression).ToArray();
+
+ values.Length.ShouldEqual(1);
+ values[0].Name.ShouldEqual("Foo");
+ values[0].Age.ShouldEqual(35);
+ }
+
+ [Test]
+ public void EntityQueryBuilder_PrepareQuery_should_loop_through_each_criterion_and_set_up_a_filter()
+ {
+ var repo = new InMemoryRepository();
+ repo.Save(new QueryDefTestEntity {Name = "Bar", Age = 99});
+ repo.Save(new QueryDefTestEntity {Name = "Foo", Age = 35});
+ repo.Save(new QueryDefTestEntity {Name = "Baz", Age = 1});
+
+ FilterTypeRegistry.ClearAll();
+
+ FilterTypeRegistry.RegisterFilter(
+ new StringFilterType
+ {
+ Key = "StringFoo",
+ StringMethod = s => s.StartsWith("")
+ })
+ .ForType();
+
+ _queryDef.AddFilterProperty(FilterableProperty.Build(t => t.Name));
+
+
+ var crit = new Criteria
+ {property = "Name", op = "StringFoo", value = "Fo"};
+
+ QueryDefTestEntity[] values =
+ _query.PrepareQuery(new[] {crit}, repo).Cast().ToArray();
+
+ values.Length.ShouldEqual(1);
+ values[0].Name.ShouldEqual("Foo");
+ values[0].Age.ShouldEqual(35);
+ }
+ }
+
+ public class QueryDefTestEntity
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeRegistryTester.cs b/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeRegistryTester.cs
new file mode 100644
index 000000000..0f87ec832
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeRegistryTester.cs
@@ -0,0 +1,29 @@
+using NUnit.Framework;
+using ShadeTree.DomainModel.Query;
+
+namespace ShadeTree.Testing.DomainModel.Query
+{
+ [TestFixture]
+ public class FilterTypeRegistryTester
+ {
+ [SetUp]
+ public void SetUp()
+ {
+ FilterTypeRegistry.ResetAll();
+ }
+
+ [Test]
+ public void should_return_expected_filter_types_for_string_type()
+ {
+ var filters = FilterTypeRegistry.GetFiltersFor();
+ filters.ShouldContain(x => x.Key == "STARTSWITH");
+ }
+
+ [Test]
+ public void should_return_expected_filter_types_for_int32_type()
+ {
+ var filters = FilterTypeRegistry.GetFiltersFor();
+ filters.ShouldContain(x => x.Key == "EQUAL");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeTester.cs b/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeTester.cs
new file mode 100644
index 000000000..c7521c46c
--- /dev/null
+++ b/src/FluentNHibernate.Testing/DomainModel/Query/FilterTypeTester.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Linq.Expressions;
+using NUnit.Framework;
+using ShadeTree.DomainModel.Query;
+
+namespace ShadeTree.Testing.DomainModel.Query
+{
+ [TestFixture]
+ public class StringFilterTypeTester : FilterTypeSpec
+ {
+ [Test]
+ public void StringFilterType_should_generate_an_expression_that_calls_the_designated_string_method_on_the_value()
+ {
+ _filter.StringMethod = s => s.StartsWith("");
+ ForMember(t=>t.Name).AndValue("Foo").TheResultExpression.ShouldEqual("Invoke(t => t.Name,target).StartsWith(\"Foo\")");
+ }
+ }
+
+ public class BinaryFilterTypeTester : FilterTypeSpec
+ {
+ [Test]
+ public void BinaryFilterType_should_generate_an_expression_that_calls_the_correct_operator_on_the_value()
+ {
+ _filter.FilterExpressionType = ExpressionType.GreaterThan;
+ ForMember(t => t.Age).AndValue(99).TheResultExpression.ShouldEqual("(Invoke(t => t.Age,target) > 99)");
+ }
+ }
+
+ public class FilterTypeSpec where FILTERTYPE : IFilterType, new()
+ {
+ protected FILTERTYPE _filter = new FILTERTYPE();
+ private ConstantExpression _valueExpr;
+ private InvocationExpression _memberAccessExpr;
+
+ public FilterTypeSpec ForMember(Expression> memberExpression)
+ {
+ _memberAccessExpr = Expression.Invoke(memberExpression, Expression.Parameter(typeof(ENTITYTYPE), "target"));
+ return this;
+ }
+
+ public FilterTypeSpec AndValue(VALUE value)
+ {
+ _valueExpr = Expression.Constant(value);
+ return this;
+ }
+
+ public string TheResultExpression
+ {
+ get
+ {
+ return _filter.GetExpression(_memberAccessExpr, _valueExpr).ToString();
+ }
+ }
+ }
+
+ public class FilterTestEntity
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/Fixtures.cs b/src/FluentNHibernate.Testing/Fixtures.cs
new file mode 100644
index 000000000..ae0e8a335
--- /dev/null
+++ b/src/FluentNHibernate.Testing/Fixtures.cs
@@ -0,0 +1,127 @@
+using fit;
+using fitlibrary;
+using ShadeTree.DomainModel.Fixtures;
+using ShadeTree.Testing.DomainModel;
+using StoryTeller.FixtureModel;
+using System;
+
+namespace DomainFixtureGeneration
+{
+
+ public partial class CaseFixture : DomainClassFixture
+ {
+
+ public CaseFixture() : base() { }
+ public CaseFixture(Case subject) : base(subject) { }
+
+
+ [Example("|Number is|[value]|")]
+ public void NumberIs(Int64 propertyValue)
+ {
+ Subject.Number = propertyValue;
+ }
+
+ [Example("|Check|Number|is|[value]|")]
+ public System.Int64 NumberIs()
+ {
+ return Subject.Number;
+ }
+
+
+ [Example("|Name is|[value]|")]
+ public void NameIs(String propertyValue)
+ {
+ Subject.Name = propertyValue;
+ }
+
+ [Example("|Check|Name|is|[value]|")]
+ public System.String NameIs()
+ {
+ return Subject.Name;
+ }
+
+
+ [Example("|Id is|[value]|")]
+ public void IdIs(Int64 propertyValue)
+ {
+ Subject.Id = propertyValue;
+ }
+
+ [Example("|Check|Id|is|[value]|")]
+ public System.Int64 IdIs()
+ {
+ return Subject.Id;
+ }
+
+ }
+
+ public partial class CaseListFixture : DomainListFixture
+ {
+
+ public CaseListFixture() : base() { }
+ public CaseListFixture(Action doneAction) : base(doneAction) { }
+
+
+ public System.Int64 Number
+ {
+ set
+ {
+ subject.Number = value;
+ }
+ }
+
+ public System.String Name
+ {
+ set
+ {
+ subject.Name = value;
+ }
+ }
+
+ public System.Int64 Id
+ {
+ set
+ {
+ subject.Id = value;
+ }
+ }
+ }
+
+ public partial class ContactFixture : DomainClassFixture
+ {
+
+ public ContactFixture() : base() { }
+ public ContactFixture(Contact subject) : base(subject) { }
+
+
+ [Example("|Id is|[value]|")]
+ public void IdIs(Int64 propertyValue)
+ {
+ Subject.Id = propertyValue;
+ }
+
+ [Example("|Check|Id|is|[value]|")]
+ public System.Int64 IdIs()
+ {
+ return Subject.Id;
+ }
+
+ }
+
+ public partial class ContactListFixture : DomainListFixture
+ {
+
+ public ContactListFixture() : base() { }
+ public ContactListFixture(Action doneAction) : base(doneAction) { }
+
+
+ public System.Int64 Id
+ {
+ set
+ {
+ subject.Id = value;
+ }
+ }
+ }
+}
+
diff --git a/src/FluentNHibernate.Testing/FluentNHibernate.Testing.csproj b/src/FluentNHibernate.Testing/FluentNHibernate.Testing.csproj
new file mode 100644
index 000000000..7ca52d55e
--- /dev/null
+++ b/src/FluentNHibernate.Testing/FluentNHibernate.Testing.csproj
@@ -0,0 +1,109 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}
+ Library
+ Properties
+ FluentNHibernate.Testing
+ FluentNHibernate.Testing
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\..\tools\fit\fit.dll
+
+
+ False
+ ..\..\tools\NHibernate\NHibernate.dll
+
+
+ False
+ ..\..\tools\NHibernate\NHibernate.Linq.dll
+
+
+ False
+ ..\..\tools\nunit\nunit.framework.dll
+
+
+ False
+ ..\..\tools\rhino\Rhino.Mocks.dll
+
+
+ False
+ ..\..\tools\ShadeTree\ShadeTree.Core.dll
+
+
+ False
+ ..\..\tools\StructureMap\StructureMap.dll
+
+
+
+ 3.5
+
+
+ 3.5
+
+
+
+
+
+
+ CommonAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {1C988DFB-1EC5-484E-87D9-1D3C775BA435}
+ ShadeTree.DomainModel
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/Properties/AssemblyInfo.cs b/src/FluentNHibernate.Testing/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..3b2da68c2
--- /dev/null
+++ b/src/FluentNHibernate.Testing/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+using System.Reflection;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly : AssemblyTitle("FluentNHibernate.Testing")]
+[assembly : AssemblyDescription("")]
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/SpecificationExtensions.cs b/src/FluentNHibernate.Testing/SpecificationExtensions.cs
new file mode 100644
index 000000000..d98c6ada4
--- /dev/null
+++ b/src/FluentNHibernate.Testing/SpecificationExtensions.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml;
+using NUnit.Framework;
+
+namespace ShadeTree.Testing
+{
+ public delegate void MethodThatThrows();
+
+ public static class SpecificationExtensions
+ {
+ public static void ShouldBeFalse(this bool condition)
+ {
+ Assert.IsFalse(condition);
+ }
+
+ public static void ShouldBeTrue(this bool condition)
+ {
+ Assert.IsTrue(condition);
+ }
+
+ public static object ShouldEqual(this object actual, object expected)
+ {
+ Assert.AreEqual(expected, actual);
+ return expected;
+ }
+
+ public static object ShouldNotEqual(this object actual, object expected)
+ {
+ Assert.AreNotEqual(expected, actual);
+ return expected;
+ }
+
+ public static void ShouldBeNull(this object anObject)
+ {
+ Assert.IsNull(anObject);
+ }
+
+ public static void ShouldNotBeNull(this object anObject)
+ {
+ Assert.IsNotNull(anObject);
+ }
+
+ public static object ShouldBeTheSameAs(this object actual, object expected)
+ {
+ Assert.AreSame(expected, actual);
+ return expected;
+ }
+
+ public static object ShouldNotBeTheSameAs(this object actual, object expected)
+ {
+ Assert.AreNotSame(expected, actual);
+ return expected;
+ }
+
+ public static void ShouldBeOfType(this object actual, Type expected)
+ {
+ Assert.IsInstanceOfType(expected, actual);
+ }
+
+ public static void ShouldNotBeOfType(this object actual, Type expected)
+ {
+ Assert.IsNotInstanceOfType(expected, actual);
+ }
+
+ public static void ShouldContain(this IList actual, object expected)
+ {
+ Assert.Contains(expected, actual);
+ }
+
+ public static void ShouldContain(this IEnumerable actual, T expected)
+ {
+ ShouldContain(actual, x => x.Equals(expected));
+ }
+
+ public static void ShouldContain(this IEnumerable actual, Func expected)
+ {
+ actual.Single(expected).ShouldNotEqual(default(T));
+ }
+
+ public static void ShouldBeEmpty(this IEnumerable actual)
+ {
+ actual.Count().ShouldEqual(0);
+ }
+
+ public static void ShouldHaveCount(this IEnumerable actual, int expected)
+ {
+ actual.Count().ShouldEqual(expected);
+ }
+
+ public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2)
+ {
+ Assert.Greater(arg1, arg2);
+ return arg2;
+ }
+
+ public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2)
+ {
+ Assert.Less(arg1, arg2);
+ return arg2;
+ }
+
+ public static void ShouldBeEmpty(this ICollection collection)
+ {
+ Assert.IsEmpty(collection);
+ }
+
+ public static void ShouldBeEmpty(this string aString)
+ {
+ Assert.IsEmpty(aString);
+ }
+
+ public static void ShouldNotBeEmpty(this ICollection collection)
+ {
+ Assert.IsNotEmpty(collection);
+ }
+
+ public static void ShouldNotBeEmpty(this string aString)
+ {
+ Assert.IsNotEmpty(aString);
+ }
+
+ public static void ShouldContain(this string actual, string expected)
+ {
+ StringAssert.Contains(expected, actual);
+ }
+
+ public static string ShouldBeEqualIgnoringCase(this string actual, string expected)
+ {
+ StringAssert.AreEqualIgnoringCase(expected, actual);
+ return expected;
+ }
+
+ public static void ShouldEndWith(this string actual, string expected)
+ {
+ StringAssert.EndsWith(expected, actual);
+ }
+
+ public static void ShouldStartWith(this string actual, string expected)
+ {
+ StringAssert.StartsWith(expected, actual);
+ }
+
+ public static void ShouldContainErrorMessage(this Exception exception, string expected)
+ {
+ StringAssert.Contains(expected, exception.Message);
+ }
+
+ public static Exception ShouldBeThrownBy(this Type exceptionType, MethodThatThrows method)
+ {
+ Exception exception = null;
+
+ try
+ {
+ method();
+ }
+ catch (Exception e)
+ {
+ Assert.AreEqual(exceptionType, e.GetType());
+ exception = e;
+ }
+
+ if (exception == null)
+ {
+ Assert.Fail(String.Format("Expected {0} to be thrown.", exceptionType.FullName));
+ }
+
+ return exception;
+ }
+
+ public static void ShouldEqualSqlDate(this DateTime actual, DateTime expected)
+ {
+ TimeSpan timeSpan = actual - expected;
+ Assert.Less(Math.Abs(timeSpan.TotalMilliseconds), 3);
+ }
+
+ public static object AttributeShouldEqual(this XmlElement element, string attributeName, object expected)
+ {
+ Assert.IsNotNull(element, "The Element is null");
+
+ string actual = element.GetAttribute(attributeName);
+ Assert.AreEqual(expected, actual);
+ return expected;
+ }
+
+ public static XmlElement ShouldHaveChild(this XmlElement element, string xpath)
+ {
+ XmlElement child = element.SelectSingleNode(xpath) as XmlElement;
+ Assert.IsNotNull(child, "Should have a child element matching " + xpath);
+
+ return child;
+ }
+
+ public static XmlElement DoesNotHaveAttribute(this XmlElement element, string attributeName)
+ {
+ Assert.IsNotNull(element, "The Element is null");
+ Assert.IsFalse(element.HasAttribute(attributeName), "Element should not have an attribute named " + attributeName);
+
+ return element;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.Testing/TestUtility.cs b/src/FluentNHibernate.Testing/TestUtility.cs
new file mode 100644
index 000000000..1fb0f7479
--- /dev/null
+++ b/src/FluentNHibernate.Testing/TestUtility.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Reflection;
+
+namespace ShadeTree.Testing
+{
+ public static class TestUtility
+ {
+ public static void FireEvent(object control, string eventName, params object[] args)
+ {
+ MethodInfo minfo =
+ control.GetType().GetMethod("On" + eventName,
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ ParameterInfo[] param = minfo.GetParameters();
+ Type parameterType = param[0].ParameterType;
+ minfo.Invoke(control, new[] {Activator.CreateInstance(parameterType)});
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate.sln b/src/FluentNHibernate.sln
new file mode 100644
index 000000000..71326062c
--- /dev/null
+++ b/src/FluentNHibernate.sln
@@ -0,0 +1,25 @@
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentNHibernate", "FluentNHibernate\FluentNHibernate.csproj", "{1C988DFB-1EC5-484E-87D9-1D3C775BA435}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentNHibernate.Testing", "FluentNHibernate.Testing\FluentNHibernate.Testing.csproj", "{F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1C988DFB-1EC5-484E-87D9-1D3C775BA435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C988DFB-1EC5-484E-87D9-1D3C775BA435}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C988DFB-1EC5-484E-87D9-1D3C775BA435}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C988DFB-1EC5-484E-87D9-1D3C775BA435}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F5DC3221-827E-4CB4-B61C-5F50EB4D32EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/FluentNHibernate/AssemblyInfo.cs b/src/FluentNHibernate/AssemblyInfo.cs
new file mode 100644
index 000000000..bc6ead63b
--- /dev/null
+++ b/src/FluentNHibernate/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+using System.Reflection;
+//
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+//
+
+[assembly : AssemblyTitle("StoryTeller.DomainModel")]
+[assembly : AssemblyDescription("")]
\ No newline at end of file
diff --git a/src/FluentNHibernate/Conventions.cs b/src/FluentNHibernate/Conventions.cs
new file mode 100644
index 000000000..240752e8f
--- /dev/null
+++ b/src/FluentNHibernate/Conventions.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using ShadeTree.Core.Validation;
+using ShadeTree.DomainModel.Mapping;
+using ShadeTree.Validation;
+
+namespace ShadeTree.DomainModel
+{
+ public interface IPropertyConvention
+ {
+ void Process(IProperty property);
+ }
+
+ public class AttributeConvention : IPropertyConvention where T : Attribute
+ {
+ private Action _action;
+
+ public AttributeConvention(Action action)
+ {
+ _action = action;
+ }
+
+ public void Process(IProperty property)
+ {
+ T att = Attribute.GetCustomAttribute(property.Property, typeof (T), true) as T;
+ if (att != null)
+ {
+ _action(att, property);
+ }
+ }
+ }
+
+ public class Conventions
+ {
+ private readonly List _typeConventions = new List();
+ private List _propertyConventions = new List();
+
+ public int DefaultStringLength { get; set; }
+
+ public Conventions()
+ {
+ DefaultStringLength = 100;
+
+ AddTypeConvention(new IgnoreNullableTypeConvention());
+ AddTypeConvention(new EnumerationTypeConvention());
+
+ ForAttribute((att, prop) =>
+ {
+ if (prop.ParentIsRequired)
+ {
+ prop.SetAttributeOnPropertyElement("not-null", "true");
+ }
+ });
+
+ ForAttribute((att, prop) => prop.SetAttributeOnPropertyElement("length", att.Length.ToString()));
+ ForAttribute((att, prop) => prop.SetAttributeOnColumnElement("unique", "true"));
+ }
+
+ public Func GetForeignKeyName = prop => prop.Name + "_id";
+ public Func GetForeignKeyNameOfParent = type => type.Name + "_id";
+
+ public Func GetManyToManyTableName =
+ (child, parent) => child.Name + "To" + parent.Name;
+
+ public void AddTypeConvention(ITypeConvention convention)
+ {
+ _typeConventions.Add(convention);
+ }
+
+ public ITypeConvention FindConvention(Type propertyType)
+ {
+ var find = _typeConventions.Find(c => c.CanHandle(propertyType));
+ return find ?? new DefaultConvention();
+ }
+
+ public void AlterMap(IProperty property)
+ {
+ if (property.PropertyType == typeof(string))
+ {
+ property.SetAttributeOnPropertyElement("length", DefaultStringLength.ToString());
+ }
+
+ ITypeConvention convention = FindConvention(property.PropertyType);
+ convention.AlterMap(property);
+
+ _propertyConventions.ForEach(c => c.Process(property));
+ }
+
+ public void ForAttribute(Action action) where T : Attribute
+ {
+ AttributeConvention convention = new AttributeConvention(action);
+ _propertyConventions.Add(convention);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate/Entity.cs b/src/FluentNHibernate/Entity.cs
new file mode 100644
index 000000000..a82f696ff
--- /dev/null
+++ b/src/FluentNHibernate/Entity.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace ShadeTree.DomainModel
+{
+ [Serializable]
+ public class Entity : IEquatable
+ {
+ public long Id { get; set; }
+
+ public virtual bool Equals(Entity obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (GetType() != obj.GetType()) return false;
+ return obj.Id == Id;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (GetType() != obj.GetType()) return false;
+ return Equals((Entity) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ public static bool operator ==(Entity left, Entity right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(Entity left, Entity right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
diff --git a/src/FluentNHibernate/Fixtures/DomainClassFixture.cs b/src/FluentNHibernate/Fixtures/DomainClassFixture.cs
new file mode 100644
index 000000000..97bace5bf
--- /dev/null
+++ b/src/FluentNHibernate/Fixtures/DomainClassFixture.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using fit;
+using fitlibrary;
+using ShadeTree.Validation;
+using StoryTeller.FixtureModel;
+using ObjectFactory=StructureMap.ObjectFactory;
+
+namespace ShadeTree.DomainModel.Fixtures
+{
+ public class GenericRowFixture : RowFixture where T : class
+ {
+ private T[] _array;
+
+ public GenericRowFixture(T[] array)
+ {
+ _array = array;
+ }
+
+ public override object[] Query()
+ {
+ return _array;
+ }
+
+ public override Type GetTargetClass()
+ {
+ return typeof(T);
+ }
+ }
+
+ public class DomainClassFixture : DoFixture where T : Entity, new()
+ {
+ private T _subject;
+ private Action _onFinished = t => { };
+
+
+
+ public DomainClassFixture()
+ {
+ _subject = new T();
+ }
+
+ public DomainClassFixture(T subject)
+ {
+ _subject = subject;
+ }
+
+ [Example("|Find|[id]|")]
+ public void Find(long id)
+ {
+ IRepository repository = ObjectFactory.GetInstance();
+ _subject = repository.Find(id);
+
+ if (_subject == null)
+ {
+ string message = string.Format("Could not find {0}.Id = {1}", typeof (T).Name, id);
+ throw new ApplicationException(message);
+ }
+ }
+
+ [Example("|Find by alias|[alias]|")]
+ public void FindByAlias(string alias)
+ {
+ long id = TestContext.GetValue(alias);
+ Find(id);
+ }
+
+ [Example("|Save as alias|[alias]|")]
+ public void SaveAsAlias(string alias)
+ {
+ IRepository repository = ObjectFactory.GetInstance();
+ repository.Save(_subject);
+
+ TestContext.StoreAlias(alias, _subject.Id);
+ }
+
+
+ public Action OnFinished
+ {
+ get { return _onFinished; }
+ set { _onFinished = value; }
+ }
+
+ public override void DoRows(Parse theRows)
+ {
+ base.DoRows(theRows);
+ _onFinished(Subject);
+ }
+
+ public T Subject
+ {
+ get { return _subject; }
+ }
+
+
+ [Example("|There are no validation messages for|[fieldName]|")]
+ public bool ThereAreNoValidationMessagesFor(string fieldName)
+ {
+ return Validator.ValidateField(Subject, fieldName).Length == 0;
+ }
+
+ [Example(@"|The Validation Messages For|[fieldName]|are|
+|Message|
+|[message]|")]
+ public Fixture TheValidationMessagesForAre(string fieldName)
+ {
+ NotificationMessage[] messages = Validator.ValidateField(Subject, fieldName);
+ return new GenericRowFixture(messages);
+ }
+
+ [Example("|The Validation Messages For|[fieldName]|contains|[message]|")]
+ public bool TheValidationMessagesForContains(string fieldName, string message)
+ {
+ NotificationMessage[] messages = Validator.ValidateField(Subject, fieldName);
+ bool correct = Array.Find(messages, m => m.Message == message) != null;
+
+ if (!correct)
+ {
+ throwErrorMessagesAreWrong(messages);
+ }
+
+ return true;
+ }
+
+ private void throwErrorMessagesAreWrong(NotificationMessage[] messages)
+ {
+ string errorMessage = "\n\nWrong messages!";
+ foreach (NotificationMessage notificationMessage in messages)
+ {
+ errorMessage += "\n" + notificationMessage.ToString();
+ }
+
+ errorMessage += "\n\n\n";
+
+ throw new ApplicationException(errorMessage);
+ }
+
+ [Example("|The Validation Messages For|[fieldName]|does not contain|[message]|")]
+ public bool TheValidationMessagesForDoesNotContain(string fieldName, string message)
+ {
+ NotificationMessage[] messages = Validator.ValidateField(Subject, fieldName);
+ bool correct = Array.Find(messages, m => m.Message == message) == null;
+
+ if (!correct)
+ {
+ throwErrorMessagesAreWrong(messages);
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FluentNHibernate/Fixtures/DomainFixtureWatcher.cs b/src/FluentNHibernate/Fixtures/DomainFixtureWatcher.cs
new file mode 100644
index 000000000..271840ba7
--- /dev/null
+++ b/src/FluentNHibernate/Fixtures/DomainFixtureWatcher.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ShadeTree.DomainModel.Fixtures
+{
+ public static class DomainFixtureWatcher
+ {
+ private static List> list = new List>();
+ private static Dictionary> actionsByType = new Dictionary>();
+
+ public static Action LoadingList = t => { };
+
+ public static void RegisterListener(Action