Entity Framework 4.1 Code First с сохранением двойного уровня N - PullRequest
1 голос
/ 09 июня 2011

У меня есть проект WCF с сущностями, у которых много детей.У меня есть бизнес-уровень и уровень доступа к данным, на уровне доступа к данным у меня есть репозитории, которые извлекаются и сохраняются в моей базе данных.Я понимаю, что EF создает и уничтожает DataContext по мере необходимости.Например, допустим, у меня есть сущность Person и сущность Book (это не мое приложение, а просто попытка проиллюстрировать проблему).

Предположим, что Person выглядит следующим образом.

Person
  string Name
  vitual ICollection<Book> Books

С Книгой может быть что-то вроде этого

Book
 string Title
 Person PersonLending

Теперь в моем BLL я хочу прочитать таблицу лиц и затем назначить книгу этому человеку, но человек уже существует в базе данных, поэтомуBLL вызывает хранилище для объекта лица.

var person = repository.GetPerson("John Doe");

Мой репозиторий имеет этот код.

using(var context = new MyContext())
{
  return (from p in context.Person
          where p.Name == person
          select p).FirstOrDefault());
}

Теперь в BLL я создаю новую книгу и назначаю этому человеку это.

var book = new Book();
book.PersonLending = person;
book.Title = "New Book";

repository.SaveBook();

Наконец, в хранилище я пытаюсь сохранить книгу обратно.

using(var context = new MyContext())
{
  context.Book.Add(book);
  context.SaveChanges();
}

Теперь, что происходит, я получаю две строки Person в таблице.Насколько я понимаю, это вызвано тем, что первый контекст разрушен, а второй контекст не знает, что Персона уже существует.

У меня есть два вопроса, я думаю.

  1. Что такоелучшая практика для обработки DataContext в WCF?Если только один текстовый текст передается из класса в класс и обратно в репозитории.
  2. Или есть способ сделать это сохранение.

Я попытался установить EntityStateНе изменяется для Person, но, похоже, он не работает.

Редактировать:

Я изменил параметры, чтобы создать новый DataContext для запроса (AfterReceiveRequest и BeforeSendReply),

public class EFWcfDataContextAttribute : Attribute, IServiceBehavior
{
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase){}

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters){}

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (var endpoint in channelDispatcher.Endpoints)
            {
                endpoint.DispatchRuntime.MessageInspectors.Add(new EFWcfDataContextInitializer());
                //endpoint.DispatchRuntime.InstanceContextInitializers.Add(new EFWcfDataContextInitializer());
            }
        }    
    }

Инициализатор

public class EFWcfDataContextInitializer : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        instanceContext.Extensions.Add(new EFWcfDataContextExtension(new MyDataContext()));
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        WcfDataContextFactory.Dispose();
    }
}

И расширение

public class EFWcfDataContextExtension : IExtension<InstanceContext>
{
    public ICoreDataContext DataContext { get; private set; }

    public EFWcfDataContextExtension(ICoreDataContext coreDataContext)
    {
        if(DataContext != null)
            throw new Exception("context is not null");

        DataContext = coreDataContext;
    }

    public void Attach(InstanceContext owner){}

    public void Detach(InstanceContext owner) {}
}

Это, кажется, создает совершенно новую проблему.Я получаю текущий контекст, вызывая OperationContext.Current.InstanceContext.Extensions.Find (). DataContext, но теперь кажется, что два контекста влияют друг на друга.По тому же запросу первый вернет нулевую запись, а второй будет успешным.Они оба находятся в уникальных сеансах, и когда они оба созданы, они являются нулевыми и создаются как новый DataContext.Когда я проверяю свойство Database.Connection с первого раза, оно закрывается, и попытка открыть его вручную создает дополнительные ошибки.Я действительно думал, что это решит проблему.

Я также попытался сделать это с IContractBehaviour, с тем же результатом.Поэтому я либо делаю что-то не так, либо упускаю что-то очевидное.

PS: Я пытался установить состояние «Без изменений» до того, как сделал исходное сообщение.PPS: В случае, если кто-то задается вопросом, моя фабрика данных просто использует эти два метода

  public static void Dispose()
    {
        ICoreDataContext coreDataContext = OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
        coreDataContext.Dispose();
        coreDataContext = null;
    }

    public static ICoreDataContext GetCurrentContext()
    {
        var context =  OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
        if (context != null)
        {
            if (context.Database.Connection.State == ConnectionState.Closed)
                context.Database.Connection.Open();
        }

        return context;
    }

1 Ответ

1 голос
/ 09 июня 2011

Ваше понимание абсолютно верно. Как только вы передаете данные обратно службе, новый контекст не знает ни Book, ни Person. Вызов Add на Book приводит к маркировке каждого неизвестного объекта в графе объектов как Added. Это очень большая проблема отдельных сценариев.

Решение не разделяет контекст Это худший способ решения проблемы, поскольку он создает множество других проблем и в конце концов все равно не будет работать. Используйте новый контекст для каждого вызова службы.

Попробуйте это:

using(var context = new MyContext())
{
    context.Book.Attach(book);
    context.Entry(book).State = EntityState.Added;
    context.SaveChanges();
}

или это:

using(var context = new MyContext())
{
    context.Book.Add(book);
    context.Entry(book.PersonLending).State = EntityState.Unchanged;
    context.SaveChanges();
}

Эта проблема более сложная , и как только вы начинаете посылать более сложные графы объектов с изменениями в отношениях. В итоге вы сначала загрузите граф объектов и объедините изменения в прикрепленные объекты.

...