Realm数据库是SQLite、Core Data的替代品,跨平台、支持加密、更快的执行效率,不用写sql。
安装Realm
使用VS新建一个UWP项目,右键解决方案资源管理器
中项目节点下的的引用
节点,选择管理Nuget程序包
,打开NuGet
包管理器。切换到浏览
标签,搜索Realm
,选择第一项Realm
,点击右侧的安装
。
创建 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还要同时安装Fody
,Realm.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字段数据。
这里,realm
和mandUser.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数据库管理工具。
参考
- Getting Started