【问题标题】:Realm: Auto-Incrementing Primary Key领域:自动递增主键
【发布时间】:2019-01-02 22:08:22
【问题描述】:

我正在通过实现一个简单的财务簿记工具来试用 Realm。我正在使用类似于以下内容的架构(从基于 sql 的项目继承)

最初,我使用 long 类型的 PrimaryKey 为所有这些表设置类。然后我了解到,至少在 2015 年,领域doesn't support auto-increment。在我自己的测试中,这似乎是正确的,因为我收到一个异常说 0 是重复的主键。

查看getting started guide 上的示例,我试图重新评估我是否真的需要特定的 Id 字段。在大多数情况下,除了在数据库中使用主键的潜在速度优势之外,我似乎并没有特别做。但是,无论如何,当我建立关系时,期望 Realm 在后台执行此操作对我来说似乎是合理的。不过有一个例外。

// TransactionLink.cs
using Realms;

namespace Finance.Models {
    public class TransactionLink : RealmObject {
        public Transaction Lhs { get; set; }
        public Transaction Rhs { get; set; }
    }
}

// Transaction.cs
using Realms;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Finance.Models {
    public class Transaction : RealmObject {
        [PrimaryKey]
        public long Id { get; set; }

        // ...

        [Ignored]
        public IQueryable<Transaction> LinkedTransactions {
            get {
                var lhs = Realm.All<TransactionLink>().Where(tl => tl.Lhs.Id == Id).Select(tl => tl.Rhs);
                var rhs = Realm.All<TransactionLink>().Where(tl => tl.Rhs.Id == Id).Select(tl => tl.Lhs);
                return lhs.Union(rhs);
            }
        }
    }
}

起初,我考虑让 Transaction 有一个字段 IList&lt;Transaction&gt;,但我不喜欢删除一个条目将其添加到它们两者的好处。必须维护数据的单独副本对我没有吸引力。我考虑过使用Equals 函数比较Id 而不是因为我注意到RealmObject 重载它(类似于tl =&gt; tl.Rhs.Equals(this)),但我不确定它在查询中的处理方式(也许我应该在发布后尝试问题...但我距离能够测试它还有一点距离)

好吧,在这一点上(我可能是错的)似乎对我来说最好实现一些自动递增的东西。因此,按照afore-mentioned stack overflow post 中的示例,我想出了一些东西。

// IPrimaryKeyId.cs
namespace Finance.Context {
    public interface IPrimaryKeyId {
        long Id { get; set; }
    }
}

// Database.cs
using Finance.Models;
using Realms;
using System.Linq;
using System.Windows;
using static Finance.Context.Handle;

namespace Finance.Context {
    public class Database {
        //-------------------------------------------------------------------------------------------------------------+
        // Non-Static Interface: Provide access to all the tables in the database                                      |
        //-------------------------------------------------------------------------------------------------------------+
        private Realm Db { get; set; }
        public IQueryable<Bank>                  Banks                  { get { return Db.All<Bank>(); } }
        public IQueryable<Models.Transaction>    Transactions           { get { return Db.All<Models.Transaction>(); } }
        public IQueryable<TransactionLink>       TransactionLinks       { get { return Db.All<TransactionLink>(); } }
        // ...

        public void SetupExample() {
            AutoIncrement(new Bank { Name = "Cash" });
            var first = Banks.First();
            MessageBox.Show(first.Id.ToString() + ": " + first.Name);
        }

        public void AutoIncrement<T>(T obj) where T : RealmObject, IPrimaryKeyId {
            AutoIncrement(Db, obj);
        }

        public static void AutoIncrement<T>(Realm db, T obj) where T : RealmObject, IPrimaryKeyId {
            db.Write(() => {
                long id = db.All<T>().Max(el => el.Id);
                obj.Id = id + 1;
                db.Add(obj);
            });
        }
    }
}

afore-mentioned guide 表示领域不喜欢这些类中的继承,但使用这个简单的接口似乎可以正常运行。问题是在调用Max() 的行上会抛出NotSupportedException。所以现在我还有一些潜在的问题需要解决,其中任何一个都会让我继续前进。

  1. 替代Max() 用于确定最大值Id
  2. 验证在 Linq 查询中使用 Equals(this) 并同时放弃 Id 的效果
  3. 我没有考虑过的另一种选择

这是为了让我习惯于境界,所以我想尽可能地以“境界方式”做事。欢迎任何其他关于我如何更好地适应这种风格的方法。

对@Cristo 的回应

调用异常的行是long id = db.All&lt;T&gt;().Max(el =&gt; el.Id);,正如您在上面的代码sn-p中看到的那样。确切的文本是System.NotSupportedException: 'The method 'Max' is not supported'。堆栈跟踪是

