Thursday, 12 July 2012

IList Covariance

In this post I am going to describe a way to achieve Covariance for an IList<> implementation.

Now, this is obviously not something that can be done in a Type safe way (otherwise it would already be available out of the box).

There are plenty of details on why this is the case. But it boils down to this:

You have a IList<T> such that you want to add to the list, but the T here is a superclass. How can the compiler know that the actual underlying Type is the one that you are adding? It can’t.

Now, what if you promise to be really good and only use it carefully? Well, in that case, there are plenty of solutions out there. One that caught my eye was this one from Stack Overflow. And I have taken the liberty to enhance this with a few methods that IList implements.

/// <summary>
/// Covariant Enumerable with Index and Count properties
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IIndexedEnumerable<out T> : IEnumerable<T>
{
/// <summary>
/// Gets the <see cref="T"/> at the specified index.
/// </summary>
T this[int index] { get; }

/// <summary>
/// Gets the count.
/// </summary>
int Count { get; }
}

public static class ListCovarianceExtensions
{
/// <summary>
/// Converts a list into a covariant enumerable with Index and Count properties.
/// </summary>
/// <typeparam name="T">The base type for the covariance</typeparam>
/// <param name="list">The list.</param>
/// <returns>A covariant enumerable with Index and Count properties.</returns>
public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> list)
{
return new CovariantList<T>(list);
}

/// <summary>
/// Adds the item to the specified indexed enumerable.
/// </summary>
/// <typeparam name="T">The covariant type that the enumerable holds.</typeparam>
/// <param name="list">The enumerable list.</param>
/// <param name="item">The item to add.</param>
/// <remarks>
/// Covariance clearly doesn't let you add items to a generic list, so we use a unsafe Add method. The type check is still performed, but this is not a type safe Add and should be used with caution.
/// </remarks>
/// <exception cref="ArgumentException">Thrown when the underlying list type doesn't match the item type.</exception>
/// <returns>the enumerable</returns>
public static IIndexedEnumerable<T> Add<T>(this IIndexedEnumerable<T> list, T item)
{
var unsafeList = ((IUnsafeList)list);

if (item.GetType() != unsafeList.ListItemType)
{
throw new ArgumentException("item", string.Format("The type of the item to add is {0}, but the underlying list has a type of {1}", item.GetType(), unsafeList.ListItemType));
}

unsafeList.List.Add(item);
return list;
}

/// <summary>
/// Adds the item to the specified indexed enumerable at the specified index.
/// </summary>
/// <typeparam name="T">The covariant type that the enumerable holds.</typeparam>
/// <param name="list">The enumerable list.</param>
/// <param name="item">The item to add.</param>
/// <param name="index">The index.</param>
/// <exception cref="ArgumentException">Thrown when the underlying list type doesn't match the item type.</exception>
/// <returns>the enumerable</returns>
public static IIndexedEnumerable<T> Insert<T>(this IIndexedEnumerable<T> list, T item, int index)
{
var unsafeList = ((IUnsafeList)list);

if (item.GetType() != unsafeList.ListItemType)
{
throw new ArgumentException("item", string.Format("The type of the item to add is {0}, but the underlying list has a type of {1}", item.GetType(), unsafeList.ListItemType));
}

unsafeList.List.Insert(index, item);
return list;
}

/// <summary>
/// Removes the item at the specified index.
/// </summary>
/// <typeparam name="T">the base type of the covariant list.</typeparam>
/// <param name="list">The list.</param>
/// <param name="index">The index of the item to remove.</param>
/// <returns>the enumerable</returns>
public static IIndexedEnumerable<T> RemoveAt<T>(this IIndexedEnumerable<T> list, int index)
{
((IUnsafeList)list).List.RemoveAt(index);
return list;
}

#region Helper Classes and Interfaces

/// <summary>
/// Gives access to the properties of the underlying list for a CovariantList
/// </summary>
private interface IUnsafeList
{
/// <summary>
/// Gets the list.
/// </summary>
IList List { get; }

/// <summary>
/// Gets the type of the list item.
/// </summary>
/// <value>
/// The type of the list item.
/// </value>
Type ListItemType { get; }
}

