Сведение цикла с поисками в одно выражение linq - PullRequest
2 голосов
/ 17 октября 2011

В Поддержка членов типа в LINQ-to-Entities? Я пытался объявить свойство класса для запроса в LINQ, что привело к возникновению некоторых проблем.Здесь я выложу код внутри реализации в надежде на некоторую помощь для преобразования его в запрос.

У меня есть класс Quiz, который содержит коллекцию Question s, каждый из которых классифицированв соответствии с QuestionLevel ... мне нужно определить, является ли тест «открытым» или «закрытым», что осуществляется посредством внешнего объединения на уровнях вопросов и подсчета вопросов на каждом уровне по сравнению стаблица максимальных значений.Вот код, дословно:

public partial class Quiz
{
    public bool IsClosed
    {
        get
        {
            // if quiz has no questions, it's open
            if (this.Questions.Count() == 0) return false;

            // get a new handle to the EF container to do a query for max values
            using (EFContainer db = new EFContainer())
            {
                // we get a dictionary of LevelName/number
                Dictionary<string, int> max = db.Registry
                    .Where(x => x.Domain == "Quiz")
                    .ToDictionary(x => x.Key, x => Convert.ToInt32(x.Value));
                // count the number of questions in each level, comparing to the maxima
                // if any of them are less, the quiz is "open"
                foreach (QuestionLevel ql in db.QuestionLevels)
                {
                    if (this.Questions.Where(x => x.Level == ql).Count() < max["Q:Max:" + ql.Name])
                        return false;
                }
            }
            // the quiz is closed
            return true;
        }
    }
 }

, так что вот моя еще не работающая попытка:

    public static IQueryable<Quiz> WhereIsOpen(this IQueryable<Quiz> query)
    {
        EFContainer db = new EFContainer();
        return from ql in db.QuestionLevels
               join q in query on ql equals q.Questions.Select(x => x.Level)
               into qs
               from q in qs.DefaultIfEmpty()
               where q.Questions.Count() < db.Registry
                    .Where(x => x.Domain == "Quiz")
                    .Where(x => x.Key == "Q:Max" + ql.Name)
                    .Select(x => Convert.ToInt32(x.Value))
               select q;
    }

не удается выполнить учетную запись при объединении, жалуясь:

Неверно указан тип одного из выражений в предложении соединения.Ошибка вывода типа при вызове 'GroupJoin'

Я все еще пытаюсь выяснить это.

* обновление I *

ах.глупое я.

   join q in query on ql equals q.Questions.Select(x => x.Level).Single()

еще один блокпост:

Указанное выражение LINQ содержит ссылки на запросы, связанные с различными контекстами.

thisиз-за нового контейнера, который я создаю для максимального поиска;поэтому я подумал перефакторировать вот так:

    public static IQueryable<Quiz> WhereIsOpen(this IQueryable<Quiz> query)
    {
        EFContainer db = new EFContainer();
        IEnumerable<QuestionLevel> QuestionLevels = db.QuestionLevels.ToList();
        Dictionary<string, int> max = db.Registry
                .Where(x => x.Domain == "Quiz")
                .ToDictionary(x => x.Key, x => Convert.ToInt32(x.Value));
        return from ql in QuestionLevels
               join q in query on ql equals q.Questions.Select(x => x.Level).Single()
               into qs
               from q in qs.DefaultIfEmpty()
               where q.Questions.Count() < max["Q:Max:" + ql.Name]
               select q;
    }

но я не могу заставить выражение скомпилировать ... мне нужно преобразовать QuestionLevels в IQueryable (но приведение не работает, производяисключения времени выполнения).

* обновление II *

Я нашел решение проблемы приведения, но теперь я вернулся к исключению "разные контексты".grr ...

return from ql in QuestionLevels.AsQueryable()

* обновление (предложение Кирка) *

, так что теперь у меня есть это, которое компилируется, но генерирует исключение времени выполнения:

public static IQueryable<Quiz> WhereIsOpen(this IQueryable<Quiz> query)
{
    EFContainer db = new EFContainer();
    IEnumerable<string> QuestionLevels = db.QuestionLevels.Select(x => x.Name).ToList();
    Dictionary<string, int> max = db.Registry
            .Where(x => x.Domain == "Quiz")
            .ToDictionary(x => x.Key, x => Convert.ToInt32(x.Value));
    return from ql in QuestionLevels.AsQueryable()
           join q in query on ql equals q.Questions.Select(x => x.Level.Name).Single()
           into qs
           from q in qs.DefaultIfEmpty()
           where q.Questions.Count() < max["Q:Max:" + ql]
           select q;
}

, который я затем называю так:

List<Product> p = db.Quizes.WhereIsOpen().Select(x => x.Component.Product).ToList();

с результирующим исключением:

Этот метод поддерживает инфраструктуру LINQ to Entities и не предназначен дляиспользовать прямо из вашего кода.

Ответы [ 2 ]

5 голосов
/ 17 октября 2011

Проблемы, с которыми вы сталкиваетесь, являются общими, когда вы связываете объекты базы данных с объектами вашего домена. Именно по этой причине хорошо иметь отдельный набор классов, которые представляют ваш домен, и отдельный набор классов, которые представляют вашу базу данных и используются для базы данных CRUD. Можно ожидать совпадения свойств, но этот подход обеспечивает больший контроль над вашим приложением и отделяет вашу базу данных от вашей бизнес-логики.

Идея о том, что тест закрыт, принадлежит вашему домену (бизнес-логика). Ваш DAL (уровень доступа к данным) должен отвечать за объединение всех необходимых таблиц, чтобы при возврате викторины была доступна вся информация, необходимая для определения того, закрыта она или нет. Затем ваш домен / сервис / бизнес-уровень должен создать объект домена с правильно заполненным свойством IsClosed, чтобы на вашем уровне пользовательского интерфейса (MVC) вы могли легко получить к нему доступ.

Я вижу, что вы обращаетесь к контексту базы данных напрямую, я бы предостерегал против этого и рекомендовал бы вам изучить использование инфраструктуры DI / IoC (отлично подходит Ninject), однако я собираюсь получить доступ к контексту базы данных напрямую также

Используйте этот класс в ваших представлениях / контроллерах:

public class QuizDomainObject 
{
    public int Id {get; set;}
    public bool IsClosed {get; set;}
    // all other properties
}

Контроллер:

public class QuizController : Controller 
{
    public ActionResult View(int id)
    {
        // using a DI/IoC container is the 
        // preferred method instead of 
        // manually creating a service
        var quizService = new QuizService(); 
        QuizDomainObject quiz = quizService.GetQuiz(id);

        return View(quiz);
    }
}

Уровень обслуживания / бизнеса:

public class QuizService
{
    public QuizDomainObject GetQuiz(int id)
    {
        // using a DI/IoC container is the 
        // preferred method instead of 
        // access the datacontext directly
        using (EFContainer db = new EFContainer())
        {
            Dictionary<string, int> max = db.Registry
                .Where(x => x.Domain == "Quiz")
                .ToDictionary(x => x.Key, x => Convert.ToInt32(x.Value));

            var quiz = from q in db.Quizes
                       where q.Id equals id
                       select new QuizDomainObject()
                       {
                            Id = q.Id,
                            // all other propeties,

                            // I'm still unclear about the structure of your  
                            // database and how it interlates, you'll need 
                            // to figure out the query correctly here
                            IsClosed =  from q in ....
                       };


            return quiz;
        }
    }
}
1 голос
/ 17 октября 2011

Re: ваш комментарий

Соединение с QuestionLevels заставляет его думать, что существует два контекста ... но на самом деле этого не должно быть, потому что QuestionLevels должен содержать объекты в памяти

Я считаю, что если вы объедините простые типы, а не объекты, вы избежите этой проблемы. Следующее может работать для вас:

return from ql in QuestionLevels                
       join q in query 
       on ql.LevelId equals q.Questions.Select(x => x.Level).Single().LevelId
       into qs 

(и если это не сработает, создайте несколько анонимных типов и присоединитесь к Id)

Проблема в том, что объединение на объектах уровня заставляет EF совершать магию под прикрытием - найти объекты в базе данных и выполнить соединение там. Если вы скажете ему присоединиться к простому типу, то он должен отправить значения в базу данных для SELECT, извлечь объекты и соединить их обратно на уровне приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...