Search
Close this search box.

LINQ to SQL – Implementing the Repository pattern

I have been working for the past few days to try and figure out how to get LINQ to SQL integrated into my normal nTier environment. I have also been trying to follow along with the general principles of Domain Driven Design but am finding that there are some deficiencies in the current LINQ to SQL implementation that force a few restrictions on how I do things.

Let’s first look at some of the restrictions

Disconnected DataContext

My understanding of the LINQ to SQL DataContext is that it prefers to remain open.  The primary reason for this is that its sole purpose in life is to track the state of it’s objects.  As you add new objects into your collections, modify existing objects, or delete objects, the DataContext is tracking everything.  Then you can all DataContext.SubmitChanges() and it will persist all of those changes into the database.

Keeping a DataContext around for the life of a web application though just doesn’t sound realistic to me!  I am very much used to the idea of opening a connection to the database, getting in, getting my job done, and getting out.  As soon as I start down the standard tiered way of thinking where I open my DataContext, get an object, close the DataContext, do some work on my object, open a new DataContext, persist my object, and finally close that DataContext – LINQ to SQL is very unhappy with me!

This is just not the way that LINQ to SQL is currently designed to work!  There are many work around’s to get it to work the way it was intended, but it seems to add a level of complexity to the system that I think is inappropriate.

Interesting reading:

LINQ to SQL DataContext Lifetime Management

LINQ to SQL and attaching Entities

Simple object relationships

When I think about LINQ to SQL I am thinking that I have a whole ORM system similar to NHibernate that allows me to fetch an object and it’s children, make some changes, push that back into the database, etc.  All seamlessly!  That has not been the case so far. 

I am finding that LINQ to SQL doesn’t like working with complex relationships.  This is primarily due to my working in a disconnected environment of course.  I am told that if I were to just use one DataContext that I keep alive forever – all would be fine.  I am a web developer though – not a desktop guy!  This just isn’t possible.

This leaves me to managing my own relationships between my objects.

Persisting child objects

In the same way that I would think LINQ to SQL would allow me to fetch and play with child objects I would think it would allow me to easily persist them.  This too doesn’t appear to be the case.  When spinning up a new DataContext I have to first detach all my children objects from the parent object, then attach the parent to the DataContext, then attach the children object to the parent object, and then SubmitChanges().  This is not always 100% though!

Value Objects

Entities and Value Objects are a very important part of Domain Driven Design.  A value object is basically an object that can’t exist without a parent object (or Entity).  It doesn’t have any unique identifier by itself.  Where as an Entity object can exist without any help from other objects.  As far as LINQ to SQL is concerned, all objects are to be Entity objects.  This makes sense considering that LINQ needs a unique identifier in order for it to do it’s job efficiently.  However, this breaks some of the concepts of Domain Driven Design!

Now let’s look at how we can get around these issues

Disconnected DataContext

When working with a disconnected nTier environment we need to be able to spin up a connection to the database, do some work, and disconnect – freeing up resources and going on our marry way.  LINQ to SQL provides some features that allows you to do this as long as you are aware of the surrounding issues with making this decision.  LINQ to SQL provides you with the Attach() method.  There are various overrides of Attach(), but the one we are most concerned with is the one that allows you to specify that the object you are attaching already exists and that you are attaching a modified version of it.

dc.Respondents.Attach(respondent, true);
dc.SubmitChanges();

Simple object relationships

To be totally honest with you, the easiest way I have found to work with LINQ to SQL and how it manages relationships regarding my objects is to simply remove all knowledge of my relationships.  At first this may sound a bit off, but it really does work out quite well in a Domain Driven Design where my repositories are responsible for simple fetching and persistence functionality and my services are more concerned with managing the relationships of my objects.  As soon as I decided to use LINQ to SQL in this manner, everything started to work without any problem.  No more hacked work around’s to try and get it to perform as advertised!

Persisting child objects

As stated above, now that LINQ knows nothing about how my objects are related to one another, I no longer have to worry about this as an issue.  I can now directly handle my relationships at the service layer above my repositories.  If I need to save a complex object with child objects, I can pass it to a service layer which will break it apart and save out the underlying objects.  This gives me total control at this point.

Value Objects

I have yet to find a fix for this issue!  LINQ requires that our objects are unique in the system on their own merit!  I doubt this will ever change.

Putting it all together?

Now that we know about the pros and cons of LINQ to SQL in a tiered environment and understanding my suggested work around’s, lets see it in action.

Connection Wrapper

My connection wrapper may be a bit more complex than you might need.  It is set up to work in an automated building/testing environment where NAnt is in control of which connection string to use.

using System;
using System.Xml;
using Company.Project.Core.Domain;
using Company.Project.Core.Properties;

namespace Company.Project.Core.DataAccess.Impl {
public
class Connection {
 public
  ProjectDataContext GetContext() {
    string connString = "";
    try {
      XmlDocument doc = new XmlDocument();
      doc.Load("ConnectionStringToUse.xml");

      XmlNodeList xnl = doc.GetElementsByTagName("environment");
      XmlElement xe = (XmlElement)xnl[0];

      switch (xe.InnerText.ToString().ToLower()) {
        case "local":
          connString = Settings.Default.ProjectConnectionStringLocal;
          break;

        case "development":
          connString = Settings.Default.ProjectConnectionStringDevelopment;
          break;

        case "production":
          connString = Settings.Default.ProjectConnectionStringProduction;
          break;

        default:
          throw new Exception("No connection string defined in app.config!");
      }
    } catch {
      connString = Settings.Default.ProjectConnectionStringLocal;
    }

    ProjectDataContext fdc = new ProjectDataContext(connString);
    return fdc;
  }
}
}  // namespace Impl

