Почему нет метода 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.