A lot has been written about how to set up (table per class) inheritance with LINQ to SQL. I use it a lot and I always use an enum as inheritance discriminator.
But now, I need to instantiate new instances of derived classes in code. The user is provided a ComboBox
with the available enum
values and an ‘Add’ button. When the ‘Add’ button is pressed, I need an instance of the type that ‘belongs’ to the selected enum
.
The obvious (and fastest) approach would be: the switch
statement.
public enum MyEnum
{
Rectangle = 1,
Triangle = 2,
}
public MyShape CreateInstance(MyEnum requiredShape)
{
switch (requiredShape)
{
case MyEnum.Rectangle:
return new MyRectangle();
case MyEnum.Triangle:
return new MyTriangle();
default:
throw new System.NotSupportedException(requiredShape.ToString());
}
}
The drawbacks of this approach are:
- For each set of classes with subclasses, you will need to write a
switch
statement. - Maintainability; when the inheritance specifications change, you also need to update this code.
It would be easier (less code, better maintainable) to have a method that will figure out the class to instantiate from the inheritance mappings of the LINQ entity. Using reflection on the InheritanceMappingAttributes
is how to determine the type that belongs to a certain enum
value. Indeed, this will be slower, but I will take that for granted.
Some LINQ entities might have been set up like this:
[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.MyShape")]
[global::System.Data.Linq.Mapping.InheritanceMappingAttribute
(Code="Rectangle", Type = typeof(MyRectangle), IsDefault = true)]
[global::System.Data.Linq.Mapping.InheritanceMappingAttribute
(Code="Triangle", Type = typeof(MyTriangle))]
public abstract partial class MyShape : INotifyPropertyChanging, INotifyPropertyChanged
{
[global::System.Data.Linq.Mapping.ColumnAttribute
(Storage="_Type", DbType="Int NOT NULL",
CanBeNull=false, IsDiscriminator=true)]
public MyEnum Type
{
get { ... }
set { ... }
}
...
}
public partial class MyRectangle : MyShape
{
...
}
public partial class MyTriangle : MyShape
{
...
}
Let’s create a factory(ish) class (it’s not a fully equipped object factory…) that does the reflection work and the instantiation of the appropriate class. It can be a simple static
class with a static
method that will do the job:
public static class LinqEntityFactory
{
public static TBaseClass CreateEntity(Enum inheritanceModifier)
where TBaseClass : class
{
...
}
}
Time to the implement the CreateEntity
method. Always start with checking the input values:
if (inheritanceModifier == null)
{
throw new System.ArgumentNullException("inheritanceModifier");
}
The type of the class to instantiate is specified in one of the attributes. If the attributes is not found, fall back to the attribute that is marked as default.
Type enumType = inheritanceModifier.GetType();
MemberInfo info = typeof(TBaseClass);
var attribs = info.GetCustomAttributes(typeof(InheritanceMappingAttribute), false).Cast();
var attr = attribs.Where(a =>
inheritanceModifier.Equals(Enum.Parse(enumType, a.Code.ToString())))
.SingleOrDefault();
if (attr == null)
{
attr = attribs.Where(a => a.IsDefault).SingleOrDefault();
}
If the provided class is really marked with InheritanceModifierAttributes
, then we now have the attribute. What is left is to create the instance.
But beware, the type may represent an abstract
class. Instantiating abstract
classes at runtime cause errors which can be hard to debug. Therefore, throw an exception in this case providing the value of the enum
.
Also an additional check is added for the case the method is called on an object that has no InheritanceModifierAttributes
at all.
if (attr == null || attr.Type.IsAbstract)
{
throw new System.NotSupportedException(inheritanceModifier.ToString());
}
else
{
return (TBaseClass)Activator.CreateInstance(attr.Type);
}
And this is how to use the factory:
MyEnum requiredShape = MyEnum.Triangle;
MyShape shape = LinqEntityFactory.CreateEntity<MyShape>(requiredShape);
That’s it!
Thanks for reading and I hope you will find this post useful.
CodeProject