MrKWatkins

DataContext Inheritance

LINQ to SQL isn't bad. Apart from a few annoying bugs with the designer (i.e. The designer.cs getting deleted and not regenerated when you update the model if you have a partial code file and it's insistence on adding in connection strings all over the shop being the two biggest) and some missing features (proper many-to-many relationships) it's pretty good. We use it at work and it performs well. One thing that can happen if you're not careful though is you end up with a massive .dbml files that contain all the tables in your database. This can be less than ideal, especially if your application has several areas. For example you might work for a company that makes cars and boats. (Yeah I admit that's quite unlikely but please endulge me...) Both sections of the company would need access to customer information but the car guys wouldn't need details of the boats and vice versa. Rather than have one big DataContext with all the tables in is there anyway we could structure things a bit better?

The short answer is yes, thanks to the 'Base Class' property in the .dbml designer. (Click on the .dbml somewhere and have a look in the Properties window) Make youself a .dbml for your core entities, then make .dbmls for each of your other areas and set the 'Base Class' property for these 'child' .dbmls the core DataContext. So you might have a CoreDataContext that contains Customer entities and then a CarDataContext that has Car entities and a BoatDataContext with Boat entities. Both the CarDataContext and BoatDataContext will have access to the Customer entities but not to each other's entities.

Of course just doing that isn't that much use. The benfit of O/R mappers like LINQ to SQL is the easy use of relationships between entities. So for our Car.dbml to be useful we would like our Car entity to have a reference to a Customer entity. But if we load Car.dbml into our designer we will not see any entities from the parent Core.dbml. So how do we create the relationship?

Manually is the answer. If we inspect the designer file for a .dbml we can copy the code for a similar one-to-many property and tweak it for our use:

public partial class Car
{
    private EntityRef<Customer> _Owner;

    [AssociationAttribute(Name = "Owner_Car", Storage = "_Owner", ThisKey = "OwnerId", OtherKey = "Id", IsForeignKey = true)]
    public Customer Owner
    {
        get
        {
            return _Owner.Entity;
        }
        set
        {
            Customer previousValue = _Owner.Entity;
            if (((previousValue != value) || !_Owner.HasLoadedOrAssignedValue == false))
            {
                SendPropertyChanging();
                if ((previousValue != null))
                {
                    _Owner.Entity = null;
                }
                _Owner.Entity = value;
                if ((value != null))
                {
                    _OwnerId = value.Id;
                }
                else
                {
                    _OwnerId = 0;
                }
                SendPropertyChanged("Owner");
            }
        }
    }
}

We can now have an Owner property on our Car entity that we can use like a normal LINQ to SQL property:

using (var carDataContext = new CarDataContext(Connection))
{
    // Grab our Customer.
    var customer = carDataContext.Customers.Where(c => c.Id == customerId).Single();

    // Create a Car.
    var car = new Car
    {
        Registration = "T1 KARR",
        Owner = customer
    };

    // Save it.
    carDataContext.Cars.InsertOnSubmit(car);
    carDataContext.SubmitChanges();
}

But what about the other way around? What if the boat department what to get a list of all the boats a customer owns? I.e. can we get a Customer.Boats property? The answer to that is not really... The approach I tried was to define a subclass of Customer with the property and then replace the table in the DataContext. I.e. something like:

public sealed class BoatCustomer : Customer
{
    private EntitySet<Boat> _Boats;

    public BoatCustomer()
    {
        _Boats = new EntitySet<Boat>(new Action<Boat>(Attach_Boats), new Action<Boat>(Detach_Boats));
    }

    [AssociationAttribute(Name = "Customer_Boat", Storage = "_Boats", ThisKey = "Id", OtherKey = "OwnerId")]
    public EntitySet<Boat> Boats
    {
        get
        {
            return _Boats;
        }
        set
        {
            _Boats.Assign(value);
        }
    }

    private void Attach_Boats(Boat entity)
    {
        SendPropertyChanging();
        entity.Owner = this;
    }

    private void Detach_Boats(Boat entity)
    {
        SendPropertyChanging();
        entity.Owner = null;
    }
}

public partial class BoatDataContext : CoreDataContext
{
    public new Table<BoatCustomer> Customers
    {
        get
        {
            return GetTable<BoatCustomer>();
        }
    }
}    

Sadly this, and other similar approaches, don't work... InvalidOperationExceptions with messages like "Data member 'Int32 Id' of type 'KWatkins.DataContextInheritance.Customer' is not part of the mapping for type 'BoatCustomer'. Is the member above the root of an inheritance hierarchy?" start getting thrown around the place. The problem is that the attribute mapping doesn't look into base classes for some members. (If you open the lid with Reflector you'll see gratuitous use of the BindingFlags.DeclaredOnly enum value) So what are our alternatives?

  1. Recreate the various properties of Customer entity in the BoatCustomer class. Not great; pretty soon we'd be back to large DataContexts with everything in. Kinda defeats the point of doing this whole DataContext inheritance thing in the first place!
  2. Alter the AttributeMappingSource class so that it looks in base classes. Not really possible; all AttributeMappingSource does is return the class AttributedMetaModel. AttributedMetaModel does all the work but we cannot change it because it is internal.
  3. Create a public version of AttributedMetaModel by using Reflector to get the original code then change it to look in base classes. Sadly not really an option either... There is a lot of code behind AttributedMetaModel that uses a lot of internal stuff from various areas of the framework. I started then gave up...
  4. Create a MetaModel that acts as a wrapper around another MetaModel. We might then be able to wrap it around an AttributedMetaModel instance and intecept the relevant calls and change them accordingly. I didn't even attempt this one; if someone wants to try please be my guest...

In the end I settled on an extension method on Customer that returns a query:

public static IQueryable<Boat> Boats(this Customer customer, BoatDataContext dataContext)
{
    Validate.Argument(customer, "customer").IsNotNull();
    Validate.Argument(dataContext, "dataContext").IsNotNull();

    return from boat in dataContext.Boats
           where
                boat.OwnerId == customer.Id
           select boat;
}

(See this post for details about the Validate class) This method returns a query that returns all the Boats for a Customer. Sadly it's not a proper EntitySet so we can't add and remove members from it. But I haven't found this to be a major problem; I usually use properties like this for querying rather than inserting/deleting so it hasn't bothered me much. It's a shame we have to pass in a BoatDataContext but LINQ to SQL entities don't have a reference to a DataContext so there isn't much we can do.

There you go. By use of inheritance you can help split up your .dbml files and not end up with one massive, unwieldy DataContext. Feel free to download some example source code that demonstrates all of this. Note that I used Visual Studio 2010 to write it though; I wanted to see if some of the LINQ to SQL bugs have been fixed. (The .designer.cs classes getting deleted I mentioned above seems to have been fixed) You'll therefore have to setup your own projects if you want a VS2008 version...