Подзапросы разрыва интерфейса и хранилища абстракций в Entity Framework 4.1 DbContext API?
Цель:
Я пытаюсь использовать новый API Entity Framework 4.1 DbContext (используя Database First с новым ADO.NET DbContext Generator для классов POCO) и обеспечить уровень абстракции с использованием базовых общих хранилищ.
Эта проблема:
Если я пытаюсь использовать подзапрос с моими репозиториями, EF не может завершить перевод и выдает ошибку:
System.NotSupportedException: LINQ to Entities не распознает метод 'System.Linq.IQueryable`1[EntityFramework41Test.Data.Entity.Table2] Query()', и этот метод нельзя преобразовать в выражение хранилища.
Я успешно использовал этот дизайн со старым 4.0 ObjectContext в прошлом, но я хотел бы использовать новый API. Тот же самый подход также терпит неудачу с 4.0 ObjectContext API (протестировано с использованием сгенерированных объектов POCO).
Примечание: я не думаю, что реалистично размещать сотни строк кода, но у меня есть пример решения с проектом ASP.NET MVC 3, использующим SQL Server CE 4.0, и базовый проект модульного тестирования, демонстрирующий результаты различных подходов, которые могут быть загружены или по электронной почте, если это помогает.
Интерфейс репозитория, который я использую, очень прост:
public interface IRepository<TEntity> : IDisposable where TEntity : class, ITestEntity
{
TEntity GetById(int id);
IQueryable<TEntity> Query();
void Add(TEntity entity);
void Remove(TEntity entity);
void Attach(TEntity entity);
}
Контекстный интерфейс еще проще:
public interface ITestDbContext : IDisposable
{
IDbSet<TEntity> Set<TEntity>() where T: class, ITestEntity;
void Commit();
}
Вот пример использования, которое не работает, используя интерфейсы для репозитория и экземпляров контекста:
using (ITestDbContext context = new TestDbContext())
using (IRepository<Table1> table1Repository = new Repository<Table1>(context))
using (IRepository<Table2> table2Repository = new Repository<Table2>(context))
{
// throws a NotSupportedException
var results = table1Repository.Query()
.Select(t1 => new
{
T1 = t1,
HasMatches = table2Repository.Query()
.Any(t2 => t2.Table1Id == t1.Id)
})
.ToList();
}
Код выше - это подход, который я хотел бы использовать. Конкретные классы в конечном итоге будут введены.
Пожалуйста, не обращайте внимания на тот факт, что есть лучший способ написать этот конкретный запрос, чем использование подзапроса. Я специально упростил код, чтобы сосредоточиться на реальной проблеме: EF не будет переводить запрос.
Сохранение "внутреннего" репозитория Метод Query () приводит к тому, что локальная переменная действительно работает, но не идеальна, так как вам придется постоянно это делать.
using (ITestDbContext context = new TestDbContext())
using (IRepository<Table1> table1Repository = new Repository<Table1>(context))
using (IRepository<Table2> table2Repository = new Repository<Table2>(context))
{
var table2RepositoryQuery = table2Repository.Query();
// this time, it works!
var results = table1Repository.Query()
.Select(t1 => new
{
T1 = t1,
HasMatches = table2RepositoryQuery
.Any(t2 => t2.Table1Id == t1.Id)
})
.ToList();
}
Я также заметил, что некоторые другие подходы ломаются или преуспевают, например, игнорируя репозитории и вызывая TestDbContext.Set<TEntity>()
работает но ITestDbContext.Set<TEntity>()
не переведу Изменение определения ITestDbContext.Set<TEntity>()
возвращать DbSet<TEntity>
вместо IDbSet<TEntity>
по-прежнему не удается.
Редактировать:
Я не думаю, что это возможно без некоторого перехвата запросов и перевода. Если я найду решение в будущем, я обязательно поделюсь им.
2 ответа
У меня нет опыта работы с EF, но, основываясь на работе с NHibernate и его развивающейся поддержкой LINQ, я подозреваю, что ответ на ваш вопрос - тот, который вам не понравится - похоже, этой конкретной конструкции нет (пока?) поддерживается поставщиком EF LINQ, и вам необходимо изменить свой запрос.
Вы должны включить все свойства связанных объектов на вашем DbSet<SomeEntity>
прежде чем создать IQuerable из него.
public class SomeEntity
{
public Guid Id { get; set; }
public virtual SomeOtherEntity Other { get; set; }
}
public class Repository
{
public IQueryable<SomeEntity> Query()
{
_context.Set<SomeEntity>().Include("Other").AsQueryable();
}
}
Вы можете предоставить это вашему методу Query, используя func или что-то еще. Используйте свое творчество;)