[External Code] 
Finance.exe!Finance.Context.Database.AutoIncrement.AnonymousMethod__0() Line 48 C#
[External Code] 
Finance.exe!Finance.Context.Database.AutoIncrement<Finance.Context.Handle>(Realms.Realm db, Finance.Context.Handle obj) Line 47 C#
Finance.exe!Finance.Context.Database.Create(string file) Line 74    C#
Finance.exe!Finance.MainWindow.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 14    C#
[External Code] 

其中第 47 行对应于db.Write(,第 48 行对应于调用Max 的行

【问题讨论】:

标签: c# .net realm


【解决方案1】:

问题是在调用 Max() 的那一行会抛出 NotSupportedException

假设你有一个 int|long|... 类型作为主键,你可以在一个临时查询上使用 Last() 来获取 last|max id 值并手动增加它一个(由于 Realm 查询是延迟加载的,因此只实例化最后一个对象以确定主键的当前值)。

示例:

public class ItemModel : RealmObject
{
    public ItemModel() { }

    public ItemModel(int ID)
    {
        this.ID = ID;
    }

    [PrimaryKey]
    public int ID { get; set; }

    public static ItemModel Create()
    {
        ItemModel NewItemModel()
        {
            var q = Realm.GetInstance(Consts.realmDBName).All<ItemModel>();
            return new ItemModel(q.Any() ? q.Last().ID + 1 : 0);
        }

        if (Realm.GetInstance(Consts.realmDBName).IsInTransaction)
        {
            return NewItemModel();
        }
        // Use a pseudo transaction to ensure multi-process safety on obtaining the last record 
        using (var trans = Realm.GetInstance(Consts.realmDBName).BeginWrite())
        {
            return NewItemModel();
        }
    }

}

用法:

var aRealmObject = instance.Add(ItemModel.Create(), false);

【讨论】:

  • 您作为示例提出的Create 函数必须复制粘贴到每个继承RealmObject 的类上,这就是我着手实现接口的原因。然而,Last 的建议我能够引入我的解决方案,它似乎正在工作。但是,我确实想知道我是否会因为类似于我在评论 Cristo 的回答中提到的异常消息:IPrimaryKeyId is not in the limited set of classes for this Realm 的原因而遇到任何问题。想法?
  • @Assimilater 您不必将静态 Create 方法复制/粘贴到每个模型中,您可以创建一个自动设置主键的类工厂(基于相同的逻辑,只是以通用方式...,我只将 RealmObject 继承公开为基于模型的接口,因此只要在将 pri-key 添加到您的领域实例之前设置了 pri-key,它如何完成对我的设计/体系结构并不重要(因为它在...之后无法更改))。我不确定克里斯托到底在强加什么……(?)。
  • 这只是我尝试使用OrderByDescending时出现的错误消息,我记得在入门指南中看到一些内容表明Realm不喜欢具有继承的类
  • @Assimilater 啊……好的。是的,您不能对 RealmObject 进行多级继承...您肯定已经围绕它设计了模型,这很痛苦吗,当然,我倾向于通过使用接口向任何 RealmObject 的消费者隐藏它,但是在最后,您必须提供一个存储在领域实例中的具体类。
【解决方案2】:

不确定为什么“Max”在那里不起作用。 Realm.All() 返回一个继承IEnumerableIQueryable,所以你应该可以使用Max()。您确定“Max()”正在抛出 NotSupportedException 吗? “All”返回的值是多少?你能发布异常消息和堆栈跟踪吗?

“Max()”的替代方法是按降序排序并取元素 0:

db.All<T>().OrderByDescending(el => el.Id).First();

如果 'Max()' 不起作用,我也不希望 OrderByDescending 起作用。您可能必须遍历 All 并以老式方式找到最大 id。

另一种选择是“ToList”,然后尝试上述方法或排序。

与您使用 IPrimaryKeyId 的方式相比,一个令人毛骨悚然的 hack 替代方法是让每个 DB 类继承自一个抽象基础对象,该基础对象的私有静态 id 在 ctor 中递增...

public interface IPrimaryKeyId
{
    long Id { get; }
}

public abstract BankPrimaryKeyId : IPrimaryKeyId
{
    private static long _id;
    public long Id => _id;
    protected BankPrimaryKeyId()
    {
        _id++;
    }
}    

public class Bank : BankPrimaryKeyId
{
}

【讨论】:

  • 静态成员只有在程序运行期间才能工作。如果程序关闭并再次打开,则跟踪将丢失。
  • 我更新了我的问题,回复了您的其他询问。感谢您抽出宝贵时间回复
  • 尝试您的 OrderByDescending 实际上可以解决这个问题:System.NotSupportedException: 'The class IPrimaryKeyId is not in the limited set of classes for this Realm, so sorting by its properties is not allowed.'
猜你喜欢
  • 2017-03-03
  • 1970-01-01
  • 1970-01-01
  • 2013-09-28
  • 2017-02-10
  • 1970-01-01
  • 2018-11-23
  • 2019-08-07
  • 2015-05-11
相关资源
最近更新 更多