EF - дублирование записей при вставке
От: α Российская Империя  
Дата: 04.12.15 07:23
Оценка:
Пусть у нас есть такая простая структура классов

public class Порода
{
  [Key]
  public string Название {get;set;}
}

public class Собака
{
  [Key]
  public string Кличка {get;set;}
  public Порода Порода {get;set;}
}


Дальше:

var шарик = new Собака { Кличка = "Шарик", Порода = new Порода { "Овчарка" } };
var тузик = new Собака { Кличка = "Тузик", Порода = new Порода { "Овчарка" } };
context.Собаки.Add(шарик);
context.Собаки.Add(тузик);
context.SaveChanges();


В этом примере в контекст добавляются две собаки одной породы, при этом название породы — первичный ключ.
Но объект класса Порода в контекст добавляется не явно, а через навигационное свойство объекта класса Собака.
Поэтому получается, что в контекст попадают два объекта Порода с одинаковым первичным ключом. Сохранения при этом не происходит по понятным причинам, СУБД ругается на дублирование первичного ключа в таблице "Порода".

Конечно правильнее было бы сделать так: сперва создать объект класса Порода, потом сослаться на него из обоих собак, но я так сделать не могу, потому что по факту эти собаки у меня получаются из десериализации XML такой структуры:

<Собака>
  <Кличка>Шарик</Кличка>
  <Порода>
    <Название>Овчарка</Название>
  </Порода>
</Собака>
<Собака>
  <Кличка>Тузик</Кличка>
  <Порода>
    <Название>Овчарка</Название>
  </Порода>
</Собака>


Пример понятное дело сильно упрощен, по факту в реальной XML-схеме около 300 классов и разрулить все подобные ссылки руками ну чрезвычайно тяжело.

Можно ли как-то подкрутить EF так, чтобы она понимала, что запись с таким-то первичным ключом не нужно вставлять дважды?
Re: EF - дублирование записей при вставке
От: Doc Россия http://andrey.moveax.ru
Дата: 04.12.15 07:40
Оценка:
Здравствуйте, α, Вы писали:

Вы создали 2 объекта (породы) и хотите их сохранить. Логично что будет ошибка.
Насколько я знаю, подкрутить не выйдет, т.к. EF тогда должна уметь мержить объекты по правилам которых она не знает. Ведь не факт что 2 объекта с одним ключом будут 1 в 1. Какой сохранять тогда?

С ходу могу предложить написать вот так:

var breed = new Breed { "Овчарка" };
context.Breeds.Add(breed);

var dog1 = new Dog { Nick = "Шарик", Breed = breed };
context.Dogs.Add(dog1);

var dog2 = new Dog { Nick = "Тузик", Breed = breed };
context.Dogs.Add(dog2);

context.SaveChanges();


PS: И, пожайлуста, не пишите в коде имена классов, переменные кирилицей Глаза ломает
Re[2]: EF - дублирование записей при вставке
От: α Российская Империя  
Дата: 04.12.15 07:51
Оценка:
Здравствуйте, Doc, Вы писали:

Doc>Вы создали 2 объекта (породы) и хотите их сохранить. Логично что будет ошибка.

Doc>Насколько я знаю, подкрутить не выйдет, т.к. EF тогда должна уметь мержить объекты по правилам которых она не знает.

Вот я и хочу ей как-то подсказать.
Из того что есть под рукой в Code First 6:
1) виртуальные методы DbContext.ValidateEntity и DbContext.SaveChanges, которые можно перегрузить
2) возможность принудительного изменения состояния объектов (Attached/Detached etc)
3) возможность явной подгрузки навигационных свойств (DbEnityEntry.Reference("Порода").Load)
4) возможность перезаписывать значения сущности (DbEntityEntry.CurrentValues.SetValues())
5) что ещё?

Можно ли при помощи вот этого набора инструментария убрать дубликаты (это я могу сделать самостоятельно или подсказать контексту как это сделать) и перереференсить ссылки?
Doc>С ходу могу предложить написать вот так:

Я написал, что так в моем случае нереально, слишком развесистая объектная модель

Doc>PS: И, пожайлуста, не пишите в коде имена классов, переменные кирилицей Глаза ломает


Никогда не пишу, но мне что-то показалось, что для примера так нагляднее
Re[3]: EF - дублирование записей при вставке
От: Doc Россия http://andrey.moveax.ru
Дата: 04.12.15 08:04
Оценка:
Здравствуйте, α, Вы писали:

α>Я написал, что так в моем случае нереально, слишком развесистая объектная модель


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

Классов много, поэтому надо как-то универсально. Например — сделать словарь типа <key, T> куда складывать аналоги breed после добавления. Т.е. что-то вроде
— получили объект из источника
— поглядели если ли такой key в словаре
— если да, берем готовое
— если нет, то создаем и добавляем в БД и в словарь

В общем я бы копал именно в сторону работы с моделью.
Re[4]: EF - дублирование записей при вставке
От: α Российская Империя  
Дата: 04.12.15 08:11
Оценка:
Здравствуйте, Doc, Вы писали:

Doc>Здравствуйте, α, Вы писали:


α>>Я написал, что так в моем случае нереально, слишком развесистая объектная модель


Doc>На мой взгляд ту лучше работать над моделью, чем пытаться научить чему-то EF. Просто дальнейшее сопровождение такого кода будет сложнее. А если вдруг потребуется переехать на EF7 ....


