Entity Framework: предотвращение вставки дубликатов - PullRequest
7 голосов
/ 21 марта 2011

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

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

Обновлен фрагмент кода:

static void Main(string[] args)
    {
        Story story1 = new Story();
        story1.Title = "Introducing the Entity Framework";
        story1.Tags.Add(new Tag { Name = ".net",  });
        story1.Tags.Add(new Tag { Name = "database" });

        Story story2 = new Story();
        story2.Title = "Working with Managed DirectX";
        story2.Tags.Add(new Tag { Name = ".net" });
        story2.Tags.Add(new Tag { Name = "graphics" });

        List<Story> stories = new List<Story>();
        stories.Add(story1);
        stories.Add(story2);

        EfQuestionEntities db = new EfQuestionEntities();

        Category category = (from c in db.Categories
                             where c.Name == "Programming"
                             select c).First();

        foreach (Story story in stories)
        {
            foreach (Tag tag in story.Tags)
            {
                Tag currentTag = tag;
                currentTag = GetTag(tag.Name, category, db);
            }

            db.Stories.AddObject(story);
        }

        db.SaveChanges();
    }

    public static Tag GetTag(string name, Category category, EfQuestionEntities db)
    {
        var dbTag = from t in db.Tags.Include("Category")
                    where t.Name == name
                    select t;

        if (dbTag.Count() > 0)
        {
            return dbTag.First();
        }

        var cachedTag = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added).
            Where(ose => ose.EntitySet == db.Tags.EntitySet).
            Select(ose => ose.Entity).
            Cast<Tag>().Where(x => x.Name == name);

        if (cachedTag.Count() != 0) 
        {
            return cachedTag.First();
        }

        Tag tag = new Tag();
        tag.Name = name;
        tag.Category = category;

        db.Tags.AddObject(tag);

        return tag;
    }

Однако я получаю исключение для объекта с тем же EntityKey, который уже присутствует в ObjectContext.

Кроме того, если я удалю оператор else, я получу исключение о нарушении ограничения FK, поэтому похоже, что его атрибут Category установлен в null.

1 Ответ

4 голосов
/ 21 марта 2011

У меня была такая же проблема с EF. Вот что я в итоге сделал:

  1. Вместо того, чтобы делать story1.Tags.Add(new Tag { Name = ".net", }) самостоятельно, маршрутизируем все Tag создания с помощью вспомогательного метода, подобного этому: story1.Tags.Add(GetTag(".net")).
  2. Метод GetTag проверяет теги в контексте, чтобы увидеть, должен ли он вернуть существующую сущность, как вы. Если это так, он возвращает это.
  3. Если нет существующего объекта, он проверяет ObjectStateManager, чтобы увидеть, есть ли Tag объекты, добавленные в контекст, но еще не записанные в БД. Если он находит соответствующий Tag, он возвращает это.
  4. Если он все еще не нашел Tag, он создает новый Tag, добавляет его в контекст и затем возвращает его.

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

Пример кода, снятого с моего проекта (используется InventoryItem вместо Tag, но вы поняли).

Проверка на шаге 3 выполняется следующим образом:

// Second choice: maybe it's not in the database yet, but it's awaiting insertion?
inventoryItem = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
    .Where(ose => ose.EntitySet == context.InventoryItems.EntitySet)
    .Select(ose => ose.Entity)
    .Cast<InventoryItem>()
    .Where(equalityPredicate.Compile())
    .SingleOrDefault();

if (inventoryItem != null) {
    return inventoryItem;
}

Если Tag не найден на шаге 3, вот код для шага 4:

inventoryItem = new InventoryItem();
context.InventoryItems.AddObject(inventoryItem);
return inventoryItem;

Обновление:

Следует использовать так:

Story story1 = new Story();
story1.Title = "Introducing the Entity Framework";
story1.Tags.Add(GetTag(".net", category, db));
story1.Tags.Add(GetTag("database", category, db));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...