Architecting LINQ To SQL part 9

Previously: Architecting LINQ To SQL part 8

LINQ To SQL with N-Tier: Why is there pain?

People tend to experience pain using LINQ in N-Tier scenarios because they are trying to pass entities between layers. You can find some examples of this complaint, not to pick on anyone but just to avoid repeating it, here and here.

First up is that you will have trouble serializing EntitySet and EntityRef. As you will recall from Part 5, we need to use these collection types to support lazy loading. The problem is that they will not serialize with the XmlSerializer or because they contain circular references. They will however serialize using WCF’s DataContractSerializer.

Second, remember that we said that you should always access entities within the scope of a DataContext in Part 7. The DataContext provides the unit of work and change tracking, so objects outside the scope of their context must be attached if we are to tell LINQ to SQL that they are persistent and not transient. You need to provide LINQ with information as to the original state of the persistent entity, so as to avoid spurious concurrency errors .When we access a lazy loaded collection the proxy for that collection uses the DataContext it was loaded with to retrieve the persistent child items. If the DataContext has been disposed you will get an exception.

DataContext itself is not serializable and contains non-remotable resources like a Db connection. This means you cannot gain change tracking and lazy loading by simply serializing your DataContext too. This tends to throw people who come from a DataSet background who have become used to serializing the context with the data. Indeed this ability to work in a disconnected fashion is trumpeted as a feature of the DataSet. As a result, this set of users tries to use LINQ to SQL in the same way and then becomes frustrated when they can’t.

It is important to recognize that ORMs represent a different architectural style to DataSets. Persistence Ignorance means that we want our domain model to be unaware of persistence related concerns, such as the relational model, Db connections, SQL statements, and object change tracking. As a result, these types of architecture don’t support this model of interaction. Those of us who believe in persistence ignorance think that this is a good thing, because clean separation of concerns makes our software more amenable to incremental re-architecture which keeps our software fit and healthy as it changes.

Prefer Messaging
So if LINQ to SQL follows this school of development what is the answer to working in a multi-tier environment? Simply it is to prefer messaging when communicating across tiers.
The usual alternatives to messaging are shared database integration and remote procedure calls.

The former is not usually raised as a mechanism within LINQ to SQL discussion but having been the recipient of legacy systems that use shared database integration I would advise strongly against it. Problems occur when multiple systems update one Db. To support multiple applications you need to express the Db schema in a generic way. Each application has to transform this schema when it loads its entities to match its own domain model. These model differences, systems tend to comprehend the entities in different ways, lead to subtle inconsistencies. Whenever you change how we interact with the Db for one application, we have to test all applications that share that Db, increasing the cost of making changes. It is not just schema changes that are at issue here, how we update the Db, the values we write, are just as important a concern. Just because both of my systems have a Customer does not mean that the semantics of dealing with a Customer are the same to both of them. For the order fulfilment and complaints departments a customer may be very different. Domain models are application specific  and not shareable.

Remote procedure calls are problematic because they exchange type. Exchanging type locks us into a relationship between client and server that becomes burdensome because when we change the server we have to change all the clients, or if we need a change for one client, we have to change the server and all other clients. This increases the total cost of ownership of the solution.

When we talk about messaging as a solution there are a number of important things to remember:

We should share schema not type. We want to send message not transfer an instance of a type. The sender may compose the message from one of more types and the receiver may consume the message into a type. But it’s a message not an entity.

Do not reference your domain model across the tiers. If you find yourself creating references to an assembly from one application in another, in an effort to send and receive entities, you have lost your way.

Exchange state, not behaviour. What we care about is passing a message that has some state, not the behaviours associated with an entity.

Assume you do not own the ‘other’ tier. Remember that when we talked about distribution one reason was to expose our services to a range of clients. If we make assumptions about the client, we limit our consumers.

Servers decide what to do with a message, clients decide what to do with replies, if there are any. With no transfer of behaviour each side decides what to do with the message, if anything.

Remember how we said the first law of distribution was don’t? Well one thing to think about is that if you balk at the idea of messages because ‘in the real world we need to get this code done in a reasonable timescale’ then your issue might be that you should not be opting for a multi-tier scenario anyway, because if you need n-tiers then you tend to be able to pay the price of messaging.

Insulate the Domain
When you use messages you want to insulate your domain from the message that goes over the wire. This is because you want to be able to change your domain but continue to support existing clients who use the current message format. Remember that you may not own your clients, so you cannot assume that they will change just because you change. So you need to separate your domain from the message.

In most cases you will tend to replace your RPC need to serialize the entity with a Document Message. To populate the document message it is tempting to serialize your entities directly over the wire as the message body. Instead, populate your messages from your entity.

The following trivial service represents this pattern, showing how we use ‘replay’ to load the entity affected by the message, update it, and then submit the changes:

    public class RegionService : IRegionService
    {

        public RegionDTO GetRegion(int regionId)
        {
            NorthwindDataContext session = new NorthwindDataContext();

            var query =
                from r in session.Regions
                where r.RegionID == regionId
                select new RegionMessage { Id = r.RegionID, Description = r.RegionDescription };

            return query.Single();
               
        }

        public void SendRegion(RegionMessage regionMessage)
        {
            NorthwindDataContext session = new NorthwindDataContext();

            Region region =
                    (from r in session.Regions
                    where r.RegionID == regionMessage.Id
                    select r).Single();

                region.RegionDescription = regionMessage.Description;

            session.SubmitChanges();            
        }

    }
 

About Ian Cooper

Ian Cooper has over 18 years of experience delivering Microsoft platform solutions in government, healthcare, and finance. During that time he has worked for the DTi, Reuters, Sungard, Misys and Beazley delivering everything from bespoke enterpise solutions to 'shrink-wrapped' products to thousands of customers. Ian is a passionate exponent of the benefits of OO and Agile. He is test-infected and contagious. When he is not writing C# code he is also the and founder of the London .NET user group. http://www.dnug.org.uk
This entry was posted in Architecture, LINQ. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://Bryan.ReynoldsLive.com Bryan Reynolds

    To add the Jon Kruger’s comments, basically there are situations where sharing type works and should be used. It depends on what you are building and why.

  • http://jonkruger.com/blog Jon Kruger

    The idea of not sharing types between the server and client is widely accepted as a good practice, but if you’re in a situation where you own both the server and client and you only have one client hitting a service, I think it’s overkill to _not_ share types. I’ve been on projects that did it both ways and it is much easier sharing types! On the other projects where we didn’t share types I felt like all I did was write plumbing code.

    Now if I ever expose a service to external callers (other than my client that I own), I definitely won’t share the types for all of the reasons that you’ve mentioned. But IMO not sharing types when you own both sides or not sharing types because someday you might want to change your database model without changing the object model is premature optimization.

  • Ian Cooper

    @Marco

    Great question. The specification pattern does not really exist across the wire, although you might implement your service using specifications. With a message, you are going to have to either query by example, choose fixed criteria, or parse a message body to figure out what criteria you need to build up.

  • Marco Wolff

    I love this series, it’s always a pleasure reading this.

    One more question. In your blog “Architecting LINQ to SQL applications, part 4 ” you wrote about specification pattern. One specification is always mapped to one Entity in the DataContext. But using the message pattern the client doesn’t know the entity.
    What is they best way create a query on DTO for the client?

  • http://ubik.com.au/blog Nick

    Excellent article, Ian. Now I have to go back and read the rest of the series! :)

  • http://www.donxml.com DonXML

    I couldn’t have said it any better!