Использование Contains () в запросе Realm - PullRequest
0 голосов
/ 08 мая 2020

Допустим, у нас есть результаты области, полученные с помощью

RealmDb.All<Entry>();

Затем я хочу выполнить поиск по этим результатам, используя еще не поддерживаемые методы, такие как StartsWith при возврате функции или свойстве, которое не сопоставлены в области et c, поэтому я получаю подмножество

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

Чтобы каким-то образом получить их как часть RealmResults, я извлекаю идентификаторы записей следующим образом:

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

и, наконец, Я хочу вернуть подмножество RealmResults (не IEnumerable) только из тех записей, которые содержат эти идентификаторы, как я могу это сделать? 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 "";
            }
        }
    }
}

1 Ответ

1 голос
/ 08 мая 2020

Согласно документации , Realm. NET поддерживает LINQ, так что это многообещающе. В вашем конкретном примере c вы указываете, что StartsWith не поддерживается, но я вижу это на приведенной выше странице, в частности здесь .

Теперь ваш пример ясно показывает, что Entry a RealmObject, поэтому непонятно, откуда вы могли взять RealmResult (и в их документации на этой странице не упоминается RealmResult). В частности, домашняя страница указывает на то, что вы действительно собираетесь работать только с Realm, RealmObject и Transaction, поэтому я просто предполагаю, что вы имели в виду, что вы нужно получить RealmObject по их примерам.

То, как вы сейчас настроили свой объект данных, вы застряли, называя его так, как вы (хотя, если бы я мог порекомендовать его немного упростить:

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]. Согласно документации снова:

Как правило, вы можете создавать предикаты только с условиями, которые полагаются на данные в Realm. Представьте себе класс

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

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

Учитывая этот класс, вы можете создавать запросы с условиями, которые применяются к свойствам FirstName и LastName, но не к свойству FullName. Точно так же нельзя использовать свойства с атрибутом [Ignored].

Поскольку вы используете [NotMapped], я должен поверить, что это будет вести себя аналогично [Ignored] и далее, потому что это просто вычисленное значение, это не то, что Realm сможет обрабатывать как часть запроса - он просто выполняет Я не знаю этого, потому что вы не сопоставили его с информацией, которую хранит Realm. Скорее вам придется вычислять свойство Content, когда у вас действительно есть экземпляры ваших объектов Entry для перечисления.

Точно так же я ожидаю, что у вас возникнут проблемы с получением значений из Phrase, Word и Text, поскольку они также не отображаются и, следовательно, не сохраняются в записи в Realm (если вы не заполняете те коды, которые не публиковали перед выполнением фильтра Where).

Таким образом, вы можете вместо этого рассмотреть возможность хранения отдельных записей как PhraseEntry, WordEntry и TextEntry, чтобы вы действительно могли выполнить именно этот фильтр и выполнить его в 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, в которой указывается на поддержку одновременного выполнения одного и того же запроса к нескольким таблицам, так что, по крайней мере, это позволит вам оставить фильтрацию базе данных и работать с более ограниченным подмножеством значений локально.

Наконец, все это у нас есть, и я пропустил ваш последний вопрос наверху. Вы указываете, что хотите вернуть подмножество своих записей на основе некоторой коллекции созданных вами идентификаторов. В предоставленном вами logi c вы извлекаете все свойства Id во всех своих результатах, так что на самом деле нет никакого другого подмножества для извлечения.

Тем не менее, давайте предположим, что у вас есть отдельный список идентификаторов, которые по какой-то сложной причине вы могли получить только после получения списка Entry типов сверху (сами все объекты PhraseEntry, WordEntry или TextEntry).

На этом этапе, поскольку вы уже извлекли все значения из Realm и разместили их локально, просто выполните для них другой оператор Where . Поскольку List реализует IEnumerable, вы можете, таким образом, выполнить LINQ локально без каких-либо ограничений Realm:

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.

Edit to address comment

Вы видите эту ошибку из-за подробностей, представленных в верхней части этого страница в документации. В частности (и адаптируясь к вашему коду):

Первый оператор дает вам новый экземпляр Entry класса, реализующего IQueryable ... Это стандартная реализация LINQ - вы получаете объект, представляющий запрос. Запрос ничего не делает до тех пор, пока вы не сделаете следующий вызов, который должен выполнить итерацию или подсчет результатов.

Затем ваша ошибка выводится, беря результат из RealmDb.All<Entry>() и пытаясь преобразовать его в IEnumerable<Entry>, чтобы действовать против него, как будто у вас есть локальные данные. Пока вы не вызовете ToList() on RealmDb.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 (опять же, вы вытащили данные из Realm в первой строке).

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

Я обновил приведенные выше примеры, чтобы отразить использование ToList() в соответствующих местах.

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