Как найти источник всех объектов, которые отслеживаются на предмет изменений в EF CodeFirst CTP5? - PullRequest
3 голосов
/ 16 марта 2011

У меня проблема «На объект сущности нельзя ссылаться несколькими экземплярами IEntityChangeTracker». После некоторой проверки кажется, что у меня есть объект, который отслеживается на предмет изменений. Проблема в том, что я не знаю источника проблемного объекта ... он явно помещен в контекст, но я не уверен, какой вызов не был должным образом отключен.

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

Ошибка выводится в строке 226, поэтому похоже, что у меня либо есть «скрытый» Клиент, либо, возможно, это вызвано одним из свойств Клиента, поскольку у Клиента есть пара других свойств, которые являются их собственным комплексом. типы объектов ...

Line 224:                    if (null != this.Customer)
Line 225:                    {
Line 226:                        context.Entry(this.Customer).State = EntityState.Unchanged;
Line 227:                    } 

Ошибка не говорит , какой объект вызывает ошибку, она просто указывает на строку 226. После , предполагая , это фантомный объект Customer, который вызывает это, я пытался :

        var test = ((IObjectContextAdapter)dataContext).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged);

        foreach(var e in test)
        {
            if(e.GetType() == typeof(Customer))
            {
                dataContext.Detach(e);
            }
        }

Идея состояла в том, чтобы пройтись по предмету, который содержит ссылки на все объекты, надеемся найти непослушного Клиента и запустить его. Но, увы, это не сработало; Клиенты не найдены в этом цикле. О, к вашему сведению - это выполняется за несколько строк до предыдущего кода, так что я не крадусь в создании каких-либо дополнительных объектов.

Так что мне нужен способ определить, какой объект на самом деле вызывает ошибку.

@ Ladislav - FYI - у меня есть общая библиотека, которая содержит все бизнес-объекты (BO). Эта общая библиотека используется другими проектами - службой Windows, веб-службой и т. Д. Я пытался заставить каждого BO отвечать за заполнение и сохранение себя, чтобы у меня не было одного класса доступа к данным hugo. Каждый BO отвечает за свой метод Save (). Вот пример метода current saveUpdate:

    public void SaveOrUpdate(DataContext context)
    {
        if (context.Entry(this).State == EntityState.Detached)
        {
            context.Customers.Add(this);
            context.SaveChanges();
        }
        else //update
        {
            context.Entry(this).State = System.Data.EntityState.Modified;
            context.SaveChanges();
        }
    }

Что касается вашего предложения по области действия, я пробовал различные стратегии - на первый взгляд все говорят, что делают это атомарно - поэтому у меня был каждый метод, собирающий новый экземпляр DataContext, чтобы выполнить свою работу. Это работало нормально, пока объекты не были очень сложными и не зависели друг от друга, то есть содержали только свойства базового типа, такие как int и string.

Но как только я начал получать эти ошибки параллелизма, я углубился в это и обнаружил, что DataContext каким-то образом удерживал ссылки на объекты , даже когда он был утилизирован Это немного сумасшедший кусок инженерный, имхо. То есть, если я добавлю BO клиента в DataContext, затем позволю DataContext выйти из области видимости и быть уничтоженным, а затем развернуть новый DataContext, чтобы сделать что-то, исходный указатель Customer BO все еще там!

Итак, я прочел несколько статей о StackOverflow (я бы добавил, что вы можете добавить много ответов), Трактат Рика Штрола о DataContext Lifetime Management и 8 Entity Framework Gotchas от Julia Lerman

Итак, Джулия говорит, что добавьте метод Dispose, и я это сделал, но это не помогло, DataContext все еще волшебным образом удерживает ссылку.

Итак, Рик говорит, что попытайтесь использовать «глобальный» DataContext, чтобы у вас был только один DataContext, о котором нужно беспокоиться, и он должен знать все, что происходит, чтобы он не наступал самому себе. Но это тоже не сработало. Честно говоря, Рик говорит о Linq to SQL и веб-приложении, но я надеялся, что оно будет применимо и ко мне.

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

Ну, я разбил Единицу работы , чтобы обозначить все изменения, дополнения и обновления, сделанные для группы объектов, которые вы хотели бы сделать вместе.Итак, для моего примера Вот некоторые BO и свойства:

MessageGroup
- Свойство: список
- Свойство: Клиент

Клиент
- свойство: список
- свойство: список

сообщение
- свойство: клиент
- свойство: MessageGroup
- свойство: пользователь

Пользователь
- Свойство: Клиент
- Свойство: Список

В системе, когда приходит MessageGroup (в формате Xml), она проверяется и анализируется.Конструктор MessageGroup использовал Dependency Injection и принимает DataContext в качестве одного из своих параметров - так что все создаваемые дочерние BO используют этот единственный экземпляр DataContext.Customer выбирается из базы данных (или создается новая) и назначается в MessageGroup ... давайте предположим, что это существующий Customer - поэтому обновлять его не нужно, он только что обновленDataContext.

Затем цикл MessageGroup.Messages зацикливается, и первый дочерний объект BO, создаваемый, является новым объектом User.Я назначаю тот же объект Customer (из MessageGroup) пользователю.Однако, когда context.Users.Add (this) вызывается, я получаю ошибку.Если я не назначаю Клиента Пользователю, я не получаю ошибку.

Так что теперь у меня есть Клиент (или дочернее свойство, я не уверен), который только что из БД,что мне не нужно отслеживать, что вызывает у меня беспокойство.Я думал, что смогу просто удалить его из контекста, используя что-то вроде:

var cust = Customer.GetCustomerFromExternalId(crm.CustomerId);
dataContext.Detach(cust);
dataContext.SaveChanges();

Но я все равно получаю сообщение об ошибке, несмотря на то, что я просто удалил его.Конечно, если это одно из дочерних свойств Customer, может быть, оно не было удалено?

В настоящее время я задаюсь вопросом, подходит ли шаблон репозитория для моих целей.Мне также интересно, является ли EF CodeFirst в корне ошибочным или просто слишком сложным?Может мне стоит использовать SubSonic или NHibernate вместо этого?

1 Ответ

1 голос
/ 16 марта 2011

Как я знаю, вероятно, нет четкого способа получить связанный контекст от объекта POCO - все связанные свойства динамического прокси не являются публичными. Для проверки сущностей в DbContext используйте:

context.ChangeTracker.Entries<Customer>().Where(e => e.State == ...)

Лучший способ избежать ваших проблем - использовать один контекст на «единицу работы». Очевидно, вы не будете следовать этому подходу, если у вас есть объекты из разных контекстов. Более того, похоже, что вы используете несколько одновременных живых контекстов.

...