/// <summary>
/// Wraps a generic list so that it can be used covariantely while still giving access to much used properties
/// </summary>
/// <typeparam name="T">The type of the underlying list.</typeparam>
private class CovariantList<T> : IIndexedEnumerable<T>, IUnsafeList
{
#region Fields

private readonly IList<T> _list;

#endregion


#region Constructors

/// <summary>
/// Initializes a new instance of the <see cref="CovariantList&lt;T&gt;"/> class.
/// </summary>
/// <param name="list">The underlying list.</param>
public CovariantList(IList<T> list)
{
// It doesn't matter if the list is null, we still want the methods to work
_list = list ?? new List<T>();
}

#endregion


#region Implementation of IEnumerable

/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>1</filterpriority>
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}

/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

#endregion


#region Implementation of IIndexedEnumerable<out T>

/// <summary>
/// Gets the <see cref="T"/> at the specified index.
/// </summary>
public T this[int index]
{
get { return _list[index]; }
}

/// <summary>
/// Gets the count.
/// </summary>
public int Count
{
get { return _list.Count; }
}

#endregion


#region Implementation of IUnsafeList

/// <summary>
/// Gets the list.
/// </summary>
public IList List
{
get { return (IList)_list; }
}

/// <summary>
/// Gets the type of the list item.
/// </summary>
/// <value>
/// The type of the list item.
/// </value>
public Type ListItemType
{
get { return typeof(T); }
}

#endregion
}

#endregion
}


And here are the tests:



[TestFixture]
public class IIndexedEnumerableTests : AbstractTest
{
#region Test Classes

public abstract class Super
{
public virtual IEnumerable<IIndexedEnumerable<Super>> SuperClassLists
{
get
{
return new List<IIndexedEnumerable<Super>>();
}
}
}

public class Sub1 : Super
{
}

public class Sub2 : Super
{
public List<Sub1> Sub1s { get; set; }

public override IEnumerable<IIndexedEnumerable<Super>> SuperClassLists
{
get
{
yield return Sub1s.AsCovariant();
}
}
}

#endregion

[Test]
public void Add()
{
var sub2 = new Sub2
{
Sub1s = new List<Sub1>
{
new Sub1(),
new Sub1(),
new Sub1(),
}
};

foreach (IIndexedEnumerable<Super> superClassList in sub2.SuperClassLists)
{
superClassList.Add(new Sub1());
}

Assert.AreEqual(4, sub2.Sub1s.Count);
}

[Test]
public void Add_TypeCheck()
{
var sub2 = new Sub2
{
Sub1s = new List<Sub1>
{
new Sub1(),
new Sub1(),
new Sub1(),
}
};

foreach (IIndexedEnumerable<Super> superClassList in sub2.SuperClassLists)
{
superClassList.Add(new Sub1());
var local = superClassList;
Assert.Throws<ArgumentException>(() => local.Add(new Sub2()));
}
}

[Test]
public void Index()
{
var sub2 = new Sub2
{
Sub1s = new List<Sub1>
{
new Sub1(),
new Sub1(),
new Sub1(),
}
};

foreach (IIndexedEnumerable<Super> superClassList in sub2.SuperClassLists)
{
Assert.AreSame(sub2.Sub1s[1], superClassList[1]);
Assert.AreNotSame(sub2.Sub1s[1], superClassList[0]);
}
}

[Test]
public void Count()
{
var sub2 = new Sub2
{
Sub1s = new List<Sub1>
{
new Sub1(),
new Sub1(),
new Sub1(),
}
};

foreach (IIndexedEnumerable<Super> superClassList in sub2.SuperClassLists)
{
Assert.AreEqual(3, superClassList.Count);
}
}

[Test]
public void NullList()
{
var sub2 = new Sub2();

foreach (var superClassList in sub2.SuperClassLists)
{
// Ensure that it doesn't throw a wobbly if the Ilist was null
// You might want this, but for our purposes, we didn't want this to cause an exception.
foreach (IIndexedEnumerable<Super> superClass in superClassList)
{

}
}
}

[Test]
[Ignore("This was a spike to check that adding the type check in wasn't going to hinder performance. It didn't")]
public void TypeCheckPerformance()
{
var sub2 = new Sub2
{
Sub1s = new List<Sub1>()
};

var start = DateTime.Now;
foreach (var superClassList in sub2.SuperClassLists)
{
for (var i = 0; i < 100000; i++)
{
superClassList.Add(new Sub1());
}
}
var timeTaken = DateTime.Now - start;
Console.WriteLine("Time Taken: {0}ms", timeTaken.TotalMilliseconds);
}
}

Submit this story to DotNetKicks Shout it

Wednesday, 8 February 2012

Fluent NHibernate PK Rename convention (SQL Server)

Following on from my previous post of using some sql to enable the SchemaExport tool to make changes outside of its current capabilities, I created a simple convention for Fluent NHibernate which enables you to name the PK’s of tables.
Currently this only works with SQL Server, but I believe that this approach should also be possible in other databases.
using System.Linq;
using System.Text;
using Iesi.Collections.Generic;
using NHibernate.Dialect;
using NHibernate.Mapping;

namespace Conventions
{
    public class CustomPrimaryKeyIndexNameConvention
    {
        public static void CreateUpdatePkNameScript(NHibernate.Cfg.Configuration config)
        {
            var script = new StringBuilder("");
            script.AppendLine("DECLARE @keyName AS SYSNAME");
            script.AppendLine();
            foreach (var tableName in config.ClassMappings.Select(m => m.Table.Name).Distinct())
            {
                script.AppendFormat(string.Format("SELECT  @keyName = name FROM sys.key_constraints kc WHERE kc.parent_object_id = OBJECT_ID('dbo.{0}', 'U')", tableName));
                script.AppendLine();
                script.AppendFormat("EXEC sp_rename @keyName, N'PK_{0}', N'OBJECT';", tableName);
                script.AppendLine();
            }

            config.AddAuxiliaryDatabaseObject(new SimpleAuxiliaryDatabaseObject(script.ToString(), null, new HashedSet<string> { typeof(MsSql2000Dialect).FullName, typeof(MsSql2005Dialect).FullName, typeof(MsSql2008Dialect).FullName }));
        }
    }
}
As you can see, the convention is to use “PK_<TableName>” for the PK name and is applied using the Auxiliary database extensions:
.ExposeConfiguration(CustomPrimaryKeyIndexNameConvention.CreateUpdatePkNameScript)

Submit this story to DotNetKicks Shout it

Monday, 6 February 2012

Fluent NHibernate Solution to enable SchemaExport to create HiLo columns

I spent a few hours recently trying to figure out why Fluent NHibernate (FNH) wasn’t doing what I thought it should be doing when using the HiLo generator conventions and then using the SchemaExport tool to generate my schema. After eventually looking at the hbm files that FNH was producing for a possible error and to make sure that I was using FNH correctly, I realised that FNH wasn't the problem, and that the problem lies within SchemaExport.
Fabio Maulo puts this down to design and gives a solution for using ConfORM. I agree with the comments made and find his answer very unsatisfactory and that there is a bug and that it should be fixed.
Whilst I am sure that that debate will roll on, I have created a solution that matches Fabio’s one for ConfORM but for FNH shown below:
using System.Linq;
using System.Text;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using Iesi.Collections.Generic;
using NHibernate.Dialect;
using NHibernate.Mapping;

namespace Conventions
{
    public class CustomIdentityHiLoGeneratorConvention :IIdConvention
    {
        public const string NextHiValueColumnName= "NextHiValue";
        public const string NHibernateHiLoIdentityTableName = "NHibernateHiLoIdentity";
        public const string TableColumnName = "Entity";

        #region Implementation of IConvention<IIdentityInspector,IIdentityInstance>

        public void Apply(IIdentityInstance instance)
        {
            instance.GeneratedBy.HiLo(NHibernateHiLoIdentityTableName, NextHiValueColumnName, "500", builder => builder.AddParam("where", string.Format("{0} = '[{1}]'", TableColumnName, instance.EntityType.Name)));
        }

        #endregion
        
