Цель:
Я пытаюсь использовать новый API-интерфейс Entity Framework 4.1 DbContext (используя Database First с новым генератором DbContext ADO.NET для классов 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 и базового проекта unit test, демонстрирующего результат различных подходов, которые могут быть загружены или отправлены по электронной почте, если это поможет.
Интерфейс репозитория, который я использую, прост:
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>
все еще не выполняется.
Edit:
Я не думаю, что это возможно без какого-либо перехвата запросов и перевода. Если я найду решение в будущем, я обязательно поделюсь им.