简体   繁体   中英

Realm: Auto-Incrementing Primary Key

I'm trying out Realm by implementing a simple financial book-keeping tool. I'm working with a schema that looks something like the following (inheriting from a sql based project)

示例模式

Initially I setup classes for all of these tables with a PrimaryKey of type long . Then I learned that, at least as of 2015, realm doesn't support auto-increment . In my own testing it appears to be true, as I get an exception saying 0 is a duplicate primary key.

Looking at the examples on the getting started guide I tried to re-evaluate if I actually needed specific Id fields. For the most part it doesn't seem I particularly do aside from potential speed benefits of using a primary key in the database. But it seems reasonable to me to expect that Realm does this under the hood when I setup relations anyways. There was one exception though.

// 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);
            }
        }
    }
}

At first I considered perhaps having Transaction have a field IList<Transaction> but I didn't like the removed benefit of having one entry that adds it to both of them. Having to maintain separate copies of the data wasn't appealing to me. I considered instead of comparing the Id using the Equals function as I noticed RealmObject overloads it (something like tl => tl.Rhs.Equals(this) ), but I wasn't sure how it would handle in queries (maybe I should try that after posting this question...but I'm a little ways from being able to test that)

Well, at this point (I could be wrong) it seems it was best for me to implement something auto-incrementing. So following off the example in the afore-mentioned stack overflow post I came up with something.

// 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);
            });
        }
    }
}

The afore-mentioned guide says realm doesn't like inheritance in these classes but it seemed to run ok with this simple interface. The problem is that on the line that calls Max() it throws a NotSupportedException . So now I'm left with a few potential problems to solve, any one of which will get me moving forward.

  1. Alternative to Max() for determining the max Id
  2. Verifying the efficacy of using Equals(this) inside of a Linq query and ditching Ids all together
  3. Another alternative I haven't considered

This is for me getting accustomed to realm, so as much as possible I want to do things "the realm way". Any other comments about how I can fit that style better are welcome.

Responses to @Cristo

The line in question where the exception gets called is long id = db.All<T>().Max(el => el.Id); as you can see in the above code snippet. The exact text is System.NotSupportedException: 'The method 'Max' is not supported' . The stack trace is

[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] 

Where line 47 corresponds to the db.Write( and 48 corresponds to the line with the call to Max

The problem is that on the line that calls Max() it throws a NotSupportedException

Assuming you have an int|long|... type as the primary key, you can use Last() on a transient query to obtain the last|max id value and manually increment it by one (since the Realm query is lazy loaded, only the last object is instanced to determine the current value of the primary key).

Example:

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();
        }
    }

}

Usage:

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

Not sure why 'Max' doesn't work there. Realm.All() returns an IQueryable which inherits IEnumerable , so you should be able to use Max() . Are you sure that 'Max()' is throwing the NotSupportedException? What's the value that's being returned by 'All'? Can you post the exception message and stack trace?

An alternative to 'Max()' would be to order by descending and take element 0:

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

If 'Max()' doesn't work, I wouldn't expect OrderByDescending to work either though. You might have to loop over All and find the max id the old fashioned way.

Another alternative would be 'ToList', then try the above or sort.

A fugly hack alternative to the way you're using IPrimaryKeyId is to have each DB class inherit from an abstract base object which has a private static id incremented int the 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
{
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM