views:

1249

answers:

5

I have started using Linq to SQL in a (bit DDD like) system which looks (overly simplified) like this:

public class SomeEntity // Imagine this is a fully mapped linq2sql class.
{
 public Guid SomeEntityId { get; set; }
 public AnotherEntity Relation { get; set; }
}

public class AnotherEntity // Imagine this is a fully mapped linq2sql class.
{
 public Guid AnotherEntityId { get; set; }
}

public interface IRepository<TId, TEntity>
{
    Entity Get(TId id);
}

public class SomeEntityRepository : IRepository<Guid, SomeEntity>
{
    public SomeEntity Get(Guid id)
 {
  SomeEntity someEntity = null;
  using (DataContext context = new DataContext())
  {
   someEntity = (
    from e in context.SomeEntity
    where e.SomeEntityId == id
    select e).SingleOrDefault<SomeEntity>();
        }

        return someEntity;
 }
}

Now, I got a problem. When I try to use SomeEntityRepository like this

public static class Program
{
 public static void Main(string[] args)
 {
  IRepository<Guid, SomeEntity> someEntityRepository = new SomeEntityRepository();
  SomeEntity someEntity = someEntityRepository.Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"));
  Console.WriteLine(someEntity.SomeEntityId);
  Console.WriteLine(someEntity.Relation.AnotherEntityId);
 }
 }

everything works nicely until the program gets to the last WriteLine, because it throws an ObjectDisposedException, because the DataContext does not exist any more.

I do see the actual problem, but how do I solve this? I guess there are several solutions, but none of those I have thought of to date would be good in my situation.

  • Get away from the repository pattern and using a new DataContext for each atomic part of work.
    • I really would not want to do this. A reason is that I do not want to be the applications to be aware of the repository. Another one is that I do not think making linq2sql stuff COM visible would be good.
    • Also, I think that doing context.SubmitChanges() would probably commit much more than I intended to.
  • Specifying DataLoadOptions to fetch related elements.
    • As I want my Business Logic Layer to just reply with some entities in some cases, I do not know which sub-properties they need to use.
  • Disabling lazy loading/delayed loading for all properties.
    • Not an option, because there are quite a few tables and they are heavily linked. This could cause a lot of unnecessary traffic and database load.
  • Some post on the internet said that using .Single() should help.
    • Apparently it does not ...

Is there any way to solve this misery?

BTW: We decided to use Linq t0 SQL because it is a relatively lightweight ORM solution and included with the .NET framework and Visual Studio. If the .NET Entity Framework would fit better in this pattern, it may be an option to switch to it. (We are not that far in the implementation, yet.)

A: 

Specifying DataLoadOptions to fetch related elements. As I want my Business Logic Layer to just reply with some entities in some cases, I do not know which sub-properties they need to use.

If the caller is granted the coupling necessary to use the .Relation property, then the caller might as well specify the DataLoadOptions.

DataLoadOptions loadOptions = new DataLoadOptions();
loadOptions.LoadWith<Entity>(e => e.Relation);
SomeEntity someEntity = someEntityRepository
  .Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"),
  loadOptions);

//

using (DataContext context = new DataContext())
{
  context.LoadOptions = loadOptions;
David B
A caller could specify the load options for sure, but I would rather like the system not to have this leaked into a business logic layer. Also, why should accessing the someEntity.Relation property be related to specifying DataLoadOptions?
hangy
+3  A: 

Rick Strahl has a nice article about DataContext lifecycle management here: http://www.west-wind.com/weblog/posts/246222.aspx.

Basically, the atomic action approach is nice in theory but you're going to need to keep your DataContext around to be able to track changes (and fetch children) in your data objects.

See also: http://stackoverflow.com/questions/226127/multiplesingle-instance-of-linq-to-sql-datacontext and http://stackoverflow.com/questions/196253/linq-to-sql-where-does-your-datacontext-live.

Corbin March
Thanks, do you have any code examples for storing the data context in a loaded entity without too much hassle and overhead? Also, wouldn't this mean that any entity would have to implement IDisposable so I can dispose the DataContext properly?
hangy
Truth is, there's a bit of hassle and overhead. I evaluated each of the article methods and settled on DataContext/obj as the least evil. IDisposable is a good idea though your connection closes when all records are consumed: http://msdn.microsoft.com/en-us/library/bb386929.aspx - FAQ 3
Corbin March
+1  A: 

You have to either:

1) Leave the context open because you haven't fully decided what data will be used yet (aka, Lazy Loading).

or 2) Pull more data on the initial load if you know you will need that other property.

Explaination of the latter: here

Timothy Khouri
+1  A: 

I'm not sure you have to abandon Repository if you go with atomic units of work. I use both, though I admit to throwing out the optimistic concurrency checks since they don't work out in layers anyway (without using a timestamp or some other required convention). What I end up with is a repository that uses a DataContext and throws it away when it's done.

This is part of an unrelated Silverlight example, but the first three parts show how I'm using a Repository pattern with a throwaway LINQ to SQL context, FWIW: http://www.dimebrain.com/2008/09/linq-wcf-silver.html

Daniel Crenna
A: 

This is what I do, and so far it's worked really well.

1) Make the DataContext a member variable in your repository. Yes, this means you're repository should now implement IDisposable and not be left open... maybe something you want to avoid having to do, but I haven't found it to be inconvenient.

2) Add some methods to your repository like this:

public SomeEntityRepository WithSomethingElseTheCallerMightNeed()
{
 dlo.LoadWith<SomeEntity>(se => se.RelatedEntities);
 return this; //so you can do method chaining
}

Then, your caller looks like this:

SomeEntity someEntity = someEntityRepository.WithSomethingElseTheCallerMightNeed().Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"));

You just need to make sure that when your repository hits the db, it uses the data load options specified in those helper methods... in my case "dlo" is kept as a member variable, and then set right before hitting the db.

tlianza