UWP使用Realm数据库

Realm数据库是SQLite、Core Data的替代品,跨平台、支持加密、更快的执行效率,不用写sql。

安装Realm

使用VS新建一个UWP项目,右键解决方案资源管理器中项目节点下的的引用节点,选择管理Nuget程序包,打开NuGet包管理器。切换到浏览标签,搜索Realm,选择第一项Realm,点击右侧的安装

UWP使用Realm数据库_第1张图片
uwp_realm_01.png

创建 FodyWeavers.xml 文件

在项目中,新建一个xml文件,命名为FodyWeavers.xml,文件属性保持默认,内容如下:



    

The Realm package contains everything you need to use Realm. It depends on Fodyweaver, responsible for turning your RealmObject subclasses into persisted ones.

目前更新项目之后发现现在的写法要改成下面这样,而且注意NuGet还要同时安装FodyRealm.Fody



  

创建至少一个RealmObject派生类

//User.cs
class User: RealmObject
{
    [Realms.PrimaryKey]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Sex { get; set; }
    public int Age { get; set; }
}
//Book.cs
class Book : RealmObject
{
    [Realms.PrimaryKey]
    public int Id { get; set; }
    public string Name { get; set; }
}

Realm数据库基本操作

连接或创建数据库

//对Realm数据库进行配置,Test.relam为数据库文件名
var config = new RealmConfiguration("Test.realm");
//打印一下这个数据库文件实际的保存路径
System.Diagnostics.Debug.WriteLine($"{Windows.Storage.ApplicationData.Current.LocalFolder.Path}\\Test.relam");
//设置数据库中的表对应的实体类,这里设置数据库只有一个User表;如果不设置,默认所有派生自RealmObject的类都会在该数据库中生成对应表 
config.ObjectClasses = new Type[] { typeof(User) };
//设置数据库加密密码,长度为固定的64 bytes
//config.EncryptionKey = new byte[64]
    //{
    //    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    //    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
    //    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    //    0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
    //    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
    //    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
    //    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
    //    0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78
    //};
//获取Relam实例
var realm = Realm.GetInstance(config);

插入记录

//插入记录
var mandUser = new User() { Id = 3, Name = "Mandarava", Sex = "M", Age = 66 };            
realm.Write(() =>
{
    realm.Add(new User() { Id = 1, Name = "Tom", Sex = "M", Age = 77 });
    realm.Add(new User() { Id = 2, Name = "Jerry", Sex = "F", Age = 88 });
    realm.Add(mandUser);
    realm.Add(new User() { Id = 4, Name = "Jack", Sex = "M", Age = 99 });
});

更新记录

//更新记录
realm.Write(() => {
    //更新mandUser的Sex字段
    mandUser.Sex = "男";
    //更新mandUser //因为这里的new User的Id=3,mandUser的Id也是3,Id是PrimaryKey,所以这里仍然更新mandUser
    realm.Add(new User() { Id = 3, Name = "鳗驼螺", Sex = "Male", Age = 88 }, true); 
}); 

查询记录

//查询记录
var users = realm.All().Where(user => user.Sex == "M");
foreach(var user in users)
{
    System.Diagnostics.Debug.WriteLine($"{user.Id}\t{user.Name}\t{user.Sex}\t{user.Age}");
}

删除记录

//删除记录
realm.Write(() =>{
    //删除指定的记录
    realm.Remove(mandUser);
    //删除满足条件的一组记录
    var dusers = realm.All().Where(user => user.Sex == "M");
    realm.RemoveRange(dusers);
});

注:Realm.Write方法都是基于事务处理的。

一些问题和解决方法

No RealmObjects 错误

如果在运行时出现下面的错误:

System.InvalidOperationException:“No RealmObjects. Has linker stripped them? See https://realm.io/docs/xamarin/latest/#linker-stripped-schema”

如果没有在项目中写任何RelamObject派生类,就会出现这个问题。实际上是config.ObjectClasses没有设置造成的。所以,至少需要创建一个RelamObject的派生类。

非自动属性问题

Realm只支持自动属性,如:

[Realms.PrimaryKey]
public int Id{get;set;}

如果写成下面这种常规属性的形式:

[Realms.PrimaryKey]
public int Id {
    get { return id; }
    set { id = value; }
}
private int id;

你会得到类似下面的错误:

Fody/RealmWeaver: User.Id has [PrimaryKey] applied, but it's not persisted, so those attributes will be ignored.

当然,你可能会说把[Realms.PrimaryKey]去掉就行了。去掉是不会报错了,但这个属性仍然会被忽略,不会成为Realm数据库表的字段,因为根本问题不在于这个属性修饰符,而是它被视为it's not persisted

那如果我们在设置属性的时候,确有要求需要做点额外操作(比如检查属性值是否符合要求),这种情况下只能写成非自动属性,比如:

public int Age {
    get { return age; }
    set { age = value >= 0 ? value : 0; }
}
private int age;

但非自动属性又不能被Realm持久化,那这种情况下该怎么办?

