Почему нет метода AddRange/RemoveRange в интерфейсе IDbSet в Entity 6?

В Entity Framework введен метод AddRange. Это отлично подходит для больших вставок, потому что метод DbSet.Add всегда вызывает DetectChanges, что крайне замедляет процесс. Я просто хотел использовать какой-то существующий код на основе интерфейса IDbSet, когда понял, что у него нет метода AddRange. Он существует только в классе DbSet.

Я немного искал Google и нашел это обсуждение - http://forums.asp.net/t/1978828.aspx?Why+is+there+no+AddRange+method+for+System+Data+Entity+IDbSet+T+ - но нет четкого вывода о том, почему метод AddRange не существует в интерфейсе IDbSet.

Является ли это ошибкой или есть какая-то веская причина, чтобы она не была там? Любые идеи?

UPDATE

Здесь https://entityframework.codeplex.com/workitem/2781 Microsoft дала мне ответ:

Это по дизайну. Интерфейсный подход не был хорошим для DbSet, потому что добавление элементов разбивает любые существующие приложения, реализующие интерфейс.

Учитывая, что мы хотим иметь возможность добавлять членов в DbSet, мы поменялись на подход базового класса, где DbSet - это базовый класс, который вы можете напрямую издеваться или наследовать.

Вот несколько ссылок, которые показывают, как использовать DbSet, а не IDbSet:

https://msdn.microsoft.com/en-us/data/dn314429

https://msdn.microsoft.com/en-us/data/dn314431

Ответы

Ответ 1

Из Заметки о создании проекта платформы Entity Framework, 16 мая 2013 г.:

Команда признала возможность нарушения изменений:

Классы DbSet (общие и не общие) наследуют от (общего или не общего) интерфейса IDbSet. IDbSet предназначен только для создания тестовых двойников, будь то эти макеты или подделки.

Однако в EF6 DbSet изменилось четырьмя способами: если отражение в эквивалентных изменениях для IDbSet будет нарушать изменения:

  • Добавлен FindAsync
  • Добавлен AddRange/RemoveRange
  • Локальный тип возврата изменен на DbLocalView (это изменение может быть отменено в любом случае)

Они обсудили кучу потенциальных изменений в деталях, но в конечном итоге решили избежать резкого изменения и "сделать DbSet более макетным":

Решение заключалось в том, чтобы сделать DbSet более макетным. Однако мы не устареваем IDbSet, потому что это создаст работу для тех, кто в настоящее время использует IDbSet, которым не нужно использовать новых членов. Мы добавим рекомендации IDbSet, указывающие, что использование DbSet - это способ перехода на новый код, и в зависимости от обратной связи мы можем выбрать устаревший IDbSet в будущей версии.

И если вы посмотрите на код для IDbSet, они добавили комментарии в верхнюю часть интерфейса:

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

Поэтому, начиная с EF6, новые интерфейсы не будут добавлены в этот интерфейс, и рекомендуется использовать DbSet в качестве базового класса для тестовых удвоений.

Ответ 2

Это определенно не ошибка, и это делается по дизайну. Трудно ответить на этот вопрос, не будучи разработчиком в библиотеке .NET, но я предполагаю, что они хотели, чтобы интерфейсы были простыми. Может быть, кто-то из команды .NET увидит это и включит. Проблемы с обратной совместимостью будут привязаны к реализации интерфейса/конкретного, что также вполне возможно в этом сценарии. Однако это рассуждение, вероятно, не перенесет, почему IList/ICollection не определяет их.

Один из аргументов заключается в том, что добавив AddRange (а также RemoveRange/InsertRange) к интерфейсу, вы вынуждаете разработчика определять эти методы. Интерфейсы должны быть простыми и легко определяемыми. AddRange и т.д. Методы действительно должны существовать только в конкретных коллекциях, где вы можете увидеть улучшения производительности. Например, List может оптимизировать свой AddRange, правильно увеличивая его внутреннюю емкость и т.д. Если простой цикл над элементами и вызов Add могут быть более дорогими (например, с помощью метода расширения).

Они, вероятно, делают это по той же причине, что и IList, ICollection и т.д. не имеют интерфейса AddRange на интерфейсе, а делают конкретные реализации.

Для этого есть обходной путь, а именно абстрагирование как интерфейса, так и конкретного класса с использованием вашего собственного интерфейса/класса. Вы можете указать свой интерфейс AddRange и расширить DBSet/реализовать свой интерфейс в другом классе. Это может работать или не работать в зависимости от того, как вы используете IDBSet/DBSet в своем коде. Хотя, небольшая рефакторинг может сделать эту работу. Что-то вроде этого -

public class MyDbSet<TEntity> : DbSet<TEntity>, IMyDbSet<TEntity> where TEntity : class
{
}

public interface IMyDbSet<TEntity> : IDbSet<TEntity> where TEntity : class
{
    IEnumerable<TEntity> AddRange(IEnumerable<TEntity> items);
}

Теперь вы можете просто использовать IMyDbSet в своем коде. Нет необходимости реализовывать AddRange, поскольку он уже реализован путем расширения DbSet. Внешние методы, которые принимают только IDbSet/DbSet, должны принимать MyDbSet/IMyDbSet.