Posted by: Rob | December 1, 2010

Auditing with NHibernate – Event Listeners

I’ve been doing some work on an application which needed to implement auditing.
I wanted to make it as un-obtrusive as possible, basically being able to switch the auditing on and let the system record changes made to entities.

Used in conjunction with a generic respository, I’ve got something which is *almost* there. I’m fairly happy with what it’s doing and over the next few days I’m going to do a series of blog posts showing what I did and what I’m trying to do and I’d be grateful for any suggestions as I go.

I’ll start it from scratch and put the code in as I go.

If you’re reading this, it’s after 8th December and there’s still no content, drop a message here to give me a prod.

This had me puzzled for a while this afternoon so I thought I’d put this post up.

I was trying to get an integration test running, checking to see that an object was being persisted correctly (against an im-memory SQLite database).
The test was failing with “failed: System.ApplicationException” Expected List<> but got IList<>

The problem was with the type of check I was doing. I was doing a property check but I should have been doing a “List” check.

Changing :

              .CheckProperty(y => y.list, myList)

To :

             .CheckList(y => y.list, myList)

Got it working.

Still working through session 6 of the Summer of Nhibernate and got to the bit where Steve is creating a criteria expression.  He’s converting an IList<int> to an array of ints using a ToArray method.
This conversion method doesn’t appear to be standard for IList and as I’m in a bit of a hurry today I’ve assumed it’s an extension method and wrote my own (code below) so I can follow the screen cast:

public static class MyExtensions
{
  public static T[] ToArray<T>(this IList<T> ListToConvert)
  {
    T[] arrayToReturn = new T[ListToConvert.Count];
    for (int i = 0; i < arrayToReturn.Length; i++)  
      arrayToReturn[i] = ListToConvert[i];
    return arrayToReturn;
  }
}
Posted by: Rob | December 7, 2009

Nhibernate – c.Orders.elements is not mapped

Continuing to Work through Steve Bohlen’s Summer of Nhibernate series and encountered a problem with session 6′s HQL example of obtaining a collection of child objects from the database.

The HQL shown in the screen cast was :

"select distinct c from Customer c , c.Orders.elements o where o.OrderDate > :date"

Using this HQL gave me the following error :

c.Orders.elements is not mapped

The syntax for obtaining the elements in the child collection has changed, and the correct syntax to be used is :

elements(c.Orders)

This didn’t work for me either though (NHibernate 2.1.2.4000), I was getting the following error instead :

Exception of type 'Antlr.Runtime.MissingTokenException'

I wasn’t getting very far with this problem, I couldn’t find a good example of HQL doing the same thing and I’m a bit anal in that I didn’t want to proceed with the rest of the screen cast while I had this up in the air.  In reality I doubt I’d ever use HQL but I still wanted to sort the problem.    In the end I’ve used the following HQL with an explicit inner join and fetch for the orders collection :

"Select distinct c from Customer c inner join fetch c.Orders o
      where o.OrderDate > :date"

This works great but I’m wondering if a shorter HQL statement will do the same thing as Steve demo’ed in his screen cast so I’m going to post a query on the Nhibernate group.   I’ll follow this post up once I’ve got a better solution.

** Update – 14th December 2009 **

I posted the query in the NHibernate user group and have received a response.   The problem was I wasn’t using the keyword “as” when giving the order collection an alias.   The hql to use is below :

"select distinct c from Customer c , elements(c.Orders) as o where o.OrderDate > :date"

I’ve been working through the Summer of Nhibernate screen casts by Steve Bohlen. I have to take my hat off to Steve, these are brilliant, they are so informative covering many aspects of Nhibernate…and I’m only up to Session 3!

The last example on the Session 2a screen cast demonstrates grouping using HQL and mapping the output to a class which isn’t persisted in the database. Great, works like a treat though I did make the mistake of using CreateCriteria rather than CreateQuery with the HQL statement and went around in circles for half an hour, the exception message I’d received was “No persister for: select new….

Anyway, Steve ended the session there but hadn’t shown the equivalent method using the Criteria API. I checked out the blog for the sessions and although someone had posted a sort of solution, it wasn’t complete. A working solution is below (beware of my naming conventions if you’re looking to paste the code).

The first thing to do was to create a projection list for the fields you wanted to return giving them an alias,

FirstName will be given the alias “FirstName”

count(FirstName) will be given the alias “Count”  (use Projections.Count to get the count function in SQL)

You also need a projection to group on FirstName using Projections.GroupProperty, supplying the name of the field you want to group on which is FirstName. You’ll get an informative error coming back from the database if you don’t put the group by in.

Finally you need to tell Nhibernate how to map the results into your CustomerFirstNameCounter class. To do this you use SetResultTransformer and supply the target type.

Being an NHibernate novice I’d be grateful for any feedback on ways to improve the code.


return session.CreateCriteria(typeof (Customer))
  .SetProjection(Projections.ProjectionList()
      .Add(Projections.Property("FirstName"), "FirstName")
      .Add(Projections.Count("FirstName"), "Count")
      .Add(Projections.GroupProperty("FirstName")))
      .SetResultTransformer(Transformers
            .AliasToBean(typeof(CustomerFirstNameCounter)))
  .List<CustomerFirstNameCounter>();
Posted by: Rob | August 27, 2009

_ReSharper folder in .net Projects

It can be slightly irritating having a ReSharper cache folder in the solution folder of your application.

It’s quite easily moved though.  There’s a config option in ReSharper which will move it to the system temp folder.

From the menu -
ReSharper > Options
Select General
At the very bottom of the form there’s an option “Store caches in”  select the option “system TEMP folder”.

ReSharper config setting
Posted by: Rob | June 25, 2011

Elmah – Not generatting XML Log File

On adding Elmah to my web app (logging to XML files) I noticed that error logging worked fine on my development machine but when deployed to a server nothing was being logged.

First thing I did was check that logging was working on the server by removing the errorLog setting in my web.config and trying it -

<elmah>
<security allowRemoteAccess=”yes” />
<errorLog type=”Elmah.XmlFileErrorLog, Elmah” logPath=”~/ErrorLogs” />
</elmah>

Removed the line and logging (in memory) was working. OK so the problem has to be with creating the log file itself. Digging around provided some clues and suggestions about folder access.
In the end I needed to give ‘Modify’ permission on the ‘ErrorLogs’ folder on the web server to the user account my site was running as. This could be the IIS user account. For me it was the account my App Pool was running as I had my site assigned to an Application Pool.

Came across this error today. It had me confused for a few minutes but it was an easy one to resolve and highlights a potential pitfall with copy/paste of entity maps.
I’m mapping data from an external database into my main app’s repository.

The external datasource has a composite key of 3 fields and these fields have names with suffixes of ‘id’ but they’re strings not ints.

My mapping file had something like

CompositeId()
.KeyReference(x => x.Mod_id)
.KeyReference(x => x.Year_id)
.KeyReference(x => x.Prog_id);

‘KeyReference’ was the issue. As the fields / properties were strings I had to change them to ‘KeyProperty’.

CompositeId()
.KeyProperty(x => x.Mod_id)
.KeyProperty(x => x.Year_id)
.KeyProperty(x => x.Prog_id);

I was using NHibernate when mapping some objects from a legacy system to objects in a new system and had an issue with one of the records failing to save.

The error message was “String or binary data would be truncated. The statement has been terminated.” but when I looked at the SQL being executed against the server the parameters were all question marks – VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?).

The error message was exactly right, the problem was caused by the truncation of data. When I was mapping from my source object to my target object I was inserting a text string which was too long for the property and when NHibernate was saving, the data was being truncated. The question marks in the SQL output threw me though.

I came across a problem with a Schema Fluent Nhibernate was generating for me yesterday.  I was getting the error message Repeated column in mapping for collection”.

I had an object, let’s call it “Item” which referenced instances of the same type, let’s call them “AssociatedItems”.
Thinking of a simple real-life example, when you do an oil change on your car you should change the oil filter and the sump-plug washer too.  So an oil filter Item would have associated items of oil and sump-plug washer.  Sump-plug washer would have associated items of oil and oil filter etc.