This of course could be greatly simplified to just return the default DataContext!

Once we have this in place we can start creating a repository.  A basic repository should provide ways to get an object by ID, save an object (inserting and updating), and delete an object.  It might look something like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Company.Project.Core.Domain;
using Company.Project.Core.Properties;
using StructureMap;

namespace Company.Project.Core.DataAccess.Impl {
[Pluggable("Default")] public class RespondentRepository {
 private
  Connection conn;
 public
  RespondentRepository() { conn = new Connection(); }

 public
  Respondent GetRespondentByID(Int32 RespondentID) {
    Respondent result;
    using(ProjectDataContext dc = conn.GetContext()) {
      result =
          dc.Respondents.Where(r = > r.RespondentID == RespondentID).First();
    }
    return result;
  }

 public
  void SaveRespondent(Respondent respondent) {
    using(ProjectDataContext dc = conn.GetContext()) {
      if (respondent.RespondentID > 0) {
        dc.Respondents.Attach(respondent, true);
      } else {
        dc.Respondents.InsertOnSubmit(respondent);
      }
      dc.SubmitChanges();
    }
  }

 public
  void DeleteRespondent(Respondent respondent) {
    if (Settings.Default.PerformActualDeletes) {
      using(ProjectDataContext dc = conn.GetContext()) {
        dc.Respondents.DeleteOnSubmit(respondent);
        dc.SubmitChanges();
      }
    } else {
      respondent.FlaggedForDelete = true;
      SaveRespondent(respondent);
    }
  }
}
}  // namespace Impl

The only thing that is not standard C# code here is the reference to StructureMap and the attribute of [Pluggable(“Default”)].  For more on what StructureMap is look here:

http://structuremap.sourceforge.net/Default.htm

To get started with our repository you will first notice the constructor for this class is spinning up a new instance of our Connection object.  This is important as each method will need to be able to speak with the database and they use the Connection object to do so.

From there we have a GetObjectByID method.  This will perform the basic fetch functionality that all systems need.

public Respondent GetRespondentByID(Int32 RespondentID)
        {
            Respondent result;
            using (ProjectDataContext dc = conn.GetContext())
            {
                result = dc.Respondents.Where(r => r.RespondentID == RespondentID).First();
            }
            return result;
        }

One thing to notice here is that we are spinning up our DataContext inside of a using statement.  This means that the DataContext will only exist in that using statement and will be disposed of when we are through with our work.  We then call conn.GetContext() to get an instance of our DataContext.  As this is a simple selection operation I have used a Lambda to grab the record with a corresponding ID and then called .First() to insure that we will only be returning one object.  Since we are using a unique ID I should only be returning one object anyways…but better safe than sorry!  Keep in mind that when working with LINQ, some methods are deferred and the scope of the DataContext is local to the using statement.  This means that if you want to return a result set or an object you may need to create a variable that is a bit farther reaching.  In our case we declared a Respondent object which is then passed out of the method.

We then get to our SaveRespondent method.

public void SaveRespondent(Respondent respondent)
{
    using (ProjectDataContext dc = conn.GetContext())
    {
        if (respondent.RespondentID > 0)
        {
            dc.Respondents.Attach(respondent, true);
        }
        else
        {
            dc.Respondents.InsertOnSubmit(respondent);
        }
        dc.SubmitChanges();
    }
}

This method is responsible for both inserting new objects and saving existing ones.  The make up of how we retrieve an DataContext is the same as before.  In this case though we want to test the passed in object to see if it has an ID already or not.  If it doesn’t have an ID then we will insert the item into the database.  Otherwise we will save it to the database.  We then SubmitChanges() to actually perform the persistence.

The only thing left is to provide a way for us to easily delete an object.

public void DeleteRespondent(Respondent respondent)
{
    if (Settings.Default.PerformActualDeletes)
    {
        using (ProjectDataContext dc = conn.GetContext())
        {
            dc.Respondents.DeleteOnSubmit(respondent);
            dc.SubmitChanges();
        }
    }
    else
    {
        respondent.FlaggedForDelete = true;
        SaveRespondent(respondent);
    }
}

Again, the make up of how we obtain a DataContext is the same.  There is a bit more code here than is necessary.  Technically all you need is what you see in the using statement.  However, I have baked in some extra logic here that asks are we performing real time deletes or system/batch deletes later. The basic real time delete logic is this:

using (ProjectDataContext dc = conn.GetContext())
{
    dc.Respondents.DeleteOnSubmit(respondent);
    dc.SubmitChanges();
}

The Settings.Default.PerformActualDeletes is simply a flag to determine where we are going to do the real time deletes or not.  If we are not doing real time deletes then we update the FlaggedForDelete property to true.  Then a custom process would clean out the FlaggedForDelete objects later in the evening.

Once all of these methods are in place you can then start building services on top of the repository if you feel you need to.  For the most part these simple repository methods should meet your needs.  But in the case that you have a complex object, you may need a service to assemble it or disassemble it and persist it’s children to the database via the repository.

This article is part of the GWB Archives. Original Author: Andrew Siemer`s

Related Posts