【问题标题】:Is it possible to query all objects that have one or more possible children using NHibernate?是否可以使用 NHibernate 查询具有一个或多个可能子对象的所有对象?
【发布时间】:2014-02-13 10:26:01
【问题描述】:

我有一个名为Recipes 的表,其中每行包含一个食谱。我还有一张名为RecipeIngredients 的表,其中包含特定配方使用的一种成分。因此,每个Recipe 行都有一个或多个子RecipeIngredients 行。

我要做的是创建一个查询来查找包含所需成分列表中任何成分的所有食谱。例如,向我展示所有使用面粉、鸡蛋或香蕉的食谱。

SQL 看起来像这样:

SELECT * FROM Recipes r
   WHERE EXISTS (select 1 from RecipeIngredients where RecipeId = r.RecipeId and IngredientId = ANY (5, 10, 15) limit 1);

但是,我很难弄清楚如何将其表达为 LINQ 查询,或使用 .QueryOver<T> 方法。我不想在 SQL 中硬编码,因为这需要与数据库无关,我希望配置的 NHibernate 方言生成正确的代码。

有什么想法吗?

【问题讨论】:

    标签: c# sql linq nhibernate fluent-nhibernate


    【解决方案1】:

    NHibernate 支持这种 SQL 语句,称为

    语法如下:

    var session = ...// get a ISession 
    
    Reciepe reciepe = null; // this will be a reference to parent
    
    // the SELECT inside of EXISTS
    var subquery = QueryOver.Of<ReciepeIngredient>()
        // The PARENT handling here
        // the filter, to find only related ingredients
        .Where(item => item.ReciepeId == reciepe.ID)
        .Where(Restrictions.In("ID", new[] { 5, 10, 15 }))
        // Select clause
        .Select(ing => ing.ID)
    
        ;
    

    有了上面的子查询,我们就可以这样使用了

    // the '() => reciepe' setting is essential here, it represents parent in a subquery
    var query = session.QueryOver<Reciepe>(() => reciepe);
    
    query.WithSubquery
        // our EXISTS (...
        .WhereExists(subquery);
    
    var list = query
        .List<Reciepe>();
    

    注意:让我们在这里检查更深入的子查询使用情况Query on HasMany reference

    【讨论】:

    • 太棒了!明天我会搞砸这个,看看我能不能让它工作..谢谢!
    • 太棒了! NHibernate 是很棒的工具,先生 ;)。什么更重要?如果不是最好的方法,您已经选择纠正。使用子查询,您将过滤根实体(reciepe),而它仍然保持不变,而不是相乘。很多人尝试使用 fetching ... 以笛卡尔积结束(在获取集合的情况下)。这种方法(子查询)开箱即用,可用于分页(Take()、Skip())。太好了……
    • 搞定了!我在下面添加了我自己的答案以及我发现的更多细节。但是,您的方法效果很好,可以生成快速高效的 SQL。
    【解决方案2】:

    更多细节:

    Radim 的回答被证明是表达子查询的最佳方式,但是有一些陷阱让我花了一段时间才弄清楚。因此,我也会发布答案以填写详细信息。

    首先,一行:

    .Where(Restrictions.In("ID", new[] { 5, 10, 15 }))
    
    如果ID 引用实体本身,

    实际上不起作用。换句话说:

    .Where(Restrictions.In("Ingredient", arrayOfIds))
    

    将抛出一个非常混乱的空引用异常,因为 Ingredient 字段映射到 Ingredients 对象。使用"IngredientId" 也不起作用。在这种情况下,你必须使用这个:

    .Where(Restrictions.In("Ingredient", arrayOfIds
       .Select(id => new Ingredients(id)).ToArray()))
    

    将 ID 数组转换为 Ingredients 对象数组。之后,事情就开始起作用了。

    我还发现了一个简单的性能改进,它使查询的运行速度明显更快,至少在 PostgreSQL 上是这样。如果您将子查询更改为:

    WHERE 存在(SELECT RecipeIngredientId FROM recipeingredients WHERE RecipeId = r.RecipeId 和 IngredientId in (:p0, :p1))

    收件人:

    WHERE 存在(SELECT RecipeIngredientId FROM recipeingredients WHERE RecipeId = r.RecipeId 和 IngredientId in (:p0, :p1) LIMIT 1)

    它只需要检查嵌套查询中的一行。查询对我来说运行速度大约是原来的两倍。这很容易表达:

    var subquery = QueryOver.Of<RecipeIngredients>()
       .Where(item => item.Recipe.RecipeId == recipe.RecipeId)
       .Where(Restrictions.In("Ingredient", allowedIngs))
       .Select(i => i.RecipeIngredientId).Take(1);
    

    希望这会有所帮助!

    【讨论】:

    • 我非常喜欢。很高兴看到你是如何使用那个 Diamond - NHibernate
    【解决方案3】:

    试试这个 Linq 查询:

            recipes.Where(r => r.RecipeIngredients.Any(i => new long[]{5, 10, 15}.Contains(i.Id)));
    

    【讨论】:

    • 谢谢!如果我运行它,我得到: NHibernate.dll 中发生了“System.Exception”类型的未处理异常附加信息:无法识别的方法调用:System.Linq.Enumerable:Boolean Any[TSource](System.Collections.Generic.IEnumerable@ 987654322@2[TSource,System.Boolean])
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多