So the object looked something like this :

public class Item
{
    public virtual int Id {get; set;}
    public virtual string Code {get; set;}
    public virtual string Description {get; set;}
    public virtual string IList<Item> AssociatedItems {get; set;}
}

Looks pretty straighforward.  Now to the Mapping :

Public ItemMap()
{   
    Id(x => x.Id);
    Map(x => x.Code);
    Map(x => x.Description);
    HasManyToMany(x => x.AssociatedItems);
}

OK, really simple.

Generating the schema, a table to handle the many-to-many relationships between Items and AssociatedItems is created.  It’s called something wierd like AssociatedItemsToAssociatedItems though and it only has one field / column called “Item_Id”.  I’m also getting an error message “repeated column in mapping for collection – Item_Id” – What’s going on?     The database generation seems to be getting confused because of the self reference / relationship and is trying to create a table with field name “Item_Id” for the parent object and “Item_Id” for the child object.

Getting around the problem was quite straightforward though and only involved a change to the mapping class.
If you provide NHibernate with specific PK / FK names for the relationships it will generate a table with two fields of the specificed names, a composite key for the relationships.   Great!

HasManyToMany(x => x.AssociatedItems)
     .ParentKeyColumn("Item_Id")   
     .ChildKeyColumn("AssociatedItem_Id")

It still gives you a table with a crappy name though – bollocks!   What you need to do now though is tell NHibernate what it should call the table holding the relationship, explicitly define the name of the relationship table :

    .Table("AssociatedItemToItem");

So, the new, working map definition is :

Public ItemMap()
{
   Id(x => x.Id);
   Map(x => x.Code);
   Map(x => x.Description);
   HasManyToMany(x => x.AssociatedItems)
        .ParentKeyColumn("Item_Id")
        .ChildKeyColumn("AssociatedItem_Id")
        .Table("AssociatedItemToItem");
}

I mentioned in a previous blog post that I was looking at jQuery Data Annotations.   This is really smart and provides more client-side attribute support out of the box than comes with MVC2.  It’s extendable so you can create your own attributes with corresponding validation.

I found a slight glitch using JDA though.   I had encountered a problem where the meta data was applying the MVC required attribute for non-null types.  There’s a solution for this, see my other post.

I set this flag and sure enough the required attribute was dropped.  I did have another problem though, the MVC Numeric validator was automatically being applied to numeric types.   The ignore flag didn’t seem to have changed this behaviour.

In the end I took the JDA source code and made a small change.  Now it basically ignores any of the MVC validators returned from the meta data if the ignore flag has been set.  Original source code is available from the JDA site.  Changes are shown below in brown, wrapped so you can see them.

Modified source code for jQueryValidationExtension.cs :

namespace System.Web.Mvc.Html
{
 using System;
 using System.ComponentModel; using System.Collections.Generic;
 using System.Data.Linq;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Linq.Expressions;
 using System.Text;
 using System.Web.Mvc.Resources;
 using System.Web.Routing;
 using jQuery.Validation.DataAnnotations;

   public static class jQueryValidationExtensions
   {

     [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
     public static MvcHtmlString jQueryValidationFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
     {
         StringBuilder script = new StringBuilder();
         List<string> messages = new List<string>();
         List<string> validators = new List<string>();

         foreach (ModelValidator validator in ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).GetValidators(htmlHelper.ViewContext.Controller.ControllerContext))
         {
             if (validator.ShouldBeIgnored())
                 break;
             IEnumerable<ModelClientValidationRule> rules = validator.GetClientValidationRules();
             foreach (var a in rules)
             {
                 validators.Add(a.ValidationParameters["jQueryValidationInit"].ToString());
                 messages.Add(string.Format("{0}: \"{1}\"", a.ValidationParameters["jQueryName"].ToString(), a.ErrorMessage));                        
             }
         }

         foreach(Attribute valAttr in ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).ContainerType.GetCustomAttributes(true))
         {
             if(valAttr.GetType().Equals(typeof(EqualToAttribute)))
             {
                 EqualToAttribute attr = (EqualToAttribute)valAttr;
                 ModelMetadata.FromLambdaExpression(model => model, htmlHelper.ViewData);

                 EqualToValidator validator = new EqualToValidator(ModelMetadata.FromLambdaExpression(model => model, htmlHelper.ViewData), htmlHelper.ViewContext.Controller.ControllerContext, attr);
                 foreach (var a in validator.GetClientValidationRules())
                 {
                     if (ExpressionHelper.GetExpressionText(expression) == attr.SourceProperty)
                     {
                         validators.Add(a.ValidationParameters["jQueryValidationInit"].ToString());
                          messages.Add(string.Format("{0}: \"{1}\"", a.ValidationParameters["jQueryName"].ToString(), a.ErrorMessage));                            
                     }
                 }
             }
         }