为解决这类问题,RealmObject提供了一个PropertyChanged事件,该事件在属性发生改变时被触发。所以,我们要检查某个属性值是否符合我们的要求,或者在属性设置时干点其它的事,属性仍然写成自动属性,然后可以去监听这个事件,在该事件中去检查属性合法性(或干点别的),如:

//User.cs
class User: RealmObject{
    ...
    public int Age{ get;set; }
}

//App
var mandUser = new User() { Id = 3, Name = "Mandarava", Sex = "M" };
mandUser.PropertyChanged += (obj, evt) =>
      {
          var usr = obj as User;
          switch (evt.PropertyName)
          {
              case nameof(User.Age): if (usr.Age < 0) usr.Age = 0; break;
              default: break;
          }
      };
mandUser.Age = 66;

当然,你可能有很多个User实例,每个都要添加一个PropertyChanged事件,太麻烦;而且像上面的代码,你需要在监听PropertyChanged事件后,再去设置要监听的属性Age的值,否则就会错过监听。为避免这些麻烦,因为User类派生自RealmObject类,所以更好的做法是直接在User类中重写OnPropertyChanged事件,像这样:

//User.cs

protected override void OnPropertyChanged(string propertyName)
{
    base.OnPropertyChanged(propertyName);
    switch (propertyName)
    {
        case nameof(Age): if (Age < 0) Age = 0; break;
        default: break;
    }
}

当然,还有一种方法是建立二个属性来解决这个问题,其中一个必是自动属性(可以设为private):

private int age { get; set; }
public int Age {
    get { return age; }
    set { age = value >= 0 ? value : 0; }
}

这样也可以达到目的。当然,这个方法的问题是最终数据库中年龄字段的名称变成了age,如果你说我一定要让字段名是Age,咋整?可以使用MapTo属性修饰符来限定持久化后的属性名称,像这样:

[Realms.MapTo("Age")]
private int age { get; set; }

Cannot modify managed objects outside of a write transaction 错误

从Realm数据库中取出的数据对象,或通过realm.Add已经加入到Realm数据库中的对象,在更新该对象的Realm字段值时,需要使用Realm.Write(Action)方法(或它的异步版方法)来更新。比如,在前文中,mandUser是通过new来创建的,然后它被加入到Realm数据库中,这之后,它就与这个数据库有关联了,要更新它的Realm字段,不能直接像这样:

mandUser.Sex = "男";

这种方式就会导致本错误的出现,必须使用下面的方式来更新:

realm.Write(() => {
    mandUser.Sex = "男";
};

Write方法是基于事务的更新,这也是为什么错误信息显示为outside of a write transaction的原因。
那么,普通new出来的对象,与从Realm数据库中加载的数据对象有什么区别呢? 这具体看这个对象的Realm属性值。所有派生自RealmObject的对象,都有Realm属性,如果这个属性值为null,表示这个对象并没有关联Realm数据库,一个对象刚new时,它的Realm属性值为null(你也可以直接用RealmObject.IsManaged来检测,检测结果为true就表示Realm不为null,这也就是出错信息中指出的所谓的managed objects)。这种情况下,如果使用realm.Write(Action)是没效果的(当然,它会改变这个对象的属性值,但与Realm数据库无交互),因为这条记录在realm关联的数据库中并不存在,需要先使用realm.Add方法来将它添加到数据库中,一旦添加后,这个对象的Realm属性值不再为null(事实上,它的实际值就是realm),已经与Realm数据库进行了关联(从Realm数据库中加载到的数据也一样),需要使用realm.Write(Action)来更新其Realm字段数据。
这里,realmmandUser.Realm在这个示例中是同一个对象,我们可以使用realm.IsSameInstance(mandUser.Realm)来对二者进行检测是否相同,如果检测结果为false,那表示这二者不是关联的同一数据库,不能使用realm来更新mandUser,当然,你可以用mandUser.Realm.Write(Action)来更新它自己,但注意mandUser.Realm可能为null,如果它尚未关联Realm数据库。

如果想通过一个对象来更新一条已有记录,这里有一个方法Realm.Add(RealmObject, bool)。这个方法的主要功能显然是用来向数据库添加记录的,不过它的第二参数是表示如果记录已经存在是否更新它,这个方法的第一参数是一个RealmObject对象,这个对象并不要求它已经关联了数据库,也就是它的Realm属性值可以是null,这个时候,如果要用它去更新数据库中已有的记录,就要设置这个对象的标记为[Realms.PrimaryKey]属性的Realm字段,让这个字段的值与数据中的要更新的记录中的该字段值保持一致,这样在更新的时候(二参为true),就会在数据库中找到并更新该条记录,如前文的示例:

realm.Write(() => {
    mandUser.Sex = "男";
    //更新mandUser //因为这里的new User的Id=3,mandUser的Id也是3,Id是PrimaryKey,所以这里仍然更新mandUser
    realm.Add(new User() { Id = 3, Name = "鳗驼螺", Sex = "Male", Age = 88 }, true); 
}); 

Relam数据库管理工具

可以使用 Realm Studio 管理数据库文件,它是官方提供的一个跨平台Realm数据库管理工具。

参考

  1. Getting Started

你可能感兴趣的:(UWP使用Realm数据库)