ExecuteStoreCommand «Delete» возвращает другое количество записей в «DeleteObject» после удаления, почему?

#entity-framework #entity-framework-6

#entity-framework #entity-framework-6

Вопрос:

Здесь есть странный. Я использую EF 6 поверх SQL Server 2012 и C #.

Если я удаляю запись, используя DeleteObject, я получаю:

         //order.orderitem count = 11

        db.OrderItem.DeleteObject(orderitem);  
        db.SaveChanges();

        var order = db.order.First(r => r.Id == order.id);

        //Order.OrderItem count = 10, CORRECT
  

Если я удаляю элемент заказа, используя встроенный DML ExecuteStoreCmd, я получаю:

         //order.orderitem count = 11

        db.ExecuteStoreCommand("DELETE FROM ORDERITEM WHERE ID ={0}", orderitem.Id);

        var order = db.Order.First(r => r.Id == order.id);

        //order.orderitem count = 11, INCORRECT, should be 10
  

Таким образом, версия ExecuteStoreCommand сообщает 11, однако элемент OrderItem определенно удален из базы данных, поэтому он должен сообщать 10. Также я бы подумал, что First() выполняет активный поиск, таким образом повторно заполняя коллекцию «order.orderitem».

Есть идеи, почему это происходит? Спасибо.

РЕДАКТИРОВАТЬ: я использую ObjectContext

EDIT2: это самое близкое рабочее решение, которое у меня есть, используя «отсоединение». Интересно, что «отсоединение» на самом деле занимает около 2 секунд! Не уверен, что он делает, но это работает.

 db.ExecuteStoreCommand("DELETE FROM ORDERITEM WHERE ID ={0}", orderitem.Id);
db.detach(orderitem);
  

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

  var order = db.order.First(r => r.Id == order.id);
  

EDIT3: похоже, это работает для принудительного удаления записи обновления, но все равно занимает около 2 секунд:

 db.Refresh(RefreshMode.StoreWins,Order.OrderItem);
  

Я все еще не совсем понимаю, почему нельзя просто запрашивать как заказ.Первый (r => r.id==id) запрос типа часто занимает намного меньше 2 секунд.

Комментарии:

1. «Я все еще не совсем понимаю, почему нельзя просто запрашивать как заказ. Первый (r => r.id==id) запрос типа часто занимает намного меньше 2 секунд. » Вот почему DbContexts и их сущности должны быть недолговечными. Чем больше возможных объектов было кэшировано в DbContext, тем больше объектов он должен программно просмотреть на предмет возможных ссылок на объект, который вы хотите удалить / изменить. С новым DbContext удаление дочернего элемента происходит быстро, и любые другие возможные конфликты FK будут разрешены DB с нарушениями FK при попытке сохранения. (Отсюда и рекомендуемый подход.)

Ответ №1:

Вероятно, это связано с тем, что порядок и его элементы заказа уже известны контексту при выполнении ExecuteStoredCommand . EF не знает, что команда относится к какой-либо кэшированной копии Order , поэтому команда будет отправлена в базу данных, но не обновит какое-либо загруженное состояние объекта. Где -поскольку первый будет искать любой загруженный элемент OrderItem, и когда ему будет предложено удалить его из DbSet, он будет искать любые загруженные объекты, которые ссылаются на этот элемент заказа.

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

Если orderitem представляет объект, он должен просто иметь возможность использовать:

 db.OrderItems.Remove(orderitem);
  

Если заказ загружен, элемент заказа должен быть удален автоматически. Если заказ не загружен, без потерь, он будет загружен из базы данных при последующем запросе и загрузит набор элементов заказа из БД.

Однако, если вы хотите использовать подход SQL execute, отсоединение любого локального экземпляра должно удалить его из локального кэша.

 db.ExecuteStoreCommand("DELETE FROM ORDERITEM WHERE ID ={0}", orderitem.Id);
var existingOrderItem = db.OrderItems.Local.SingleOrDefault(x => x.Id == orderItem.Id);
if(existingOrderItem != null)
    db.Entity(existingOrderItem).State = EntityState.Detached;
  

Я не верю, что вам нужно будет проверять порядок OrderItem, чтобы обновить что-либо помимо этого, но я не уверен в этом на 100%. Как правило, когда дело доходит до изменения состояния данных, я предпочитаю загружать соответствующий объект верхнего уровня и удалять его дочерний элемент.

Итак, если бы у меня была команда для удаления элемента заказа из заказа:

 public void RemoveOrderItem(int orderId, int orderItemId)
{
    using (var context = new MyDbContext())
    {
        // TODO: Validate that the current user session has access to this order ID
        var order = context.Orders.Include(x => x.OrderItems).Single(x => x.OrderId == orderId);
        var orderItem = order.OrderItems.SingleOrDefault(x => x.OrderItemId == orderItemId);
        if (orderItem != null)
            order.OrderItems.Remove(orderItem);

        context.SaveChanges();
    }
}
  

Ключевые моменты этого подхода.

  • Хотя это означает повторную загрузку состояния данных для операции, эта загрузка выполняется по идентификатору, поэтому она выполняется быстро.
  • Мы можем / должны проверить, что запрошенные данные применимы для пользователя. Любая команда для заказа, к которому они не должны обращаться, должна быть зарегистрирована, и сеанс завершен.
  • Мы знаем, что будем иметь дело с текущим состоянием данных, а не основывать решения на значениях / данных с момента времени, когда данные были впервые прочитаны.

Комментарии:

1. Огромное спасибо за это. Я использую ObjectContext и не вижу «Local» и «Entity» в качестве свойств. Насколько я понимаю, DbContext — это оболочка вокруг ObjectContext. Как я добиваюсь того же с ObjectContext, особенно в отношении «отсоединения»?

2. Самое близкое, что у меня есть к решению, — это EDIT2 в моем вопросе. Однако это добавление занимает 2 секунды