         script.AppendFormat("$(\"#{0}\").rules(\"add\", {{", ExpressionHelper.GetExpressionText(expression));
         for (int i = 0; i < validators.Count; i++)
         {
             script.AppendFormat("{0} ,", validators[i]);
         }

         script.Append("messages: {");
         for (int i = 0; i < messages.Count; i++)
         {
             script.Append(messages[i]);
             if(i != messages.Count - 1)
             {
                 script.Append(",");
             }
         }

         script.Append("}});");
         return MvcHtmlString.Create(script.ToString());
     }

     private static bool MvcValidationIsSwitchedOff()
     {
         return (!DataAnnotationsModelValidatorProvider.
                    AddImplicitRequiredAttributeForValueTypes);
     }

     public static bool ShouldBeIgnored(this ModelValidator validator)
     {
         return (MvcValidationIsSwitchedOff() &&
                 validator.IsAnMvcValidator());            
     }

     public static bool IsAnMvcValidator(this ModelValidator validator)
     {
         return validator.GetType().BaseType.FullName.
                  Equals(typeof (ModelValidator).FullName);
     }
  }
}

I’ve been playing around with MVC2 looking to use it on a new project and was glad to see the release candidate was out.   Obviously if you’re looking to roll something out in the future you’re more confident using a RTM than a RC or a beta.  Beware though,MVC RC has been superseded by MVC2 RC2 and now MVC2 RTM.   You’ll have to uninstall MVC2 RC / RC2 if you want to install the RTM.  If you’re using the VS2010 RC  be wary you don’t uninstall MVC2 “Visual Studio 2010 tools” when you’re uninstalling MVC2.  If you do, your new MVC2 projects probably won’t load.   If you do uninstall the 2010 tools you can re-install them by uninstalling the Wed Development part of VS2010 and then re-install it via add/remove program components (mentioned on Phil Haack’s blog).

I’d been looking at Data Annotations and model validation and was really pleased to see how easy it was to get client-side validation up and running but the lack of supported attributes led me to look at JQuery Data Annotations as suggested by a colleague.

Again, great, this seemed to provide additional support.  I did encounter a problem though.   DataAnnotations was implementing a Required attribute on non-null data types automatically!!!!

Fortunately you can switch this behaviour off on the model validator provider.  The way to do this is to set the AddImplicitRequiredAttributeForValueTypes property to false.  I found this information on Jeremy Skinner’s blog post.

OK…nearly there….apart from the fact that DataAnnotationsModelValidatorProvider didn’t have an AddImplicitRequiredAttributeForValueTypes propery.

Digging a bit more I found a post that suggested that there was more than one version of the MVC2 RC out there.   I managed to get hold of another copy of System.Web.MVC from a colleague who had installed VS2010 RC.  He’d also installed MVC2 RC but wasn’t sure in which order.   The main thing was that it was different from the one in the MVC2 RC (as linked above).  It then became apparent that there was a new RC out there.

So…if you can’t set the AddImplicitRequiredAttributeForValueTypes propery because it doesn’t exist, check your dll version (System.Web.Mvc.dll).   2.0.41211.0 (356kb) was the version I originally had which came in the MVC2 RC download but this didn’t have the property.  2.0.50129.0 (365kb) did have the property, but this was in MVC2 RC2.

Older Posts »

Categories

Follow

Get every new post delivered to your Inbox.