Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / SQL

How to Instantiate Derived LINQ to SQL Entities

0.00/5 (No votes)
8 Feb 2012CPOL2 min read 13.9K  
How to instantiate derived LINQ to SQL entities

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.

C#
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:

C#
[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:

C#
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:

C#
// Check the input.
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.

C#
Type enumType = inheritanceModifier.GetType();
// Get the inheritance attributes.
MemberInfo info = typeof(TBaseClass);
var attribs = info.GetCustomAttributes(typeof(InheritanceMappingAttribute), false).Cast();

// Find the inheritance attribute that specifies the requested object type.
// Instead of a string comparison, try to parse the Code value. The Code
// of the attribute is always provided as string, representing either
// the name of the Enum value or the (int) value of it.
var attr = attribs.Where(a =>
    inheritanceModifier.Equals(Enum.Parse(enumType, a.Code.ToString())))
    .SingleOrDefault();
// If the attribute was not found, find the inheritance default.
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.

C#
// The attribute contains the type of the object to instantiate.
// Verify whether the required type is not abstract. Abstract types
// can occur when the default inheritance attribute has been
// specified with an abstract class. In that case, raise an
// error because it can be hard to debug a runtime abstract class
// instantiation exception.
// By the way, there is always a default inheritance attribute,
// however since LINQ to SQL entities inherited from object (by default)
// this method can also be called on objects without any inheritance
// specifier. So always check for null.
if (attr == null || attr.Type.IsAbstract)
{
    throw new System.NotSupportedException(inheritanceModifier.ToString());
}
else
{
    // Create and return the required instance.
    return (TBaseClass)Activator.CreateInstance(attr.Type);
}

And this is how to use the factory:

C#
MyEnum requiredShape = MyEnum.Triangle;
MyShape shape = LinqEntityFactory.CreateEntity<MyShape>(requiredShape);
// shape is now an instance of MyTriangle.

That’s it!

Thanks for reading and I hope you will find this post useful.


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)