Try fast search NHibernate

15 January 2010

NHibernate.Validator: Changing validation from/by aggregate

This post is more a self-remainder but it can be useful for you if your are using NHV to validate even Business-Rules.

NHV was designed more thinking about properties-constraints than Business-Rules, by the way it was evolving and it can be used even for some business-rules. In the last 6 or 5 months I have received questions about how solve some specific situations and, each time, I’m giving a different solution using a different NHV’s feature. The last time I even proposed the implementation of a new and unnecessary feature (take a look at this thread)… at least as self-reminder a post is needed.

The case

public class Customer
{
public string CompanyName { get; set; }
public string VatIdentificationNumber { get; set; }
}

public class Invoice
{
public Customer Customer { get; set; }
public decimal Amount { get; set; }
}

Rules are:

  • The customer must have a not empty CompanyName.
  • The customer may have VatIdentificationNumber and if he has one it must be a valid Italian’s Patita-IVA.
  • The invoice must have a valid Customer.
  • When a Customer is used to emit an Invoice, the Customer must have a Italian’s Patita-IVA.

As you can see the Customer class has different constraints depending on the aggregate from where it is used/associated.

The test

public class BusinessRuleDemo
{
ValidatorEngine validatorEngine;

[TestFixtureSetUp]
public void CreateValidatorEngine()
{
var configure = new FluentConfiguration();
configure.Register(new[] { typeof(CustomerValidation), typeof(InvoiceValidation) })
.SetDefaultValidatorMode(ValidatorMode.UseExternal);
validatorEngine = new ValidatorEngine();

validatorEngine.Configure(configure);
}

[Test]
public void CustomerValidWhenHaveCompanyName()
{
(new Customer {CompanyName = "ACME"}).Satisfy(c => validatorEngine.IsValid(c));
}

[Test]
public void CustomerNotValidWhenHaveInvalidVatIdentificationNumber()
{
(new Customer { CompanyName = "ACME", VatIdentificationNumber = "123"}).Satisfy(c => !validatorEngine.IsValid(c));
}

[Test]
public void CustomerValidWhenHaveValidVatIdentificationNumber()
{
(new Customer {CompanyName = "ACME", VatIdentificationNumber = "01636120634"})
.Satisfy(c => validatorEngine.IsValid(c));
}

[Test]
public void InvoiceNotValidWhenDoesNotHaveCustomer()
{
(new Invoice()).Satisfy(invoice=> !validatorEngine.IsValid(invoice));
}

[Test]
public void InvoiceNotValidWhenHaveCustomerWithoutVatIdentificationNumber()
{
(new Invoice { Customer = new Customer { CompanyName = "ACME" } })
.Satisfy(invoice => !validatorEngine.IsValid(invoice));
}

[Test]
public void InvoiceNotValidWhenHaveCustomerWithInvalidVatIdentificationNumber()
{
(new Invoice { Customer = new Customer { CompanyName = "ACME", VatIdentificationNumber = "123" } })
.Satisfy(invoice => !validatorEngine.IsValid(invoice));
}

[Test]
public void InvoiceValidWhenHaveCustomerWithValidVatIdentificationNumber()
{
(new Invoice { Customer = new Customer { CompanyName = "ACME", VatIdentificationNumber = "01636120634" } })
.Satisfy(invoice => validatorEngine.IsValid(invoice));
}
}

The solution

I’m pushing you to use the new loquacious mapping of NHibernate.Validator because the class where you are defining all constraints become the class with responsibility of validation of an entity so, in this example, I will use our dear Loquacious.

public class CustomerValidation : ValidationDef<Customer>
{
public CustomerValidation()
{
// The customer must have a not empty CompanyName
Define(e => e.CompanyName).NotNullableAndNotEmpty();

// The customer may have VatIdentificationNumber and if he has one it must be a valid Italian’s Patita-IVA.
Define(e => e.VatIdentificationNumber).IsPartitaIva();
}
}

public class InvoiceValidation : ValidationDef<Invoice>
{
public InvoiceValidation()
{
ValidateInstance.By((invoice, validationContext) =>
{
bool isValid = true;

// When a Customer is used to emit an Invoice, the Customer must have a Italian’s Patita-IVA
if (invoice.Customer != null && string.IsNullOrEmpty(invoice.Customer.VatIdentificationNumber))
{
isValid = false;
validationContext.AddInvalid<Customer, string>("To be used in an invoice the Customer should have the VatNumber",
p => p.VatIdentificationNumber);
}

return isValid;
});

// The invoice must have a valid Customer
Define(e => e.Customer).IsValid().And.NotNullable();
}
}

As you can see I’m using the ValidateInstance but this time with a “special” overload where I can change the validation-context. A validation-context is created each time we are validating an object-graph (each time we are calling a validation-method of the ValidatorEngine) and we can use it to add invalid-values to the validation; in this case I’m using it to validate the existence of the VatIdentificationNumber.

That’s all.

11 January 2010

Map NHibernate using your API

I would say you the story of this stuff but perhaps is matter for another post if you are interested in it. In this post I will show you an example how create your API to map your domain without use XML.

The Domain

DomainDiagram

Complex enough ?