Ну если штатными документированными способами — почему бы и нет? )

Doc>Классов много, поэтому надо как-то универсально. Например — сделать словарь типа <key, T> куда складывать аналоги breed после добавления. Т.е. что-то вроде

Doc>В общем я бы копал именно в сторону работы с моделью.

Хм. Я ещё нашел вот такой Extension Method — AddOrUpdate. Похоже что он это умеет делать сам, но я что-то не могу сообразить, как его заставить работать при добавлении графа связанных объектов, вместо Add
Отредактировано 04.12.2015 8:12 α . Предыдущая версия .
Re[5]: EF - дублирование записей при вставке
От: Doc Россия http://andrey.moveax.ru
Дата: 04.12.15 08:16
Оценка:
Здравствуйте, α, Вы писали:

α>Похоже что он это умеет делать сам, но я что-то не могу сообразить, как его заставить работать при добавлении графа связанных объектов, вместо Add


Это просто метод вроде Add, который добавляет сущность или апдейтит, если она уже есть.

Хотя если вдруг найдете ответ — поделитесь. Было бы интересно поглядеть на решение задачи без "хаков"
Re: EF - дублирование записей при вставке
От: namespace  
Дата: 04.12.15 13:35
Оценка:
α>Конечно правильнее было бы сделать так: сперва создать объект класса Порода, потом сослаться на него из обоих собак, но я так сделать не могу, потому что по факту эти собаки у меня получаются из десериализации XML
Не эффективнее(и проще) отправить непосредственно XML в хранимую процедуру и в ней проделать требуемые операции?
Дебажить разве что станет существенно сложнее.
Re[2]: EF - дублирование записей при вставке
От: α Российская Империя  
Дата: 04.12.15 13:45
Оценка:
Здравствуйте, namespace, Вы писали:

α>>Конечно правильнее было бы сделать так: сперва создать объект класса Порода, потом сослаться на него из обоих собак, но я так сделать не могу, потому что по факту эти собаки у меня получаются из десериализации XML

N>Не эффективнее(и проще) отправить непосредственно XML в хранимую процедуру и в ней проделать требуемые операции?
N>Дебажить разве что станет существенно сложнее.

Не, это будет ад. XML-схема включает иерархию со множеством абстрактных классов и наследованием до 6-7 уровней. Я потому EF и взял, что Code First довольно приличную схему базы сам по этой схеме генерирует и сам умеет раскидывать это наследование по таблицам, связанным 1:1
Пока подумываю над тем, как объект развернуть в плоский список сущностей через Reflection и потом вручную затолкать этот список в контекст через AddOrUpdate
Re[3]: EF - дублирование записей при вставке
От: namespace  
Дата: 04.12.15 15:25
Оценка:
α>Пока подумываю над тем, как объект развернуть в плоский список сущностей через Reflection и потом вручную затолкать этот список в контекст через AddOrUpdate
Мда, 300 классов только автоматизировано делать. Только желательно без рефлексии, она медленно работает.
Я делал примерно так:
Собирал список объектов примерно такого типа
(где для идентификатора использовался для новых объектов декремент)
class NodeLine
{
int ?ParentId;
int Id;
XElement Node;
}
Затем для Id<0 — вставка, иначе — обновление. Примитивно, но надежно.
Re: EF - дублирование записей при вставке
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 05.12.15 17:33
Оценка:
Здравствуйте, α, Вы писали:

α>Пусть у нас есть такая простая структура классов


α>
α>public class Порода
α>{
α>  [Key]
α>  public string Название {get;set;}
α>}

α>public class Собака
α>{
α>  [Key]
α>  public string Кличка {get;set;}
α>  public Порода Порода {get;set;}
α>}
α>


α>Дальше:


α>
α>var шарик = new Собака { Кличка = "Шарик", Порода = new Порода { "Овчарка" } };
α>var тузик = new Собака { Кличка = "Тузик", Порода = new Порода { "Овчарка" } };
α>context.Собаки.Add(шарик);
α>context.Собаки.Add(тузик);
α>context.SaveChanges();
α>


α>В этом примере в контекст добавляются две собаки одной породы, при этом название породы — первичный ключ.

Тогда пиши так:

public class Порода
{
  [Key]
  public string Название {get;set;}
}

public class Собака
{
  [Key]
  public string Кличка {get;set;}
  public Порода Порода {get;set;}
  public string ПородаИд  {get;set;}
}

var шарик = new Собака { Кличка = "Шарик", ПородаИд = "Овчарка" };
var тузик = new Собака { Кличка = "Тузик", ПородаИд = "Овчарка" };
context.Собаки.Add(шарик);
context.Собаки.Add(тузик);
context.SaveChanges();


И настраивай мэпинг чтобы ПородаИд был внешним ключом для Порода.
Re[2]: EF - дублирование записей при вставке
От: α Российская Империя  
Дата: 05.12.15 19:10
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>И настраивай мэпинг чтобы ПородаИд был внешним ключом для Порода.


Спасиб, я этот трюк c FK знаю :) но он тут малополезен, так как граф строится из десериализации XML
Я вот сейчас и занимаюсь тем что обходчик модели ваяю, который должен дубликаты удалить и ссылки перестроить

Также смотрю в сторону DataContractSerializer — в нем можно привязаться к событиям и вручную перенастроить ссылки прям на этапе десериализации
Отредактировано 05.12.2015 19:16 α . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.