将查找展平为单个linq表达式的循环

时间:2011-10-16 21:57:30

标签: c# .net linq entity-framework

Type member support in LINQ-to-Entities?中,我试图在LINQ中声明要查询的类属性,但遇到了一些问题。在这里,我将在实现中布置代码,希望能够将其转换为查询。

我有一个班级Quiz,其中包含Question个集合,每个集合都根据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;
    }

它在加入帐户失败,抱怨:

  

join子句中某个表达式的类型不正确。   在调用'GroupJoin'时,类型推断失败

我还在努力解决这个问题。

*更新I *

啊。傻我。

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

又一个障碍:

  

指定的LINQ表达式包含对查询的引用   与不同的背景相关联。

这是因为我为最大查找创建的新容器;所以我想重新考虑这个因素:

    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 个答案:

答案 0 :(得分:5)

将数据库对象与域对象耦合时,遇到的问题很常见。出于这个原因,最好有一组代表你的域的单独的类和一组代表你的数据库并用于数据库CRUD的类。可以预期属性重叠,但这种方法可以更好地控制应用程序,并使数据库与业务逻辑分离。

关闭测验的想法属于您的域(业务逻辑)。您的DAL(数据访问层)应负责加入所有必要的表,以便在您返回测验时,确定是否已关闭所需的所有信息。然后,您的域/服务/业务层应创建正确填充IsClosed属性的域对象,以便在UI层(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 :(得分:1)

回复:你的评论

  

对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)

问题是加入Level 对象会导致EF做一些隐藏的魔法 - 找到数据库中的对象并在那里执行连接。如果你告诉它加入一个简单的类型,那么它应该将值发送到数据库中SELECT,检索对象并将它们拼接在应用程序层中。