EF6 - Как оптимизировать загрузку сущностей для конкретного примера?

У меня есть Users - Rules таблицы с many-to-many отношениями. Я хочу удалить некоторые правила определенного пользователя и обновить одно поле этого пользователя в том же шаге / транзакции базы данных, поэтому у меня есть следующий псевдокод в BL:

this.UnitOfWork.ExecuteTransaction(() =>
{
   // delete rules
   foreach (var rule in ruleList)
       repoRules.DeleteRulesForUser(user, rule);

   // update user field
   repoUser.UpdateUserField(user);
}

В репозиториях EF:

public void DeleteRulesForUser(User user, Rule rule)
{
   // check user
   EFUser u = this.dbContext.EFUsers.Find(user.UID);
   if (u == null)
      throw new DBModuleException();

   // check rule
   EFRule r = this.dbContext.EFRules.Find(rule.UID);
   if (r == null)
      throw new DBModuleException();


   // detach previous entities, so that we can attach with relation 
   this.dbContext.ObjectContext.Detach(u);
   this.dbContext.ObjectContext.Detach(r);

   // prepare entities
   var efUser = new EFUser { US_UID = user.UID };
   var efRule = new EFRule { RU_UID = rule.UID };
   efRule.Users.Add(efUser);

   // attach
   this.dbContext.EFRules.Attach(efRule);

   // delete relation
   efRule.Users.Remove(efUser);
}

public void UpdateUserField(User user)
{
    // get updating user
    EFUser efUser = this.dbContext.EFUsers.FirstOrDefault(u => u.UID == user.UID);
    if (efUser == null)
       throw new DBModuleException("Not found!");

    // perform update
    efUser.US_Field = user.Field;
}

Моя реализация UnitOfWork (только с EF6 или выше):

public override void ExecuteTransaction(Action transAction)
{
    // make sure to make IsolationLevel flexible later
    using (var dbTransaction = this.dbContext.Database
                              .BeginTransaction(IsolationLevel.ReadCommitted)) 
    {
       try
       {
           transAction();

           // save changes if not done yet
           this.dbContext.SaveChanges();

           // commit transaction
           dbTransaction.Commit();
       }
       catch (System.Data.DataException ex)
       {
           // rollback
           dbTransaction.Rollback();

           // handling exception ceremony comes here
       }
    }

Моя проблема в том, что DeleteRulesForUser прикрепляет этого пользователя только с UID (как указано в методе), но затем, позже в UpdateUserField EF предоставляет этому пользователю только UID, вместо того, чтобы получать от пользователя полные данные, потому что FirstOrDefault вызывает базу данных. Я предполагаю, что так работает кеш EF (?). В любом случае операция обновления не выполняется, и я хочу узнать, как решить эту проблему.

Я вижу необходимость иногда загружать объект только с UID, но иногда и с полными данными.

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

Чтобы вы посоветовали?

К вашему сведению: у меня отключена функция LazyLoading.

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

РЕДАКТИРОВАТЬ1: По мере того, как я читаю немного больше, возможно, AutoDetectChangesEnabled может помочь, но мне нужно больше информации, чтобы узнать.


person Learner    schedule 02.04.2014    source источник
comment
Что такое пользователь? Правило? Что вы имеете в виду: это не удается?   -  person L-Four    schedule 02.04.2014
comment
Пользователи и правила - это таблицы в базе данных, отношение "многие ко многим"   -  person Learner    schedule 02.04.2014
comment
Как они определены и где находится отображение? Это сначала код?   -  person L-Four    schedule 02.04.2014


Ответы (2)


Если у вас есть требование удалить тысячи правил, я бы предложил отказаться от Entity Framework и использовать операторы ADO.NET и sql (или хранимые процедуры), что намного быстрее и проще. См. Пакетное обновление / удаление EF5.

Также нет причин для прикрепления или отсоединения предметов. Если вы используете EF, создавайте контекст с того момента, когда он вам нужен, и удаляйте его как можно скорее.

person L-Four    schedule 02.04.2014
comment
На самом деле у меня уже есть решение с простым Ado.Net и оно работает нормально, но я должен предоставить поддержку Oracle помимо MS SQL, поэтому я подумал, что EF может прийти на помощь (поэтому репозитории EF были созданы позже), но кажется это также имеет недостатки - person Learner; 02.04.2014
comment
Затем найдите поставщика Oracle (stackoverflow.com/questions/682773/) - person L-Four; 02.04.2014
comment
Я забыл упомянуть, что мой DAL - это тонкая оболочка над хранимыми процедурами, поэтому мигрировать на Oracle будет непросто. Вот почему я подумал, что иметь один ORM для работы с несколькими базами данных - хорошая идея. - person Learner; 02.04.2014
comment
В конце концов, у меня это почти получилось, но я заметил такое поведение, которое хочу как-то исправить. - person Learner; 02.04.2014

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

Вы можете просто загрузить сущность User и включить правила в набор результатов для пакетной операции: var dbUser = ctx.Users.Include(x => x.Rules).FirstOrDefault(x => x.UID == user.UID);

Теперь, когда у вас есть полностью заполненный пользовательский объект и все связанные с ним правила, вы можете найти правило, которое хотите удалить: var ruleToDelete = dbUser.Rules.FirstOrDefault(x => x.UID == rule.UID);.

Теперь у вас есть правило от пользователя, которого вы хотите удалить, и вам не нужно возвращаться к базе данных. Теперь вы можете удалить правило: ctx.Rules.Remove(ruleToDelete);

Теперь, когда вы вызываете ctx.SaveChanges(), изменения будут возвращены в базу данных. Вы должны понимать, что Entity Framework автоматически включает в себя все операторы insert, update и delete в транзакции. Кроме того, EF не поддерживает пакетные операции insert, update и delete.

//Get the user from the db, should check for null after this
var dbUser = ctx.Users.Include(x => x.Rules).FirstOrDefault(x => x.UID == user.UID);

//Get the rule from the user object you want to remove
var ruleToDelete = dbUser.Rules.FirstOrDefault(x => x.UID == rule.UID);

//Set the state of the object to "Deleted" in the state manager so it will issue a delete statement
ctx.Rules.Remove(ruleToDelete);

//Save the changes to the database in a transaction
ctx.SaveChanges()
person Justin    schedule 02.04.2014
comment
Правда, я новичок ... Я понимаю ваш ответ, но я не могу сделать то, что вы предлагаете, потому что в моем реальном приложении у меня будут тысячи правил и я не хочу загружать их в память. Мне нужно сделать это как-то отстраненно - person Learner; 02.04.2014
comment
Как же тогда кто-то может удалить правило у пользователя? Я предполагаю, что есть экран со списком всех правил, так разве вы уже не загружаете все правила в тот или иной момент? - person Justin; 02.04.2014