Проблема с использованием LINQ to SQL с одним DataContext для каждого атомарного действия

Я начал использовать Linq to SQL в системе (немного похожей на DDD), которая выглядит (слишком упрощенно) следующим образом:

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;
    }
}

Теперь у меня проблема. Когда я пытаюсь использовать SomeEntityRepository вот так

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);
    }
 }

все работает нормально, пока программа не дойдет до последней WriteLine, потому что она выдает ObjectDisposedException, потому что DataContext больше не существует.

Я вижу настоящую проблему, но как мне ее решить? Думаю, есть несколько решений, но ни одно из тех, о которых я думал до сих пор, не подошло бы в моей ситуации.

  • 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.
    • Кроме того, я думаю, что выполнение context.SubmitChanges(), вероятно, потребует гораздо больше, чем я планировал.
  • 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 ...

Есть ли способ решить эту проблему?

Кстати: мы решили использовать Linq t0 SQL, потому что это относительно легкое ORM-решение, включенное в .NET framework и Visual Studio. Если .NET Entity Framework больше подходит для этого шаблона, возможно, стоит переключиться на него. (Мы еще не так далеки от реализации.)


person hangy    schedule 03.11.2008    source источник


Ответы (5)


У Рика Страла есть хорошая статья об управлении жизненным циклом DataContext здесь: http://www.west-wind.com/weblog/posts/246222.aspx.

В принципе, подход атомарного действия хорош в теории, но вам нужно будет держать свой DataContext под рукой, чтобы иметь возможность отслеживать изменения (и извлекать дочерние элементы) в ваших объектах данных.

См. Также: Несколько / один экземпляр Linq to SQL DataContext и LINQ to SQL - где находится ваш DataContext?.

person Corbin March    schedule 03.11.2008
comment
Спасибо, у вас есть примеры кода для хранения контекста данных в загруженном объекте без особых хлопот и накладных расходов? Кроме того, не будет ли это означать, что любой объект должен будет реализовать IDisposable, чтобы я мог правильно удалить DataContext? - person hangy; 03.11.2008
comment
По правде говоря, есть немного хлопот и накладных расходов. Я оценил каждый из методов статьи и остановился на DataContext / obj как на наименьшем зле. IDisposable - хорошая идея, хотя ваше соединение закрывается после использования всех записей: msdn.microsoft .com / en-us / library / bb386929.aspx - часто задаваемые вопросы 3 - person Corbin March; 04.11.2008

Вам необходимо либо:

1) Оставьте контекст открытым, потому что вы еще не полностью решили, какие данные будут использоваться (также называемая ленивая загрузка).

или 2) Получить больше данных при начальной загрузке, если вы знаете, что вам понадобится это другое свойство.

Объяснение последнего: здесь

person Timothy Khouri    schedule 03.11.2008

Я не уверен, что вам нужно отказываться от репозитория, если вы используете атомарные единицы работы. Я использую оба, хотя признаю, что отбрасываю оптимистичные проверки параллелизма, поскольку они все равно не работают на уровнях (без использования временной метки или какого-либо другого необходимого соглашения). В итоге я получаю репозиторий, который использует DataContext и выбрасывает его, когда он готов.

Это часть несвязанного примера Silverlight, но первые три части показывают, как я использую шаблон репозитория с одноразовым контекстом LINQ to SQL, FWIW: http://www.dimebrain.com/2008/09/linq-wcf-silver.html

person Daniel Crenna    schedule 03.11.2008

Указание DataLoadOptions для выборки связанных элементов. Поскольку я хочу, чтобы уровень бизнес-логики в некоторых случаях просто отвечал некоторыми объектами, я не знаю, какие подсвойства им нужно использовать.

Если вызывающей стороне предоставлено соединение, необходимое для использования свойства .Relation, то вызывающая сторона может также указать 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;
person Amy B    schedule 03.11.2008
comment
Вызывающий может точно указать параметры загрузки, но я бы предпочел, чтобы система не просочилась на уровень бизнес-логики. Кроме того, почему доступ к свойству someEntity.Relation должен быть связан с указанием DataLoadOptions? - person hangy; 03.11.2008

Я этим и занимаюсь, и пока это действительно хорошо работает.

1) Сделайте DataContext переменной-членом в вашем репозитории. Да, это означает, что теперь ваш репозиторий должен реализовывать IDisposable и не оставлять его открытым ... возможно, вы хотите избежать этого, но я не нашел это неудобным.

2) Добавьте несколько методов в свой репозиторий, например:

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

Тогда ваш абонент будет выглядеть так:

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

Вам просто нужно убедиться, что, когда ваш репозиторий попадает в базу данных, он использует параметры загрузки данных, указанные в этих вспомогательных методах ... в моем случае «dlo» сохраняется как переменная-член, а затем устанавливается прямо перед попаданием в базу данных.

person Tom Lianza    schedule 03.09.2009