Обновление базы данных с помощью анонимного типа?

#c# #entity-framework #entity-framework-4

#c# #entity-framework #entity-framework-4

Вопрос:

Следующий код получает все строки из моей таблицы Activities, которые еще не были опубликованы в Twitter. Затем он перебирает и публикует обновления Twitter для каждой из этих строк. В процессе я хотел бы обновить базу данных, чтобы указать, что эти строки теперь были «перепечатаны».

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

Есть ли способ выполнить это эффективно? Или это еще один случай, когда EF заставляет меня идти на компромиссы в производительности?

 using (MyEntities context = new MyEntities())
{
    var activities = from act in context.Activities
                     where act.ActTwittered == false
                     select new { act.ActID, act.ActTitle, act.Category,
                     act.ActDateTime, act.Location, act.ActTwittered };

    foreach (var activity in activities)
    {
        twitter.PostUpdate("...");
        activity.ActTwittered = true; // <== Error: ActTwittered is read-only
    }
}
  

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

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

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

3. @Jonathan Wood — Разве ты не знал, во что ввязываешься? Мы не стали использовать EF, потому что чувствовали, что еще много возможностей для улучшения.

4. @JonH: Ну, как я себе это представлял, единственный способ полностью понять, во что я ввязываюсь, — это попробовать это, что я и делал. EF сейчас в моде, и, похоже, у меня появится больше возможностей для консультаций, если я его изучу. Но пока мне не нравится то, что я вижу. Рад слышать, что я не единственный.

5. @Jonathan Wood — В SAP тоже есть много возможностей. Это не из-за того, насколько это отличный продукт. Это все из-за действительно плохой архитектуры продукта, отсюда и «возможности».

Ответ №1:

Вы могли бы попробовать «подход с поддельным объектом», подобный этому:

 using (MyEntities context = new MyEntities())
{
    var activities = from act in context.Activities
                     where act.ActTwittered == false
                     select new { act.ActID, act.ActTitle, act.Category,
                     act.ActDateTime, act.Location, act.ActTwittered };

    foreach (var activity in activities)
    {
        twitter.PostUpdate("...");

        // Create fake object with necessary primary key
        var act = new Activity()
        {
            ActID = activity.ActID,
            ActTwittered = false
        };

        // Attach to context -> act is in state "Unchanged"
        // but change-tracked now
        context.Activities.Attach(act);

        // Change a property -> act is in state "Modified" now
        act.ActTwittered = true;
    }

    // all act are sent to server with sql-update statements
    // only for the ActTwittered column
    context.SaveChanges();
}
  

Это «теоретический» код, не уверен, сработает ли он.

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

Больше не «теоретическое». Я протестировал это с DbContext EF 4.1, и это работает так, как описано в примере кода выше. (Поскольку DbContext это всего лишь API-оболочка ObjectContext , можно почти с уверенностью предположить, что он также будет работать в EF 4.0.)

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

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

2. Нет, хитрость в том, что, прикрепляя этот поддельный объект к контексту, EF предполагает, что он отражает текущее состояние объекта в базе данных (что, конечно, неправильно). Если мы изменим сейчас одно свойство после присоединения , механизм отслеживания изменений EF сохранит эту информацию о том, что только это единственное свойство было изменено внутренне, чтобы создать команду sql update позже, когда мы выполним SaveChanges. Таким образом, обновление не затрагивает другие столбцы в базе данных. Как уже было сказано, я не тестировал что-то подобное. Но у меня хорошее предчувствие, что этот или какой-то очень похожий подход должен сработать.

3. @Slauma: Понятно. Итак, вы думаете, что EF достаточно умен, чтобы обновлять только столбцы, помеченные как нуждающиеся в обновлении. Я не знаю, буду ли я использовать это, но, если это сработает, я согласен, что это отвечает на заданный вопрос. Спасибо.

4. @Jonathan Wood: Один важный вопрос: используете ли вы EF4.0 с ObjectContext ? Если да, используете ли вы объекты, производные от EntityObject , или вы используете POCOs? Или вы используете EF4.1 с DbContext ? Вероятно, мой код сможет работать только с объектами, производными от EntityObject . Я посмотрю, что делать в других случаях.

5. @Slauma: Я не установил EF4.1. Что касается первого вопроса, я думаю, что да. Разве это не очевидно из моего кода?

Ответ №2:

Если вы просто выберете ‘act’, то это должно сработать. Не забудьте отправить после редактирования.

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

1. Как указывалось, это заставляет мой код извлекать намного больше информации из базы данных, что занимает немного больше времени. Это еще один пример, когда EF требует от меня написания менее эффективного кода?

2. Как сказал Акаш, создавая анонимный тип, вы теряете соединение с таблицей; поэтому строка не может быть обновлена. Вы можете обновлять объекты, только если извлекаете полный объект, что на самом деле является единственной разумной ситуацией, которую вы можете ожидать.

3. @Nik: Вероятно, время потрачено не зря, но я бы сделал исключение из этой последней части. Можно разумно ожидать, что EF предоставит некоторый механизм для эффективного обновления одного столбца в выбранных строках без необходимости загружать все данные для этих строк. Я могу (и сделаю) это довольно легко без EF. Я не вижу ничего, что мешало бы EF использовать такие методы. Но опять же, я программирую много лет и довольно самоуверен в таких вещах. 🙂

4. Предоставьте механизм для обновления одного столбца, конечно, мне бы это понравилось, и я думаю, что это должно быть там. Однако, когда вы смотрите на имеющийся у вас код, вы бы спросили, должен ли EF знать, из какой таблицы взят каждый столбец, а затем, поверх этого, из какой строки, что может потребовать от него определения дополнительных столбцов идентификаторов для сбора. Я думаю, что накладные расходы, вероятно, больше, чем просто получение всей строки.

5. @Nik: Опять же, это, вероятно, нецелевое расходование времени, но один из многих способов приблизиться к этому — предоставить механизм, при котором я работаю с полной сущностью, но есть способ указать, что я хочу получить только некоторые столбцы. Действительно, EF вполне может реализовать что-то подобное в какой-то момент. Да, по-видимому, это не может быть сделано с помощью имеющегося у меня кода. Но давайте не будем предполагать, что это то, чего нельзя было сделать.

Ответ №3:

Почему вы вызываете select new вместо того, чтобы возвращать весь объект. Entity framework сможет обновлять свойство, только если оно правильно определено в ресурсах схемы, что, безусловно, не относится к анонимному типу.

Entity framework никогда не сможет определить, к какой таблице и какому полю сопоставлено свойство.

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

1. Как я объяснил в своем вопросе, я не возвращаю весь объект целиком, потому что хочу быть эффективным. Похоже, что эффективность несовместима с EF?