【问题标题】:Using Contains() in a Realm query在领域查询中使用 Contains()
【发布时间】:2020-05-07 22:37:03
【问题描述】:

假设我们有一个领域结果

RealmDb.All<Entry>();

然后我想使用尚不支持的技术对这些结果进行一些搜索,例如函数返回上的 StartsWith 或未映射到领域等的属性,所以我得到一个子集

IEnumerable<Entry> subset = bgHaystack;
var results = subset.Where(entry => entry.Content.ToLower().StartsWith(needle));

为了以某种方式将这些作为 RealmResults 的一部分,我像这样提取条目 id:

            List<int> Ids = new List<int>();
            foreach (Entry entry in entries)
            {
                Ids.Add(entry.Id);
            }
            return Ids;

最后我想返回 RealmResults 的子集(不是 IEnumerable),其中只有那些包含这些 id 的条目,我该怎么做? IDE 说不支持 Contains 方法。

我可以为此使用某种谓词或比较器吗?

Entry 是我的模型类

using System.ComponentModel.DataAnnotations.Schema;
using Realms;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System;

namespace Data.Models
{
    [Table("entry")]
    public class Entry : RealmObject
    {
        public class EntryType
        {
            public const byte Word = 1;
            public const byte Phrase = 2;
            public const byte Text = 3;
        };

        [Key]
        [PrimaryKey]
        [Column("entry_id")]
        public int Id { get; set; }

        [Column("user_id")]
        public int UserId { get; set; }

        [Column("source_id")]
        public int SourceId { get; set; }

        [Indexed]
        [Column("type")]
        public byte Type { get; set; }

        [Column("rate")]
        public int Rate { get; set; }

        [Column("created_at")]
        public string CreatedAt { get; set; }

        [Column("updated_at")]
        public string UpdatedAt { get; set; }

        [NotMapped]
        public Phrase Phrase { get; set; }

        [NotMapped]
        public Word Word { get; set; }

        [NotMapped]
        public Text Text { get; set; }

        [NotMapped]
        public IList<Translation> Translations { get; }

        [NotMapped]
        public string Content
        {
            get {
                switch (Type)
                {
                    case EntryType.Phrase:
                        return Phrase?.Content;
                    case EntryType.Word:
                        return Word?.Content;
                    case EntryType.Text:
                        return Text?.Content;
                }
                return "";
            }
        }
    }
}

【问题讨论】:

  • 我不知道你想要什么,你能解释一下吗?我在代码中没有看到.Contains
  • “我想返回 RealmResults 的子集(不是 IEnumerable)”。你希望它们是什么类型的?
  • 什么是entries
  • @RufusL 条目是我的模型,我只是将它添加到类中
  • 我需要它们作为领域对象,但经过过滤,所以问题是如何过滤领域对象而不将它们转换为其他数据类型,或者如果转换,如何转换回来

标签: c# linq realm


【解决方案1】:

根据documentation,Realm .NET 支持 LINQ,因此很有希望。在您的具体示例中,您指出不支持 StartsWith,但我在上面的页面上看到了这一点,特别是 here

现在,您的示例清楚地表明 Entry 是 RealmObject,因此不清楚您可能从哪里获得 RealmResult(他们在该页面上的文档也没有提到 RealmResult)。具体来说,home page 表示您实际上只会使用RealmRealmObjectTransaction,所以我将假设您的意思是您需要一个结果@987654334 @根据他们的例子。

按照您目前设置数据对象的方式,您宁愿像现在这样称呼它(但如果我可以建议稍微简化一下:

var entries = RealmDb.All<Entry>().ToList();
var results = entries.Where(entry => entry.Content.ToLower().StartsWith(needle));
var ids = results.Select(a => a.Id).ToList();

现在,您的大问题是将第 2 行中的过滤谓词与第 1 行的结尾组合起来:Content 本身被标记为[NotMapped] 属性。再次按documentation

作为一般规则,您只能创建具有以下条件的谓词 依赖 Realm 中的数据。想象一个类

class Person : RealmObject
{
    // Persisted properties
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // Non-persisted property
    public string FullName => FirstName + " " + LastName;
}

有了这个类,您可以创建具有适用于的条件的查询 FirstNameLastName 属性,但不适用于 FullName 财产。同样,具有[Ignored] 属性的属性不能 用过。

因为您使用的是[NotMapped],所以我必须相信它的行为类似于[Ignored],此外,因为它只是一个计算值,它不是 Realm 能够处理的东西查询的一部分 - 它根本不知道它,因为您没有将它映射到 Realm 存储的信息。相反,当您实际获得要枚举的 Entry 对象的实例时,您必须计算 Content 属性。

同样,我预计您会在从 PhraseWordText 提取值时遇到问题,因为它们也没有被映射,因此不会存储在 Realm 中的记录中(除非您正在填充这些在执行 Where 过滤器之前未发布的代码中)。

因此,您可以考虑将单独的记录存储为PhraseEntryWordEntryTextEntry,这样您就可以准确地执行该过滤器并在 Realm 上执行它。如果您改为使用以下内容会怎样?

public class Entry : RealmObject
{
    [Key]
    [PrimaryKey]
    [Column("entry_id")]
    public int Id { get; set; }

    [Column("user_id")]
    public int UserId { get; set; }

    [Column("source_id")]
    public int SourceId { get; set; }

    [Column("rate")]
    public int Rate { get; set; }

    [Column("created_at")]
    public string CreatedAt { get; set; }

    [Column("updated_at")]
    public string UpdatedAt { get; set; }

    [Column("content")]
    public string Content { get; set; }

    [NotMapped]
    public IList<Translation> Translations { get; }
}

[Table("wordEntry")]
public class WordEntry : Entry
{
}

[Table("phraseEntry")]
public class PhraseEntry : Entry
{
}

[Table("textEntry")]
public class TextEntry : Entry
{
}

现在,您可以将过滤卸载到 Realm:

 var wordEntries = RealmDb.All<WordEntry>.Where(entry => 
entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();
 var phraseEntries = RealmDb.All<PhraseEntry>.Where(entry => entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();
 var textEntries = RealmDb.All<TextEntry>.Where(entry => entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();

 var entries = new List<Entry>();
 entries.AddRange(wordEntries);
 entries.AddRange(phraseEntries);
 entries.AddRange(textEntries);

var ids = entries.Select(entry => entry.Id).ToList();

它不像将它们全部存储在一个表中那么简短,但我没有立即看到任何 Realm 文档表明支持同时对多个表执行相同的查询,所以至少这可以让你离开过滤到数据库并在本地处理更有限的值子集。

最后,我们已经掌握了所有这些,但我错过了您的最后一个问题。您表明您希望根据您创建的一些 id 集合返回条目的子集。在您提供的逻辑中,您正在检索所有结果中的所有 Id 属性,因此实际上没有进一步的子集可供提取。

也就是说,假设您有一个单独的 id 列表,无论出于何种复杂原因,您只能在从上面检索 Entry 类型列表(它们本身都是 PhraseEntryWordEntry 或 @ 987654357@ 个对象)。

此时,由于您已经从 Realm 中提取了所有值并将它们保存在本地,因此只需对它们执行另一个 Where 语句。因为List 实现了 IEnumerable,因此您可以在本地执行 LINQ,而不受任何领域限制:

var myLimitedIdSet = new List<int>() 
{
    10, 15, 20, 25 //Really complicated logic to narrow these down locally
};
var resultingEntries = entries.Where(entry => myLimitedIdSet.Contains(entry.Id)).ToList();

你已经准备好了。您将只有那些与myLimitedIdSet 中列出的 ID 匹配的条目。

编辑地址评论

由于文档中this page 顶部提供的详细信息,您会看到此错误。具体来说(并适应您的代码):

第一条语句为您提供了一个实现 IQueryable 的类的 Entry 的新实例...这是标准的 LINQ 实现 - 您将获得一个表示查询的对象。在您进行需要迭代或计算结果的进一步调用之前,查询不会执行任何操作。

然后通过从RealmDb.All&lt;Entry&gt;() 获取结果并尝试将其转换为IEnumerable&lt;Entry&gt; 以对它进行操作,就好像您有本地数据一样,您的错误就派生了。在您调用 ToList() onRealmDb.All 之前,您只需获得调用内容的 LINQ 表示,而不是数据本身。因此,当您使用 Where 语句进一步优化结果时,实际上是将其添加到 IQueryable 语句的缩小版本中,这也会失败,因为您在 Realm 数据集中缺少适当的映射。

要跳过我上面提供的优化,以下应该可以解决您的问题:

var bgHaystack = realm.All<Entry>().ToList(); //Now you have local data
var results = bgHaystack.Where(entry => entry.Content.ToLower().StartsWith(needle));

不幸的是,鉴于您提供的代码,我不希望您在这里看到任何匹配项,除非 needle 是一个空字符串。不仅您的 Content 属性不是 Realm 数据的一部分,因此您无法在 Realm 中对其进行过滤,而且您的 Phrase、Word 或 Text 属性也没有映射。因此,您在获取 Content 值时只会看到一个空字符串。

您可以进一步细化上面的 results 变量,以仅生成那些具有您认为适合普通 LINQ 的 ID 的实例(同样,您将在第一行从 Realm 中提取数据)。

var limitedIds = new List<int>{10, 20, 30};
var resultsLimitedById = results.Select(a => limitedIds.Contains(a.Id)).ToList();

我已经更新了上面的示例,以反映在适当的地方使用ToList()

【讨论】:

  • 感谢您的努力我真的很感激,但似乎我无法在这里解释真正的问题。正如您所说,.Content 不是领域数据的一部分,这可能就是 LINQ 不会对其执行 StartsWith 的原因,这就是为什么我在没有它的情况下使用新变量“子集”编译器会抛出错误。这就是为什么我决定从该子集中提取 id 以便稍后从初始数组中获取实际条目,这就是为什么使用三个表并将它们组合到一个普通 List 也无济于事的原因,重点是返回 的列表无需将它们转换为新列表或可枚举
  • @MovsarBekaev 我相信我更了解您的具体问题。请查看答案底部的补充是否对您有更多帮助。
  • 感谢您的更新,最后一段非常有用,我将实施它。 Word、Phrase 和 Text 实际上是 Realm 的一部分,[NotMapped] 注释是针对 EntityFramework 的,而不是针对领域的,抱歉造成混淆
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多