I’m going to use various types of properties, relations as many-to-one, many-to-many, one-to-one, entities and components, collections of components, collections of scalar-types, collections as set, map, bag, list, hierarchy strategy as table-per-class and table-per-class-hierarchy… enough complete… well… at least for a blog post.

The API

For this example the API is a hbm-xml-mimic:

IMapper map = new Mapper();
map.Assembly(typeof (Animal).Assembly);
map.NameSpace(typeof (Animal).Namespace);

map.Class<Animal, long>(animal => animal.Id, id => id.Generator = Generators.Native, rc =>
{
rc.Property(animal => animal.Description);
rc.Property(animal => animal.BodyWeight);
rc.ManyToOne(animal => animal.Mother);
rc.ManyToOne(animal => animal.Father);
rc.ManyToOne(animal => animal.Zoo);
rc.Property(animal => animal.SerialNumber);
rc.Set(animal => animal.Offspring, cm => cm.OrderBy(an => an.Father), rel => rel.OneToMany());
});

map.JoinedSubclass<Reptile>(jsc => { jsc.Property(reptile => reptile.BodyTemperature); });

map.JoinedSubclass<Lizard>(jsc => { });

map.JoinedSubclass<Mammal>(jsc =>
{
jsc.Property(mammal => mammal.Pregnant);
jsc.Property(mammal => mammal.Birthdate);
});

map.JoinedSubclass<DomesticAnimal>(jsc => { jsc.ManyToOne(domesticAnimal => domesticAnimal.Owner); });

map.JoinedSubclass<Cat>(jsc => { });

map.JoinedSubclass<Dog>(jsc => { });

map.JoinedSubclass<Human>(jsc =>
{
jsc.Component(human => human.Name, comp =>
{
comp.Property(name => name.First);
comp.Property(name => name.Initial);
comp.Property(name => name.Last);
});
jsc.Property(human => human.NickName);
jsc.Property(human => human.Height);
jsc.Property(human => human.IntValue);
jsc.Property(human => human.FloatValue);
jsc.Property(human => human.BigDecimalValue);
jsc.Property(human => human.BigIntegerValue);
jsc.Bag(human => human.Friends, cm => { }, rel => rel.ManyToMany());
jsc.Map(human => human.Family, cm => { }, rel => rel.ManyToMany());
jsc.Bag(human => human.Pets, cm => { cm.Inverse = true; }, rel => rel.OneToMany());
jsc.Set(human => human.NickNames, cm =>
{
cm.Lazy = CollectionLazy.NoLazy;
cm.Sort();
});
jsc.Map(human => human.Addresses, cm => { }, rel => rel.Component(comp =>
{
comp.Property(address => address.Street);
comp.Property(address => address.City);
comp.Property(address => address.PostalCode);
comp.Property(address => address.Country);
comp.ManyToOne(address => address.StateProvince);
}));
});

map.Class<User, long>(sp => sp.Id, spid => spid.Generator = Generators.Foreign<User, Human>(u => u.Human), rc =>
{
rc.Property(user => user.UserName);
rc.OneToOne(user => user.Human, rm => rm.Constrained());
rc.List(user => user.Permissions, cm => { });
});

map.Class<Zoo, long>(zoo => zoo.Id, id => id.Generator = Generators.Native, rc =>
{
rc.Discriminator();
rc.Property(zoo => zoo.Name);
rc.Property(zoo => zoo.Classification);
rc.Map(zoo => zoo.Mammals, cm => { }, rel => rel.OneToMany());
rc.Map(zoo => zoo.Animals, cm => { cm.Inverse = true; }, rel => rel.OneToMany());
rc.Component(zoo => zoo.Address, comp =>
{
comp.Property(address => address.Street);
comp.Property(address => address.City);
comp.Property(address => address.PostalCode);
comp.Property(address => address.Country);
comp.ManyToOne(address => address.StateProvince);
});
});

map.Subclass<PettingZoo>(sc => { });

map.Class<StateProvince, long>(sp => sp.Id, spid => spid.Generator = Generators.Native, rc =>
{
rc.Property(sp => sp.Name);
rc.Property(sp => sp.IsoCode);
});

Behind the API

As you can read in this old post, NHibernate is translating the XML in its metadata, each time an XML is added to the configuration; instead add an XML you can add directly the metadata. Even if everything is there, since long time ago, ready to be used, in NH3.0.0 (the trunk) I have terminated a very old pending task: all binders (from XML to metadata) now are working using the deserialized state of the XML. To be short, now you have a series of partial POCOs generated from the XSD (the xml-schema) and you can fill it instead write an XML. In the code of this post you can see how create these classes and how use it to configure NH without use XML and without generate XML (high speed mapping).

The entry point

The entry point, in the Configuration class is this method:

/// <summary>
///
Add mapping data using deserialized class.
/// </summary>
/// <param name="mappingDocument">
Mapping metadata.</param>
/// <param name="documentFileName">
XML file's name where available; otherwise null.</param>
public void AddDeserializedMapping(HbmMapping mappingDocument, string documentFileName)

The Code

If you want play with the code you can download, through SVN, it from here.

If you want see the integration test this is the link.

If you have some questions… well… I’m here.

Happy coding!!!