Подзапросы прерывания интерфейса и репозитория в Entity Framework 4.1 DbContext API? - PullRequest
10 голосов
/ 13 мая 2011

Цель:
Я пытаюсь использовать новый 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. Тот же подход не работает и с API 4.0 ObjectContext (протестировано с использованием сгенерированного 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> по-прежнему не удается.

Edit:
Я не думаю, что это возможно без некоторого перехвата запросов и перевода. Если я найду решение в будущем, я обязательно поделюсь им.

Ответы [ 2 ]

1 голос
/ 16 июля 2011

Вам необходимо включить все свойства в связанные объекты на вашем 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 с помощью функции или чего-то еще.Используйте свое творчество;)

1 голос
/ 07 июня 2011

У меня нет опыта работы с EF, но, основываясь на работе с NHibernate и его развивающейся поддержкой LINQ, я подозреваю, что ответ на ваш вопрос - тот, который вам не понравится - похоже, эта конкретная конструкция не(еще?) поддерживается поставщиком EF LINQ, и вам нужно изменить свой запрос.

...