This is just a small preview of how to implement Noma checks. The classes you'll see below work, but i still have to write the engine which runs these checks and feeds them the correct data. But i'm pretty happy with how the implementation of checks looks at the moment, so i wanted to post something about it.
First of all, there's the ICheck interface which looks like this:
public interface ICheck {}
pretty meaningless huh? I'm just using this as a base interface type to identify all different kinds of Noma checks. For instance, here's the interface that you'll need to implement if you want to perform a check on a ClassMapping:
public interface ICheckClassMappings : ICheck
{
ICollection<ClassMapping> Select(ICollection<ClassMapping> classMappings);
void Check(ClassMapping classToCheck, MetaDataStore store);
}
The Select method is passed all known ClassMappings, and you'll need to return the ones that your check is interested in. Each one of the selected ClassMappings will then be passed to the Check method along with the entire MetaDataStore which you'll use to look up all the data you need to perform your check.
If you want to write a check for something that belongs to a ClassMapping, you'll need to implement the generic ICheck interface:
public interface ICheck<T> : ICheck
{
ICollection<T> Select(ICollection<T> mappings, ClassMapping parentClassMapping);
void Check(T mappingToCheck, ClassMapping parentClassmapping, MetaDataStore store);
}
The type parameter will be the kind of mapping you want to check, like a PropertyMapping for example. The Select method will then receive a collection of all these mappings per ClassMapping, along with a reference to the ClassMapping the mappings belong to. You'll then need to return a collection of all the mappings you want to perform the check on. Each of these will then be passed to the Check method, along with the reference of the ClassMapping it belongs to, and a reference to the MetaDataStore.
To make it more clear, i'll show you two examples. First of all, there is the TableExists check:
using System.Collections.Generic;
using System.Linq;
using Noma.NHibernate.Mappings;
using NUnit.Framework;
namespace Noma.Runner.Checks.MappingToDatabase.Class
{
public class TableExists : ICheckClassMappings
{
public ICollection<ClassMapping> Select(ICollection<ClassMapping> classMappings)
{
return classMappings.Where(c => !string.IsNullOrEmpty(c.Table)).ToList();
}
public void Check(ClassMapping classToCheck, MetaDataStore store)
{
Assert.IsNotNull(store.GetTable(classToCheck.Schema, classToCheck.Table),
"Table [{0}].[{1}] was not found", classToCheck.Schema, classToCheck.Name);
}
}
}
Pretty simple right? It selects each ClassMapping that has a Table defined, and then in the Check method we use the familiar NUnit Assert class to verify that the referenced table actually exists in our MetaDataStore.
And this is how the ColumnExists check looks like:
using System.Collections.Generic;
using Noma.Database;
using Noma.NHibernate.Mappings;
using Noma.Runner.Checks.MappingToDatabase.Class;
using NUnit.Framework;
namespace Noma.Runner.Checks.MappingToDatabase.Property
{
[RequiresMappingToPassCheck(typeof(TableExists))]
public class ColumnExists : ICheck<PropertyMapping>
{
public ICollection<PropertyMapping> Select(ICollection<PropertyMapping> mappings, ClassMapping parentClassMapping)
{
// we need to check all property mappings
return mappings;
}
public void Check(PropertyMapping mappingToCheck, ClassMapping parentClassmapping, MetaDataStore store)
{
string columnName =
!string.IsNullOrEmpty(mappingToCheck.ColumnName) ? mappingToCheck.ColumnName : mappingToCheck.Name;
Table table = store.GetTable(parentClassmapping.Schema, parentClassmapping.Table);
Assert.IsNotNull(table.GetColumn(columnName),
"Class [{0}] refers to column [{1}] in table [{2}].[{3}] but the column was not found",
parentClassmapping.Name, columnName, parentClassmapping.Schema, parentClassmapping.Table);
}
}
}
Notice the usage of the [RequiresMappingToPassCheck]. You can use this to declare dependencies between checks... if one of the checks referenced with the [RequiresMappingToPassCheck] attribute already failed for a particular mapping or its parent ClassMapping, the check will not be executed for that specific mapping. In this case, we depend upon a check that only checks ClassMappings. So the ColumnExists check will not be executed for each ClassMapping that already failed the TableExists check.
In the Select method, you can see we simply return all the PropertyMappings we've received since we need to check them all. And in the Check method, we look up the table the ClassMapping refers to, and verify that the referenced column is actually there.
I've only just come up with this approach... The checks themselves work already, but as i mentioned earlier, i still need to write the engine to execute these checks, feed them the correct data, and resolve the dependencies between the checks. That should be some pretty interesting code ![]()
Obviously, you will be able to plug in your own checks which can also have dependencies between them.