        public static void CreateHighLowScript(NHibernate.Cfg.Configuration config)
        {
            var script = new StringBuilder();
            script.AppendFormat("DELETE FROM {0};", NHibernateHiLoIdentityTableName);
            script.AppendLine();
            script.AppendFormat("ALTER TABLE {0} ADD {1} VARCHAR(128) NOT NULL;", NHibernateHiLoIdentityTableName, TableColumnName);
            script.AppendLine();
            script.AppendFormat("CREATE NONCLUSTERED INDEX IX_{0}_{1} ON {0} (Entity ASC);", NHibernateHiLoIdentityTableName, TableColumnName);
            script.AppendLine();
            script.AppendLine("GO");
            script.AppendLine();
            foreach (var tableName in config.ClassMappings.Select(m => m.Table.Name).Distinct())
            {
                script.AppendFormat(string.Format("INSERT INTO [{0}] ({1}, {2}) VALUES ('[{3}]',1);", NHibernateHiLoIdentityTableName, TableColumnName, NextHiValueColumnName, tableName));
                script.AppendLine();
            }
            
            config.AddAuxiliaryDatabaseObject(new SimpleAuxiliaryDatabaseObject(script.ToString(), null, new HashedSet<string> { typeof(MsSql2000Dialect).FullName, typeof(MsSql2005Dialect).FullName, typeof(MsSql2008Dialect).FullName }));
        }
    }
}
This is then used as follows:
conventions.Add<CustomIdentityHiLoGeneratorConvention>();
and:
.ExposeConfiguration(CustomIdentityHiLoGeneratorConvention.CreateHighLowScript)

Submit this story to DotNetKicks Shout it

Sunday, 1 May 2011

Fluent NHibernate - Ignore Abstract Properties

I was struggling the other day whilst trying to have any property that started with the word "Display" be ignored by Fluent NHiberantes Automap feature. I had it working, but as soon as I added a abstract property to the base class, it all stopped working. I was using the older way of ignoring properties:
using System.Reflection;
using FluentNHibernate;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;

namespace FNHProblem
{
class Program
{
static void Main(string[] args)
{
Fluently .Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration .MsSql2008.ConnectionString("Server=(local);Integrated Security=SSPI;" ))
.Mappings(mappings => mappings.AutoMappings.Add(AutoMap .Assemblies(Assembly .GetAssembly(typeof (Program )))
.OverrideAll(p => p.IgnoreProperties(ShouldIgnoreMember)))
.ExportTo("." ))
.BuildConfiguration()
.BuildSessionFactory();
}

public static bool ShouldIgnoreMember(Member member)
{
return member.Name.StartsWith("Display");
}
}

public abstract class BaseEntity
{
public virtual int Id { get; set; }

public abstract string DisplayString { get; }
}

public class EntityA : BaseEntity
{
#region Overrides of BaseEntity

public override string DisplayString
{
get { return "I am EntityA"; }
}

#endregion

public virtual string DisplaySomethingElse { get; set; }
}
}

But then switching to using a newer API fixes my problem straight away, so here is the solution:
using  System.Reflection;
using FluentNHibernate;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;

namespace FNHProblem
{
class Program
{
static void Main(string [] args)
{
Fluently .Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration .MsSql2008.ConnectionString("Server=(local);Integrated Security=SSPI;" ))
.Mappings(mappings => mappings.AutoMappings.Add(AutoMap .Assembly(Assembly .GetAssembly(typeof (Program )), new CustomDefaultAutoMappingConfiguration ()))
.ExportTo("." ))
.BuildConfiguration()
.BuildSessionFactory();
}
}

class CustomDefaultAutoMappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Member member)
{
if (member.Name.StartsWith("Display" ))
{
return false ;
}
return base .ShouldMap(member);
}
}

public abstract class BaseEntity
{
public virtual int Id { get ; set ; }

public abstract string DisplayIShouldBeIgnored { get ; }
}

public class EntityA : BaseEntity
{
public override string DisplayIShouldBeIgnored
{
get { return "I am EntityA" ; }
}
}
}

Submit this story to DotNetKicks Shout it

Tuesday, 8 March 2011

ASP.NET MVC Getting a default route to point to an Area

This seems like such an obvious thing to want to do, but isn't clear from searching the web how this is done.
After many attempts, if found the solution, which is very easy:
 
routes.MapRoute("Defaults_Route" ,
"" ,
new { area = "MyArea" , controller = "Home" , action = "Index" }
).DataTokens.Add("area" , "MyArea" );

Submit this story to DotNetKicks Shout it