Realm and auto increment Behavior (Android)

萝らか妹 提交于 2019-11-29 02:26:57

问题


I'm trying to get data from Realm using an ID as a reference. However, when querying for an ID, I've found that Realm is giving me the same ID for all elements (ID of 0). Why doesn't the ID auto-increment when I use the @PrimaryKey annotation on the model's ID?

Here's the shortened class for the model:

public class Child_pages extends RealmObject {
    @PrimaryKey
    private int id_cp;

    private int id;
    private String day;
    private int category_id;

And the query I'm executing is: realm.where(Child_pages.class).equalTo("id_cp",page_id).findFirst()


回答1:


Realm currently doesn't support auto incrementing primary keys. However you can easily implement it yourself using something like:

public int getNextKey() { 
    try { 
         Number number = realm.where(object).max("id");
         if (number != null) {
             return number.intValue() + 1;
         } else {
             return 0;
         }
    } catch (ArrayIndexOutOfBoundsException e) { 
         return 0;
    }
}

I hope that can get you started.




回答2:


The Java binding does not support primary keys yet, but it's on the roadmap and with high priority - see: https://groups.google.com/forum/#!topic/realm-java/6hFqdyoH67w . As a workaround you can use this piece of code for generating keys:

int key;
try {
  key = realm.where(Child_pages.class).max("id").intValue() + 1;
} catch(ArrayIndexOutOfBoundsException ex) {
 key = 0;
}

I use singleton factory for generating primary keys as a more generic solution with better performance (no need to query for max("id") every time thanks to AtomicInteger).

There is a long discussion in Realm Git Hub if you need more context: Document how to set an auto increment id?




回答3:


As already mentioned, auto-increment isn't supported yet.

But for those who use kotlin and wants to have an auto-increment behavior with realm, this is one of the possibilities:

open class Route(
    @PrimaryKey open var id: Long? = null,
    open var total: Double? = null,
    open var durationText: String? = null,
    open var durationMinutes: Double? = null,
    open var distanceText: String? = null,
    open var distanceMeters: Int? = null): RealmObject() {

companion object {
    @Ignore var cachedNextId:Long? = null
        get() {
            val nextId =    if (field!=null) field?.plus(1)
                            else Realm.getDefaultInstance()?.where(Route::class.java)?.max("id")?.toLong()?.plus(1) ?: 1
            Route.cachedNextId = nextId
            return nextId
        }
}}



回答4:


if your Id is Long then:

val nextId = bgRealm.where(Branch::class.java).max("localId") as Long + 1



回答5:


Sadly, I can't comment on other answers, but I'd like to add to Vinicius` answer.

First, you shouldn't open a new realm instance, when looking for primary key, especially when you don't close them, as this adds to maximum possible file descriptor count.

Secondly, although this is a preference, you shouldn't have primitive types (or object equivalents) as nullables (in Kotlin), as this adds redundant requirement to check nullability.

Third, since you're using kotlin, you can just define an extension method to Realm class, such as:

fun Realm.getNextId(model: RealmModel, idField : String) : Long
{
    val count = realm.where(model::class.java).max(idField)
    return count + 1L
}

Since all RealmObject instances are RealmModel, and even objects that aren't tracked by Realm are still RealmModel instances, therefore it will be available where ever you use your realm related classes. Java equivalent would be:

static long getNextId(RealmModel object, Realm realm, String idField)
{
    long count = realm.where(object.class).max(idField);
    return count + 1;
}

Late edit note: You're better off not using this approach if you're dealing with data that might come from outside sources, such as other databases or the web, because that WILL result in collisions between data in your internal database. Instead you should use max([id field name]). See the previous snippets, I already modified them to accommodate for this edit.




回答6:


Here is a genereic solution, personnaly I create a class named RealmUtils and I add this type of methods :

 public static int getPrimaryKey(Class c)
{
    Realm realm = Realm.getDefaultInstance();

    String primaryKeyFied = realm.getSchema().get(c.getSimpleName()).getPrimaryKey();
    if (realm.where(c).max(primaryKeyFied)== null)
        return 1;
    int value = realm.where(c).max(primaryKeyFied).intValue();
    return value+1;
}

Why I return 0 when the table is empty ? Because I hate Id's with 0 as value, so just change it. Hope it help someone.




回答7:


//generates primary key
    public static int getNextKey(RealmQuery realmQuery, String fieldName) {
        try {
            Number number = realmQuery.max(fieldName);
            if (number != null) {
                return number.intValue() + 1;
            } else {
                return 1;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return 1;
        }
    }

Example

int id = RealmDbHelper.getNextKey(realm.where(RealmDocument.class), RealmDocument.FIELD_DOCUMENT_ID)
    realmObject.setId(id);
    realm.insert(realmObject);



回答8:


// EDIT

I found this new solution on this problem

@PrimaryKey
private Integer _id = RealmAutoIncrement.getInstance().getNextIdFromDomain(AccountType.class);

// Original Answer

I just wanted to share my attempt on solving this Problem, because i don't want to pass a primary Key value all the time. First i created a Database-Class to handle the storage of a RealmObject.

public class RealmDatabase {
Realm realm;

public RealmDatabase() {
    RealmConfiguration realmConfiguration =  new RealmConfiguration.Builder()
            .name("test").schemaVersion(1).build();

    try {
        realm = Realm.getInstance(realmConfiguration);
    } catch (Exception e) {
        Log.e("Realm init failed", e.getMessage());
    }
}

protected void saveObjectIntoDatabase(final RealmObject object) {
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm bgRealm) {
            bgRealm.copyToRealm(object);    
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            // onSuccess
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            // onError
        }
    });
}

After creating the database-class i created an Interface to distinguish between Objects with a Primary Key and without.

public interface AutoIncrementable {
    public void setPrimaryKey(int primaryKey);
    public int getNextPrimaryKey(Realm realm);
}

Now you will just need to edit this code of the previous execute method

if(object instanceof AutoIncrementable){
  AutoIncrementable autoIncrementable = (AutoIncrementable) object;
  autoIncrementable.setPrimaryKey(autoIncrementable.getNextPrimaryKey(bgRealm));
  bgRealm.copyToRealm((RealmObject)autoIncrementable);
} else {
  bgRealm.copyToRealm(object);
}

With this solution the database logic will still be in one class and this class can passed down to every class which needs to write into the database.

public class Person extends RealmObject implements AutoIncrementable{

    @PrimaryKey
    public int id;
    public String name;

    @Override
    public void setPrimaryKey(int primaryKey) {
        this.id = primaryKey;
    }

    @Override
    public int getNextPrimaryKey(Realm realm) {
        return realm.where(Person.class).max("id").intValue() + 1;
    }
}

For further suggestions feel free to leave a comment below.



来源:https://stackoverflow.com/questions/30028282/realm-and-auto-increment-behavior-android

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!