【问题标题】:NHibernate - Many to Many Query using Junction/Joiner TableNHibernate - 使用连接/连接表的多对多查询
【发布时间】:2023-04-06 12:21:02
【问题描述】:

我在这里找到了非常相似的问题,但没有一个与我正在寻找的完全匹配。我发现的两个最接近的线程是(是的,它们是不同的线程):

NHibernate many-to-many criteria(1)

NHibernate many-to-many criteria(2)

但是,我认为这两个都使用直接的多对多关系。我实际上是通过与联结表建立两个一对多关系来模拟多对多关系,这是非常标准的做法。这是我的 NHibernate 映射:

文件:

<class name="Files" table="files">
  <id name="id">
    <generator class="identity" />
  </id>
  <property name="name" />

  <bag name="files_attrs" table="files_attrs" lazy="true">
    <key column="file_id" />
    <one-to-many class="Files_Attrs" />
  </bag>
</class>

属性:

<class name="Attrs" table="attrs">
  <id name="id">
    <generator class="identity" />
  </id>
  <property name="name" />
  <property name="value" />

  <bag name="files_attrs" table="files_attrs" lazy="true">
    <key column="attr_id" />
    <one-to-many class="Files_Attrs" />
  </bag>
</class>

加入者:

<class name="Files_Attrs" table="files_attrs">
  <id name ="id">
    <generator class="identity" />
  </id>
  <many-to-one name="file" cascade="all" column="file_id" />
  <many-to-one name="attr" cascade="all" column="attr_id" />
</class>

所以我的问题与上面的第二个链接完全相同,但使用连接表完成。所以:

给定一组属性 ID,我希望运行一个查询,为我提供具有所有这些匹配属性的文件。我可以轻松地为集合中的每个属性 ID 运行“n”个查询,并比较每个列表中出现的每个列表中的文件 ID,但我觉得应该有一种更简单的方法可以通过一个查询一次完成所有操作。

例子:

File      | Attributes
----------+-----------------------------------------------------
foo.txt   | (mode = read-only,                    view = visible)
bar.txt   | (mode = read-write, security = all,   view = visible)
duck.txt  | (mode = read-only,                    view = hidden)
goose.txt | (more = read-only,  security = owner, view = visible)

鉴于这些属性:mode = read-onlyview = visible,我只想返回 foo.txtgoose.txt

谁能帮我解决这个问题?谢谢。

【问题讨论】:

  • 您是否尝试过来自 #2 的已接受答案?即使您的关系实施方式不同,它似乎也应该有效。

标签: c# sql nhibernate many-to-many junction-table


【解决方案1】:

我不确定这是不是你需要的:

<bag name="files_attrs" table="files_attrs" lazy="true" where="something like '%mode = read-only%' and something like '%view = visible%'">
    <key column="attr_id" />
    <one-to-many class="Files_Attrs" />
</bag>

其中something 是属性,或者是要过滤的数据所在的列。

试试这个查询:

Files fAlias = null;
Attrs aAlias = null;

var disjunction = new Disjunction();

disjunction.Add(Restrictions.On(() => aAlias.value)
    .IsLike("mode = read-only", MatchMode.Anywhere));
disjunction.Add(Restrictions.On(() => aAlias.value)
    .IsLike("view = visible", MatchMode.Anywhere));

var subquery = QueryOver.Of<Files_Attrs>
    .Inner.JoinAlias(x => x.file, () => fAlias)
    .Inner.JoinAlias(x => x.attr, () => aAlias)
    .Where(disjunction)
    .Select(() => fAlias);

var files = session.QueryOver<Files>
    .WithSubquery.WhereExists(subquery)
    .List();

【讨论】:

  • 嗨,纳杰拉。感谢您的回复。实际上,我主要在寻找如何编写 NHibernate 查询来实现我正在寻找的东西。我已经定义了映射、表和对象,我只是不确定是否有一个简单的查询可以返回我在问题末尾的示例中寻找的响应。
  • 我在我的问题中添加了一个查询。
  • 谢谢,我不希望它看起来好像我没有看到你的回复,但我一直在尝试解决另一个问题,该问题目前优先于这个问题。当我回到这个问题时,我会看看这个并尝试一下并写一个新的评论!所以提前感谢您到目前为止的帮助! ^_^
【解决方案2】:

实现此目的的一种方法是创建尽可能多的由 AND 连接的子查询,因为必须找到许多属性/与搜索的文件相关

我正在搜索名称/值

第一个解决方案适用于上层的名称/值对。即用户选择的模式为只读... (第二个会更容易一些,期望我们已经拥有搜索属性的 ID)

// Below I am using C# properties, which I guess are correct
// based on the mapping. Naming convention is more Java (camel)
// but this should work with above mapping 
// (also - class name Contact, not File)

Files file = null; // this is an alias used below

// here the attributes collection represents search filter
// ... settings for which is user looking for
var attributes = new List<Attrs>
{
    new Attrs{ name = "mode", value = "read-only" },
    new Attrs{ name = "view", value = "visible" }
};

// Let's start with definition of the outer/top query
// which will return all files, which do meet all filter requirements
var query = session.QueryOver<Files>(() => file);

下一步,我们将遍历属性,即filters集合

// here we will take each attribute and create a subquery
// all these subqueries, will be joined with AND
// so only these files, which do have all attributes, will be selected
foreach (var attr in attributes)
{
    // create the subquery, returning the FileId
    Attrs attribute = null;
    var subQueryForAttribute = QueryOver.Of<Files_Attrs>()
            .JoinQueryOver(fa => fa.attr, () => attribute)
            .Select(x => x.file.id)
            ;

    // now, take name and value
    var name = attr.name;
    var value = attr.value;

    // and convert them into where condition
    subQueryForAttribute.Where(() => attribute.name == name);
    subQueryForAttribute.Where(() => attribute.value == value);

    // finally, add this subquery as a restriction to the top level query
    query.WithSubquery
        .WhereProperty(() => file.id)
        .In(subQueryForAttribute);
}

现在我们有一个查询,它已准备好支持分页 - 因为我们正在处理文件的平面结构。所以我们可以根据需要使用 Take 和 skip 来获取搜索到的文件列表

// query.Take(25);
// query.Skip(100);

var list = query.List<Files>();

这是一个查询,会产生这样的 SELECT

SELECT ...
FROM files
WHERE id IN (SELECT file_Id FROM files_attrs 
                              INNER JOIN attrs ON attrs.id = file_attrs.attr_id
                            WHERE name = 'mode' AND value = 'read-only' )
  AND id IN (SELECT file_Id FROM files_attrs 
                              INNER JOIN attrs ON attrs.id = file_attrs.attr_id
                            WHERE name = 'view' AND value = 'visible' )

II 按属性 ID 搜索

第二种解决方案具有更简单的起始条件,而不是属性(名称和值),我们已经有了它们的 ID(引用问题:)

给定一组属性 ID,我希望运行一个查询,为我提供具有所有这些匹配属性的文件。

// Below I am using C# properties, which I guess are correct
// based on the mapping. Naming convention is more Java (camel)
// but this should work with above mapping 
// (also - class name Files, not File)

Files file = null; // this is an alias used below

// here the attributeIds collection represents attributes to be found
var attributeIds = new List<int> { 1, 4, 5 };

// Let's again start with definition of the outer/top query
// which will return all files, which do meet all filter requirements
var query = session.QueryOver<Files>(() => file);

接下来是通过一组已知 ID 的迭代,这些 ID 必须作为关系存在 (所有这些)

// here we will take each attribute and create a subquery
// all these subqueries, will be joined with AND
// so only these files, which do have all attributes, will be selected
foreach (var attrId in attributeIds)
{
    // create the subquery, returning the Files.id
    var subQueryForAttribute = QueryOver.Of<Files_Attrs>()
            // no need to join, all the stuff is in the pairing table
            .Select(x => x.file.id)
            ;
    var id = attrId; // local variable
    // and convert them into where condition
    subQueryForAttribute.Where(pair => pair.attr.id == id);

    // finally, add this subquery as a restriction to the top level query
    query.WithSubquery
        .WhereProperty(() => file.id)
        .In(subQueryForAttribute);
}

var list = query.List<Files>();

IDS 已知的解决方案更简单一些(SQL 语句中需要的表更少)

注意:不得不说:很高兴看到您引入了many-to-oneone-to-many,而不是多对多。我个人会说,这个例子正好说明了它可以带来多大的利润......即使使用复杂的过滤器也能搜索

一些链接,展示QueryOver的力量:Query on HasMany reference,以及为什么不使用many-to-many映射的一些充分理由:many-to-many with extra columns nhibernate

【讨论】:

  • 谢谢,我不希望它看起来好像我没有看到你的回复,但我一直在尝试解决另一个问题,该问题目前优先于这个问题。当我回到这个问题时,我会看看这个并尝试一下并写一个新的评论!所以提前感谢您到目前为止的帮助! ^_^
  • Clear :) 如果你稍后告诉我会很好;)这种技术 - 在队列中添加所有限制(WHERAND) - 即使使用子查询 (如上所示)。我们使用它在地址中按城市搜索人,按标签名称搜索文章...祝 NHibernate 好运 ;)
  • 嗨 Radim,我实际上是从 Stackoverflow 上的大量关于该主题的阅读中选择了多对一和一对多选项的路径,我相信大多数论点来自你在这里的帖子。 ^_^ 无论如何,我正在尝试你上面的代码,但我在这一行得到一个编译错误:var query = session.QueryOver&lt;Files&gt;(() =&gt; file); 错误是:"Cannot convert lambda expression to type 'string' because it is not a delegate type" 我也得到错误"The name 'file' does not exist in the current context" 我应该是首先在某个地方定义它?
  • 我重新测试了所有部分(在我的示例中,名称略有不同,但结构相同),并修复了所有小问题。如果代码与上面的 xml 映射 1:1 匹配,则该代码应该是可编译的。请告诉我。我相信你会看到你想要的 ;) ... 没有many-to-many ;)
  • Radim,您的代码绝对有效。非常感谢。我很感激,伙计!我理解它的一部分,但我还没有 100% 理解它。我肯定会努力确保我理解所有这些(老实说,我不擅长 SQL),但至少现在这将帮助我完成我的最后期限。 ^_^ 之后,我一定会回去重新处理这一切。再次,非常感谢,Radim。真的很感激!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-26
  • 1970-01-01
  • 2011-08-23
  • 2015-02-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多