为有牺牲多壮志,敢教日月换新天。

Realm .NET

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11166080.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

入门

先决条件

我们支持以下平台:

  • 适用于iOS 7或更高版本的Xamarin.iOS,使用本机UI或Xamarin Forms
  • Xamarin.Android用于API级别10或更高版本,使用本机UI或Xamarin表单
  • 适用于macOS 10.11或更高版本的Xamarin.Mac,使用nativeUI或Xamarin Forms
  • 适用于Windows 8.1或更高版本的.NET Framework 4.6.1或更高版本
  • 使用.NET Standard 2.0或更高版本的通用Windows平台应用程序(Fall Creators Update)
  • .NET Core 2.0或更高版本:
    • Ubuntu 16.04或更高版本
    • Debian 8或更高版本
    • RHEL 7.1或更高版本
    • macOS 10.11或更高版本
    • Windows 8.1或更高版本

以下环境:

  • Visual Studio 2015 Update 2或更高版本
  • Visual Studio for Mac 7.0或更高版本

安装

如果您要从Realm 3.x升级,请参阅升级指南

可以通过NuGet安装Realm ,也可以在GitHub上浏览源代码

  1. 在“解决方案”窗格中项目下的“包”节点上,单击齿轮按钮并选择“添加包...”
  2. 在搜索框中输入“Realm”
  3. 选择Realm并添加它

Realm包中包含使用Realm所需的一切。这取决于Fody织工,负责把你的RealmObject子类为持续的。

如果您的项目已经在使用Fody,则应该将Realm weaver添加到该FodyWeavers.xml文件中。否则,将在首次构建项目时创建此文件。如果您使用旧的.csproj格式,其中所有文件都明确列在项目文件中,则需要将该FodyWeavers.xml文件添加到项目中。

这是一个示例,说明如果在添加Realm时尚未使用任何其他编织器,FodyWeavers.xml文件应如何显示:

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <Realm />
</Weavers>

Realm .NET使您能够以安全,持久和快速的方式有效地编写应用程序的模型层。这是它的样子:

// Define your models like regular C# classes
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var realm = Realm.GetInstance();

// Use LINQ to query
var puppies = realm.All<Dog>().Where(d => d.Age < 2);

puppies.Count(); // => 0 because no dogs have been added yet

// Update and persist objects with a thread-safe transaction
realm.Write(() =>
{
    realm.Add(new Dog { Name = "Rex", Age = 1 });
});

// Queries are updated in realtime
puppies.Count(); // => 1

// LINQ query syntax works as well
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;

// Query and update from any thread
new Thread(() =>
{
    var realm2 = Realm.GetInstance();

    var theDog = realm2.All<Dog>().Where(d => d.Age == 1).First();
    realm2.Write(() => theDog.Age = 3);
}).Start();

Android ABI支持

由于某些指令集限制,我们支持armeabiABI设置。

创建新Xamarin项目的默认模板目前已检查所有ABI设置,用于Debug版本,但仅限armeabi于Release。您必须在执行自己的发布版本之前更改此设置。

如果您没有选中其他ABI,System.TypeInitializationException则当用户在其他设备上运行时可能会发生。例如,如果您有部署的64位设备,如Galaxy Tab的S2,将导致该错误armeabiarmeabi-v7a,但不会 arm64-v8a检查。

除非你有一个很好的理由,以避免将其他的ABI,做的最好的事情是检查所有的人以外armeabi,所以要检查:

  • armeabi-v7a
  • arm64-v8a
  • x86
  • x86_64

在Visual Studio for Mac中,这些设置来自右键单击项目的“选项 - 构建 - Android构建 - 高级”选项卡。

在Windows上的Visual Studio中,这些设置来自右键单击项目的“属性” - “Android选项” - “高级”选项卡。

Realm Studio

Realm Studio是我们的首选开发人员工具,可以轻松管理Realm数据库和Realm平台。使用Realm Studio,您可以打开和编辑本地和同步的域,并管理任何Realm Object Server实例。它支持Mac,Windows和Linux。

Realm Studio

楷模

领域数据模型是使用具有属性的传统C#类定义的。只需将RealmObject子类即可创建Realm数据模型对象。

领域模型对象的功能大多与其他C#对象一样 - 您可以将自己的方法和事件添加到它们中,并像使用任何其他对象一样使用它们。主要限制是您只能在创建它的线程上使用对象,并且持久化属性已生成getter和setter。

另请注意,您的类必须具有公共的无参数构造函数。如果您不添加任何构造函数,编译器将为您添加此构造函数。但是,如果向类中添加至少一个构造函数,请确保还有一个无参数的公共构造函数

通过包含目标类型的属性或IList对象的类型列表,可以简单地建模关系和嵌套数据结构

// Dog model
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

支持的类型

境界支持原始类型无符号的除外酮(boolcharbyteshortintlongfloatdouble),以及string,和DateTimeOffset还支持可空等效项,可在“ 可选属性”中进一步描述

集合

Realm使用标准类型来表示对象组:

  1. IQueryable<T>,一个表示从查询中检索的对象的类
  2. IList<T>,一个表示模型中多对多关系的类

在运行时,都符合IRealmCollection <T>接口这又符合IReadOnlyList<T>INotifyCollectionChanged支持懒惰枚举-集合的大小是已知的,但为你迭代集合对象仅加载到存储器。

使用受支持对象的集合是通过声明类型属性IList<T>where T任何支持的类型来完成的:

public class Image : RealmObject
{
    // Other properties...

    public IList<string> Tags { get; }

    public IList<double?> Ratings { get; }
}

集合属性发出更改通知(有关更多详细信息,请参阅集合通知),并且可以包含基本类型 - 整数,字符串等 - 以及其他对象(请参阅“多对多关系”)。修改集合(例如添加或删除项目)必须在写入事务中发生

var image = ...;
realm.Write(() =>
{
    image.Tags.Add("sunset");
    image.Ratings.Move(from: 2, to: 5);
});

日期类型

我们使用标准DateTimeOffset类型作为表示日期的主要类型,而不是DateTime

这是基于微软建议,因为模糊不清DateTime

它存储到100纳秒刻度的全精度

虽然您可以指定DateTimeOffset使用时区,但我们存储UTC值。这是一个明确的表示,如果与其他语言一起阅读,将意味着相同的瞬间。但是,因为我们丢失了时区说明符,如果您使用Ticks而不是使用比较值,这可能会导致混淆UtcTicks

计数器

Realm提供RealmInteger <T>作为特殊整数类型。RealmInteger<T>公开了一个额外的API,可以更清楚地表达意图,并在使用Synchronized Realms时生成更好的冲突解决步骤类型参数,T,可以是byteshortint,或long

传统上,计数器将通过读取值,递增并设置(myObject.Counter += 1)来实现。这在异步情况下无法正常工作 - 例如,当两个客户端处于脱机状态时 - 因为双方都会读取一个值,比如10增加它,并将值存储为11最终,当他们重新获得连接并尝试合并他们的更改时,他们会同意计数器处于11预期状态而不是预期状态12

RealmIntegerS被传统的整数类型的支持,所以从改变属性类型时就不需要进行迁移TRealmInteger<T>

public class MyObject : RealmObject
{
    [PrimaryKey]
    public int Id { get; set; }

    public RealmInteger<int> Counter { get; set; }
}

要递增计数器,只需调用Increment

var obj = realm.Find<MyObject>(1);
obj.Counter; // 0
obj.Counter.Increment(); // 1
obj.Counter.Decrement(); // 0
obj.Counter.Increment(2); // 2
obj.Counter.Increment(-5); // -3

要重置计数器,只需为其指定一个新值。请记住,在这种情况下,应用常规合并规则,必须注意确保应用中的正确语义:

var obj = realm.Find<MyObject>(1);
obj.Counter = 0; // Resetting to 0

因为RealmInteger<T>可以隐式转换为T,所以您可以在不进行转换的情况下使用它,也可以不手动构建它:

var obj = new MyObject
{
    Counter = 3
};
realm.Write(() => realm.Add(obj));

DoSomething(obj.Counter);

public void DoSomething(int value)
{
    // ...
}

关系

RealmObject S可通过使用彼此连接RealmObject和IList的性质。

对于列表,使用IList属性声明类,并在首次使用getter时创建实现对象。您应该只指定get;自动方法,因为无法设置此类列表。

一对一的关系

对于多对一或一对一关系,只需使用RealmObject子类的类型声明一个属性

public class Dog : RealmObject
{
    // ... other property declarations
    public Person Owner { get; set; };
}

public class Person : RealmObject
{
    public string Name { get; set; }
}

您可以像使用任何其他属性一样使用此属性:

realm.Write(() =>
{
    var jim = realm.Add(new Person { Name = "Jim" });

    var rex = realm.Add(new Dog { Owner = jim });
});

要打破这种关系,只需指定null

rex.Owner = null;

使用RealmObject属性时,可以使用常规属性语法访问嵌套属性。例如,rex.Owner.Address.Country将遍历对象图并根据需要自动从Realm中获取每个对象。

多对多关系

您可以使用IList属性定义多对多关系当您使用这些属性时,您将获得一个可能为空的集合或包含单个类型的相关RealmObject

要在Person链接到多只狗的模型上添加“Dogs”属性,我们只需将其声明为IList<Dog>属性。您不必初始化IList- Realm SDK会为您处理。只需添加和删除Dog列表中的对象即可建立给定Person和a 之间的关系Dog

public class Dog : RealmObject
{
    public string Name { get; set; }
}

public class Person : RealmObject
{
    // ... other property declarations
    public IList<Dog> Dogs { get; }
}
jim.Dogs.Add(rex);
jim.Dogs.Count();  // => 1 -- nobody but rex

反向关系

链接是单向的。因此,如果to-many属性Person.Dogs链接到Dog实例和to-one属性Dog.Owner链接Person,则这些链接彼此独立。将a 属性附加DogPerson实例的Dogs属性不会自动将dog的Owner属性设置为this Person由于手动同步关系对容易出错,复杂且重复信息,因此Realm提供反向链接属性来表示这些反向关系。

使用反向链接属性,您可以从特定属性获取链接到给定对象的所有对象。例如,一个Dog对象可以拥有一个名为的属性Owners该属性包含在其属性Person中具有此确切Dog对象的所有对象Dogs这是通过创建Owners类型属性IQueryable<Person>然后应用[Backlink]属性来指示OwnersPerson模型对象的关系来完成的

public class Dog : RealmObject
{
    [Backlink(nameof(Person.Dogs))]
    public IQueryable<Person> Owners { get; }
}

反向链接属性可以指向IList<RealmObject>属性(多对多关系)或RealmObject属性(对一个关系):

public class Ship : RealmObject
{
    public Captain Captain { get; set; }
}

public class Captain : RealmObject
{
    [Backlink(nameof(Ship.Captain))]
    public IQueryable<Ship> Ships { get; }
}

可选属性

引用类型,例如stringbyte[]和相关RealmObject值可以为空。

可以为Nullable值类型int?完全支持。

我们还支持可选项,DateTimeOffset?以完成具有每种属性类型的可选版本的全部范围。

控制属性持久性

RealmObject下载的在编译时Fody weaver处理具有自动设置器和getter的所有属性都被认为是持久的,并且生成了setter和getter以将它们映射到内部Realm存储。

我们还提供了一些C#属性来添加元数据来控制持久性。

要避免将属性设置为持久性,只需添加[Ignored]属性即可。一个常见的例子是使用外部媒体,您可以将路径保留到文件而不是二进制图像:

public string HeadshotPath { get; set; }

// Image in memory during run
[Ignored]
public Image Headshot { get; set; }

自定义设置器

具有自己的setter或getter实现的属性将自动被忽略。您可以使用它来添加验证:

private string Email_ { get; set; }

// Validating version of persistent Email_ property
public string Email
{
    get { return Email_; }
    set
    {
        if (!value.Contains("@")) throw new Exception("Invalid email address");
        Email_ = value;
    }
}

索引属性

目前只有字符串,整数,布尔值和DateTimeOffset索引。

索引属性将大大加快查询属性相等的查询(即==Contains运算符),代价是插入速度较慢。

要索引属性,只需将[Indexed]属性添加到属性声明中,例如:

public class Person : RealmObject
{
    [Indexed]
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

映射的属性

Realm文件本质上与平台无关,可以与任何可用的绑定打开或同步。为此,对于不同的语言,模式应该是相同的,包括属性名称的大小写。为避免编写非标准C#类,您可以使用[MapTo]属性,例如:

// C# model
public class Person : RealmObject
{
    [MapTo("name")]
    public string Name { get; set; }

    [MapTo("dogs")]
    public IList<Dog> Dogs { get; }
}
// Equivalent js model
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name: {type: 'string'},
    dogs: {type: 'list', objectType: 'Dog'}
  }
};

自动更新对象

一旦管理RealmObject的实例,它就会成为基础数据的实时自动更新视图,这意味着永远不必刷新对象。修改对象的属性将立即反映在引用同一对象的任何其他实例中。

var myDog = new Dog { Name = "Fido", Age = 1 };
realm.Write(() =>
{
    realm.Add(myDog);
});

var myPuppy = realm.All<Dog>().First(d => d.Age == 1);
realm.Write(() =>
{
    myPuppy.Age = 2;
}

myDog.Age; // => 2

RealmObject的这一方面不仅使Realm快速高效,而且使代码更简单,更具反应性。例如,如果您的UI代码依赖于特定的Realm对象,则在触发UI重绘之前,您无需担心刷新或重新获取它。

您可以订阅Realm通知,以了解对象中的Realm数据何时更新,指示何时应刷新应用程序的UI。

PrimaryKey属性

[PrimaryKey的]属性可以指定一个属性在RealmObject类。声明PrimaryKey可以有效地查找和更新对象,并为每个值强制实施唯一性。

只有chars,整数类型和字符串可以用作PrimaryKeys。使用char或更小的整数类型没有特定的存储或性能优势仅在您已拥有该类型的属性的情况下才支持它们。

[PrimaryKey]属性放在多个属性上将进行编译,但在运行时进行验证,并且只要您尝试打开该Realm ,就会抛出异常,报告Schema验证失败

将具有PrimaryKey的对象添加到Realm后,无法更改PrimaryKey。

尝试使用相同的密钥创建另一个对象将抛出RealmDuplicatePrimaryKeyValueException

您可以使用Realm.Find快速检索PrimaryKey对象,Realm.Find执行比使用LINQ更简化的查询构造,以及使用索引。它被重载以获取字符串,字符或整数键,例如:

public class Person : RealmObject
{
    [PrimaryKey]
    public string SSN { get; set; }
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var objByPK = realm.Find<Person>("457-55-5462");

模型继承

境界.NET并没有让模型以任何方式得到进一步继承。如果检测器检测到从RealmObject间接继承的类,那么它将失败

对象的所有更改(添加,修改和删除)必须在写入事务中完成。

要在线程之间共享对象或在应用程序启动之间重新使用它们,必须将它们持久保存到Realm,这一操作必须在写入事务中完成。

由于写入事务会产生不可忽略的开销,因此您应该构建代码以最小化写入事务的数量,即,如果在循环中插入多个项目,则更愿意在单个事务中执行此操作,而不是为每个项目创建事务,例如:

realm.Write(() =>
{
    var people = Realm.All<Person>();
    foreach (var person in people)
    {
        person.Age += 1;
    }
});

由于写入事务可能会像任何其他磁盘IO操作一样失败,因此您应该准备好处理写入异常,这样您就可以处理并从磁盘空间不足等故障中恢复。没有其他可恢复的错误。为简洁起见,我们的代码示例不处理这些错误,但您肯定应该在生产应用程序中。

创建写事务有两种简单的方法:Realm.BeginWrite()Realm.Write()第一个,Realm.BeginWrite()返回一个实现模式TransactionDispose,允许你使用它using

using(var transaction = realm.BeginWrite())
{
    person.FirstName = "John";
    person.Age = 56;
    transaction.Commit();
}

请注意,您必须显式提交事务,否则它将自动回滚。另一种方法是使用Realm.Write()包装变异代码

realm.Write(() =>
{
    person.FirstName = "John";
    person.Age = 56;
});

这里,默认情况下将提交隐式事务,除非抛出异常。

另请参阅下面的线程下Realm.WriteAsync()讨论

创建对象

定义模型后,可以实例化RealmObject子类并将新实例添加到Realm。考虑这个简单的模型:

public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
}

您可以使用Realm.Add()方法此类的实例转换为持久的活动对象

realm.Write(() =>
{
    var myDog = new Dog();
    myDog.Name = "Rex";
    myDog.Age = 10;
    realm.Add(myDog);
});

创建对象后,您对其所做的所有更改都将保留(并且必须在写入事务中进行)。在提交写入事务时,对使用相同Realm的其他线程可以进行任何更改。

请注意,写入会相互阻塞,并且如果正在进行多次写入,则会阻止它们创建的线程。我们建议您将写入卸载到单独的线程。

由于Realm的MVCC架构,在写事务打开时不会阻止读取除非您需要同时从多个线程同时进行写入,否则您应该支持更大的写入事务,这些事务对许多细粒度的写入事务执行更多操作。

更新对象

您可以通过在写入事务中设置其属性来更新任何对象。

// Update an object with a transaction
using (var trans = realm.BeginWrite())
{
    author.Name = "Thomas Pynchon";
    trans.Commit();
}

对于已指定[PrimaryKey]的对象,可以update: truerealm.Add传入以添加传入的对象或更新现有对象:

public class Person : RealmObject
{
    [PrimaryKey]
    public int Id { get; set; }

    // ... other property declarations
}

realm.Write(() =>
{
    realm.Add(new Person
    {
        Id = 1,
        Name = "Kristian"
    });
});

var kristianWithC = new Person
{
    Id = 1,
    Name = "Christian"
};

realm.Write(() => realm.Add(kristianWithC, update: true));

如果指定update: true并传入一个没有a的类,则PrimaryKey该方法将无法找到要更新的对象,因此它的行为就像update: false传递一样。如果您的对象与其他RealmObject有关系,那么当它们PrimaryKey指定时会添加或更新它们,或者在它们不指定时添加或更新它们

var kristian = new Person { Id = 1, Name = "Kristian" };
var rex = new Dog { Id = 1, Name = "Rex" };
kristian.Dogs.Add(rex);

realm.Write(() => realm.Add(kristian));

var christian = new Person { Id = 1, Name = "Christian" };
christian.Dogs.Add(new Dog { Id = 1, Name = "Bethoven" });

realm.Write(() => realm.Add(christian, update: true));

var newName = kristian.Name; // Christian
var newDogName = kristian.Dogs.First().Name; // Bethoven

删除对象

将要删除的对象传递给写入事务中Realm.Remove方法。

var cheeseBook = realm.All<Book>().First(b => b.Name == "Cheese");

// Delete an object with a transaction
using (var trans = realm.BeginWrite())
{
    realm.Remove(cheeseBook);
    trans.Commit();
}

您还可以删除存储在Realm中的所有对象。请注意,Realm文件将在磁盘上保持其大小,以便有效地将该空间重用于将来的对象。

查询

查询实现标准LINQ语法。

您将使用realm.All <T>方法获取给定类型的所有对象的基本类型集合,然后应用Where或使用其他LINQ运算符来获取已过滤的集合。

请参阅LINQ支持页面上实现的LINQ的确切范围

流畅或扩展语法

var oldDogs = realm.All<Dog>().Where(d => d.Age > 8);

查询表达式语法

var oldDogs = from d in realm.All<Dog>() where  d.Age > 8 select d;

无论使用哪种语法,生成的集合都符合IQueryable接口,因此您可以进一步迭代或处理它:

foreach (var d in oldDogs)
{
    Debug.WriteLine(d.Name);
}

更多查询示例

如果您不习惯LINQ查询,这里有一些基本查询示例,让您习惯语法。坚持使用扩展语法,您可以看到如何编写基本查询。

要提取名为John或Peter的所有用户的列表,您需要编写:

var johnsAndPeters = realm.All<Person>().Where(p =>
    p.FirstName == "John" ||
    p.FirstName == "Peter");
var peopleList = johnsAndPeters.ToList();

第一个语句为您提供johnsAndPeters了一个实现的类的新实例IQueryable可用于查找名为“John”或“Peter”的用户。这是标准的LINQ实现 - 您将获得一个表示查询的对象。在进行需要迭代或计算结果的进一步调用之前,查询不会执行任何操作。

ToList调用,在这个例子中,触发关闭它映射直奔领域的核心查询。

复制对象- 您将获得匹配对象的引用列表,并直接使用与查询匹配的原始对象。

您可以使用标准C#foreach语句迭代查询结果,而不是将它们全部检索到列表中

foreach (var person in johnsAndPeters) // iterate query
{
    Debug.WriteLine(person.Name);
}

逻辑运算符

LINQ表达式中使用标准C#逻辑运算符来组合查询。

这包括使用括号对嵌套表达式进行分组的能力,其优先级如您在if语句中所期望的那样

var complexPeople = realm.All<Person>().Where(p =>
    p.LastName == "Doe" &&
    (p.FirstName == "Fred" || p.Score > 35));

按类型检索对象

要从Realm获取给定类型的所有对象,只需使用realm.All <T>()它返回IQueryable所查询的模型类的所有实例集合 - Person在上面的示例中。

然后,您可以选择进一步应用LINQ查询来限制集。请记住,在开始迭代集合之前,没有任何开销。

var ap = realm.All<Person>();  // this is all items of a type
var scorers = ap.Where(p => p.Score > 35);  // restrict with first search clause
var scorerDoe = scorers.Where(p => p.LastName == "Doe");  // effectively an AND clause

排序

标准LINQ子句OrderByOrderByDescendingThenBy,和ThenByDescending可被用于指定影响由所供给的对象的顺序多级排序Queryable从返回Realm.All或后续LINQ子句。

它们通过内部查询引擎工作,以提供有效的排序访问,而无需加载结果中的所有对象。

警告:如果使用该ToList子句提取对象列表,然后使用LINQ for ObjectsOrderBy在内存中进行排序确保只ToList在表达式的末尾使用

var highestScore = realm.All<Person>().OrderByDescending(p => p.Score).First();

var sortedPeople = realm.All<Person>()
    .OrderBy(p => p.LastName)
    .ThenBy(p => p.FirstName);

链接查询

由于结果永远不会被复制,而是根据请求计算,因此您可以非常高效地链接查询以逐步过滤数据:

var teenagers = realm.All<Person>().Where(p => p.Age >= 13 && p.Age <= 20);
var firstJohn = teenagers.Where(p => p.FirstName == "John").First();

限制结果

大多数其他数据库技术提供了从查询(例如SQLite中关键字)分页结果的功能LIMIT这通常是为了避免从磁盘中读取太多内容,或者一次将太多结果拉入内存中。

由于Realm中的查询是惰性的,因此根本不需要执行这种分页行为,因为Realm只会在显式访问后从查询结果中加载对象。

如果出于UI相关或其他实现原因,您需要查询中特定的对象子集,那么就像获取IQueryable对象一样简单,只读取您需要的对象。

虽然您可能想要使用LINQ Take,但尚不支持。添加后,它将枚举并实例化您在一次操作中请求的所有对象。

三界

境界是一个域数据库容器的一个实例。领域可以是本地的同步的。一个同步的领域使用领域对象服务器透明地与其他设备同步的内容。当您的应用程序继续使用同步Realm时,就像它是本地文件一样,该Realm中的数据可能会被具有该Realm写入权限的任何设备更新。在实践中,你的应用程序适用于任何一种境界同样的方式,虽然打开同步领域需要一个用户一个已经验证的对象服务器而这授权打开那个境界。

有关Realms的更详细讨论,请阅读Realm Data Model

开放的领域

只需通过实例化一个新Realm对象来打开一个领域我们已经在示例中看到过这种情况:

// Get a Realm instance
var realm = Realm.GetInstance();

如果没有参数,则GetInstance实例化Default Realm但是,您也可以指定一个RealmConfiguration对象。

配置领域

一个RealmConfiguration允许您通过多种方式控制数据库路径。(请注意,一旦构造了配置,就无法更改路径。)您可以:

  1. 通过传入新的绝对路径来更改整个路径。
  2. 通过传入相对路径将Realm文件放在标准位置的子目录中。
  3. 只需传入一个新文件名即可更改Realm文件名。
var config = new RealmConfiguration("OtherPath.realm");
var otherRealm = Realm.GetInstance(config);

在配置中,您还可以指定架构版本。有关此内容的详细信息,请参阅“ 迁移”部分。在开发时,您还可以将属性ShouldDeleteIfMigrationNeeded设置true这将导致Realm.GetInstance()删除现有数据库,以防您的模式与正在打开的文件中的模式不匹配。这使得在发布之前更容易使用模型。但是,请勿使用此标志发布应用程序。您可能希望将其设置在一个#if DEBUG部分内以避免发生事故。

您可以传递配置的实例,以使用相同的设置打开所有领域。这是在不同线程中打开相同Realm的最常见用法。

您还可以覆盖默认配置以更改默认值,而无需传递对象。

值得注意的是,Realm实例是每个配置的每线程单例。这意味着每次在相同的线程上为相同的配置调用Realm.GetInstance()时,它将返回相同的实例。

默认领域

不传递任何参数的情况下调用Realm.GetInstance()将返回“默认Realm”实例。默认领域简单地映射到一个名为default.realm位于Environment.SpecialFolder.Personal

内存领域

使用InMemoryConfiguration配置,您可以创建一个完全在内存中运行而不会持久保存到磁盘的Realm:

var config = new InMemoryConfiguration("some-identifier");
var realm = Realm.GetInstance(config);

如果内存不足,内存领域仍可能使用磁盘空间,但在关闭领域时,内存领域创建的所有文件都将被删除。不允许使用与持久化域相同的标识符创建内存域 - 标识符仍然必须是唯一的。

当处理具有特定标识符的所有内存中Realm实例或者对其进行垃圾收集时,这将释放所有Realm的数据。要在应用程序执行期间保持内存中的Realm“活着”,请保留对它的引用。

异步开放领域

如果打开Realm可能需要耗时的操作,例如应用迁移压缩或下载同步Realm的远程内容,则应使用Realm.GetInstanceAsync API执行将Realm置于可用状态所需的所有工作完成返回之前的后台线程Task<Realm>您还应该使用Realm.GetInstanceAsyncRealms,用户只具有读权限。

例如:

var config = new RealmConfiguration
{
    SchemaVersion = 1,
    MigrationBlock = (migration, oldSchemaVersion) =>
    {
        // potentially lengthy data migration
    }
};

try
{
    var realm = await Realm.GetInstanceAsync(config);
    // Realm successfully opened, with migration applied on background thread
}
catch (Exception ex)
{
    // Handle exception that occurred while opening the Realm
}

关闭领域实例

领域类实现IDisposable以照顾本地内存释放的和文件描述符,所以当变量超出作用域实例将自动关闭。

查找领域文件

如果您在查找应用程序的Realm文件时需要帮助,请查看此StackOverflow答案以获取详细说明。

辅助领域文件

除标准.realm文件外,Realm还为其内部操作生成并维护其他文件。

  • .realm.lock - 资源锁的锁文件。
  • .realm.note - 用于通知的命名管道。

这些文件对.realm数据库文件没有任何影响,如果删除或替换父数据库文件,则不会导致任何错误行为。

报告Realm问题时,请务必将这些辅助文件与主.realm文件一起包含在内,因为它们包含用于调试目的的有用信息。

将领域与应用程序捆绑在一起

如果要将Realm与应用程序捆绑在一起,请参阅此StackOverflow答案该答案解释了如何将Xamarin项目中的文件作为资源包含并复制它们以供使用。

类子集

在某些情况下,您可能希望限制哪些类可以存储在特定领域中。

您可以通过设置RealmConfigurationObjectClasses属性来完成此操作

class LoneClass : RealmObject
{
    public string Name { get; set;}
}

var config = new RealmConfiguration("RealmWithOneClass.realm");
config.ObjectClasses = new[] { typeof(LoneClass) };

// or specifying two classes in the Realm
config.ObjectClasses = new[] { typeof(Dog), typeof(Cat) };

压缩领域

在使用您的应用程序的过程中,Realm使用的内存可能会碎片化,占用的空间超出了必要的范围。要重新排列内部存储并可能减少文件大小,可以通过在打开Realm文件时指定RealmConfiguration.ShouldCompactOnLaunch回调压缩 Realm文件:

var config = new RealmConfiguration
{
    ShouldCompactOnLaunch = (totalBytes, usedBytes) =>
    {
        // totalBytes refers to the size of the file on disk in bytes (data + free space)
        // usedBytes refers to the number of bytes used by data in the file

        // Compact if the file is over 100MB in size and less than 50% 'used'
        var oneHundredMB = 100 * 1024 * 1024;
        return totalBytes > oneHundredMB && (double)usedbytes / totalBytes < 0.5;
    }
};

try
{
    var realm = Realm.GetInstance(config);
}
catch (Exception e)
{
    // handle error compacting or opening Realm
}

如果trueShouldCompactOnLaunch委托返回并且当前未使用Realm文件则将对域进行压缩

或者,您可以通过调用Realm.Compact(config)来压缩文件而无需获取Realm实例

var config = new RealmConfiguration("my.realm");
Realm.Compact(config);

压缩注意事项

  • 不应该有任何使用该文件的开放领域实例。
  • 文件系统应至少有一个Realm文件副本的可用空间。
  • 如果任何操作失败,则Realm文件保持不变。
  • true如果操作成功并且false存在打开的Realm实例访问同一文件该方法将返回
  • 目前,我们不提供用于通过压缩领域来估算潜在大小增益的API。建议的方法是在启动之间监视数据库的大小,如果超过某个阈值则压缩它。

删除领域文件

在某些情况下,例如清除缓存或重置整个数据集,从磁盘中完全删除Realm文件可能是合适的。

与大多数文件不同,Realm文件是内存映射的,Realm实例希望文件在实例的生命周期内可用。

为了避免您的应用程序代码必须知道Realm中的所有文件,我们提供了一种方便的方法Realm.DeleteRealm(RealmConfiguration)

var config = new RealmConfiguration("FileWeThrowAway.realm");
Realm.DeleteRealm(config);
var freshRealm = Realm.GetInstance(config);

穿线

在单个线程中,您可以将所有内容视为常规对象,而无需担心并发或多线程。不需要任何锁或资源协调来访问它们(即使它们同时在其他线程上被修改),并且它只修改必须包含在事务中的操作。

通过确保每个线程始终具有一致的Realm视图,Realm使并发使用变得容易。您可以在同一个Realms上并行处理任意数量的线程,并且因为它们都有自己的快照,所以它们永远不会导致彼此看到不一致的状态。

您唯一需要注意的是,您不能让多个线程共享相同的Realm对象实例如果多个线程需要访问相同的对象,则每个线程都需要获取自己的实例(否则在一个线程上发生的更改可能会导致其他线程看到不完整或不一致的数据)。

从其他线程看到变化

在主UI线程(或任何带有runloop / looper的线程)上,对象将在runloop的每次迭代之间自动更新来自其他线程的更改。在任何其他时间,您将处理快照,因此各个方法始终可以看到一致的视图,而不必担心其他线程上发生的情况。

当您最初在线程上打开Realm时,其状态将基于最近成功的写入提交,并且它将保留在该版本上直到刷新。领域在每次runloop迭代开始时自动刷新。如果一个线程没有runloop(后台线程通常就是这种情况),那么必须手动调用Realm.Refresh()才能将事务推进到最近的状态。

使用Transaction.Commit()提交写入事务时,域也会刷新

未能定期刷新Realms可能导致某些事务版本变为“固定”,从而阻止Realm重用该版本使用的磁盘空间,从而导致更大的文件大小。有关此效果的更多详细信息,请参阅我们的当前限制

跨线程传递实例

的持久性实例境界RealmObjectIQueryable从返回Realm.All,或IList特性RealmObject s只能在它们被创建的线程上被使用,否则则抛出异常。这是Realm强制执行事务版本隔离的一种方式。否则,当在没有可能广泛的关系图的情况下在不同事务版本的线程之间传递对象时,将无法确定应该做什么。

Realm公开了一种机制,可以通过三个步骤安全地传递线程限制的实例:

  1. 通过使用线程限制对象调用ThreadSafeReference.Create重载之一来获取ThreadSafeReference
  2. ThreadSafeReference传递给目标线程或队列。
  3. 通过调用Realm.ResolveReference重载之一来解析目标Realm上的此引用像往常一样使用返回的对象。

例如:

var person = new Person { Name = "Jane" };
realm.Write(() => realm.Add(person));

var personReference = ThreadSafeReference.Create(person);

Task.Run(() =>
{
    var otherRealm = Realm.GetInstance(realm.Config);
    var otherPerson = otherRealm.ResolveReference(personReference);
    if (otherPerson == null)
    {
        return; // person was deleted
    }

    realm.Write(() =>
    {
        otherPerson.Name = "Jane Doe";
    });
});

一个ThreadSafeReference对象必须最多一次可以解决。如果未能解析它将导致Realm的源版本被固定,直到引用被释放为止。因此,ThreadSafeReference应该是短暂的。

ThreadSafeReference提供了用于包装RealmObjectIList<RealmObject>IQueryable<RealmObject它们应该仅用于持久化实例,因为它们的独立版本本质上不是线程限制的。

跨线程使用领域

要从不同的线程访问同一个Realm文件,您必须初始化一个新的Realm,以便为您的应用程序的每个线程获取不同的实例。只要指定相同的配置,所有Realm实例都将映射到磁盘上的同一文件。

支持跨线程共享Realm实例访问同一域文件的域实例也必须全部使用相同的RealmConfiguration

异步写入

所述Realm.WriteAsync()方法是一种特殊形式的Write,其可以很容易地通过执行卸载UI线程上的后台线程写入。

警告: WriteAsync打开一个临时的Realm实例,该实例作为参数传递给lambda,如下例所示。注意只使用lambda中的传入实例而不是原始线程的域。由于lambdas捕获调用上下文的方式,未能更改此代码将不会得到编译器错误,而是抛出运行时异常。

await realm.WriteAsync(tempRealm =>
{
    var pongo = tempRealm.All<Dog>().Single(d => d.Name == "Pongo");
    var missis = tempRealm.All<Dog>().Single(d => d.Name == "Missis");
    for (var i = 0; i < 15; i++)
    {
        tempRealm.Add(new Dog
        {
            Breed = "Dalmatian",
            Mum = missis,
            Dad = pongo
        });
    }
});

当其线程操作完成时,WriteAsync还调用Realm.Refresh()来提升原始(通常是UI)线程读取状态该线程将立即收到后台线程中任何更改的通知。

使用WriteAsync从你知道已经在工作线程上运行的代码没有意义,因为它只会将调用转发到Write而不创建额外的线程。但是,从任何线程使用都是非常安全的,并且不会增加任何有意义的开销。重要的是要考虑在后台线程上,WriteAsync将在当前的 Realm实例上运行,而不是创建新的实例。

通常,由于领域写入非常快,我们建议使用Write进行接口驱动的更改(例如,用户键入一些字符串,或检查复选框),其中一次只有少数值发生变化。WriteAsync在有数百或数千次更改的情况下非常有用(例如,从网络中获取对象或批量更新多个对象),并且同步执行这些更改可能会导致界面抖动。

通知

在Android上,更改侦听器仅适用于Looper线程。对于非looper线程,您必须在打开Realm之前手动调用Realm.Refresh()在线程上安装SynchronizationContext可用于简化Nito.AsyncEx.Context的第三方库

领域通知

如果你有一个后台线程向Realm添加数据,你的UI或其他线程可以通过添加一个监听器来获得Realm中的更改通知,该监听器在Realm发生更改时执行(也由另一个线程或进程):

realm.RealmChanged += (s, e) =>
{
    // Update UI
}

请记住,RealmChanged的当前实现不提供更改的详细信息,因此如果需要,请检查“ 对象通知”或“ 集合通知”部分。

对象通知

RealmObject实现了INotifyPropertyChanged,因此如果要监听更改,可以订阅PropertyChangedEvent事件。

public class Account : RealmObject
{
    public long Balance { get; set; }
}

如果您在类上订阅了PropertyChangedEvent事件,Balance则在更改属性时将触发您的处理程序

var account = new Account();
realm.Write(() => realm.Add(account));
account.PropertyChanged += (sender, e) =>
{
    Debug.WriteLine($"New value set for {e.PropertyName}");
}

realm.Write(() => account.Balance = 10); // => "New value set for Balance"

请注意,在将对象添加到Realm之前或之后进行订阅并不重要。在任何一种情况下,该事件都将按预期提出。

这本身就很方便,但此外,它还可以使用Xamarin.Forms进行数据绑定。有关更多信息,请参阅Xamarin文档中的从数据绑定到MVVM

收集通知

IQueryableRealm.All <T>()或后续LINQ子句返回的对象是实时查询,这意味着它们始终保持最新。无论何时迭代集合,您都将获得最新更新的结果。类似地IListRealmObject上属性表示实时多对多关系,这意味着每次迭代它们时,您将获得最新的相关对象集合。

支持这两个集合的运行时对象也实现了IRealmCollection <T>,这意味着它公开了两种订阅更改通知的机制。我们提供了一个方便的扩展方法:AsRealmCollection将铸就IList<T>IQueryable<T>IRealmCollection<T>

如果您更喜欢使用与MVVM和数据绑定很好地结合的标准.NET INotifyCollectionChanged接口,则可以将IRealmCollection <T>直接传递给您的视图。

或者,如果您希望获得更详细的更改信息,这对于操作UITableViewListView直接操作(例如在Xamarin Native UI项目中),则可以使用SubscribeForNotifications扩展方法。您为此方法提供了一个委托,该委托将使用更改集进行调用,告诉您已添加,删除或修改的内容。

var token = realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
    // Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});

// Later, when you no longer wish to receive notifications
token.Dispose();

迁移

使用任何数据库时,您的数据模型可能会随着时间的推移而发生变化。由于Realm中的数据模型被定义为标准C#类,因此进行模型更改就像更改任何其他类一样简单。例如,假设我们有以下Person模型:

public class Person : RealmObject
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

我们希望更新数据模型以要求FullName属性,而不是分隔名和姓。为此,我们只需将对象界面更改为以下内容:

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

此时,如果您使用以前的型号版本保存了任何数据,则Realm在代码中定义的内容与Realm在磁盘上看到的数据之间将存在不匹配。发生这种情况时,除非您运行迁移,否则在尝试打开现有文件时将引发异常。

执行迁移

您可以执行的最小解决方案是提升架构版本。未指定时,Realms将架构版本设置为0.只要更改架构,就必须在配置中增加架构版本。这样,我们可以避免可能会破坏数据的意外架构更改。

var config = new RealmConfiguration() { SchemaVersion = 1 };

var realm = Realm.GetInstance(config);

现在,如果您只执行此操作并使用使用旧架构创建的数据库文件运行它,则将删除所有FirstNameLastName条目,并且每个条目都Person将具有空FullName属性。这可能不是你想要的。所以你必须告诉Realm如何处理迁移。

我们通过指定MigrationCallback函数来完成此操作此函数将接收一个Migration对象,该对象将包含两个Realm属性:OldRealmNewRealm您现在可以复制和修改旧架构中的数据到新架构。

请注意,如果您只是重命名一个类或属性,Realm将无法检测到这一点,您也必须在迁移中复制数据。

当打开具有较低版本的Realm时,将仅调用此回调函数一次。您应该在该调用期间迁移所有更新的类。

由于我们的Person模型不再包含FirstNameLastName属性,我们无法通过类型化的API访问它们。但是,我们可以使用动态API并实现相同的功能。

var config = new RealmConfiguration
{
    SchemaVersion = 1,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();

        // Use the dynamic api for oldPeople so we can access
        // .FirstName and .LastName even though they no longer
        // exist in the class definition.
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
        }
    }
};

var realm = Realm.GetInstance(config);

随着您的应用程序变得越来越老,并且您在不同版本中对模型进行了多处更改,您可以通过检查oldSchemaVersion回调参数来进行一系列迁移假设您Person通过将Age字段更改为字段来进一步更改模型Birthday

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
    public DateTimeOFfset Birthday { get; set; }
}

这是架构的第2版,您的迁移设置现在看起来如下所示:

var config = new RealmConfiguration
{
    SchemaVersion = 2,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            // Migrate Person from version 0 to 1: replace FirstName and LastName with FullName
            if (oldSchemaVersion < 1)
            {
                newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
            }

            // Migrate Person from version 1 to 2: replace Age with Birthday
            if (oldSchemaVersion < 2)
            {
                newPerson.Birthday = DateTimeOffset.Now.AddYears(-(int)oldPerson.Age);
            }
        }
    }
};

var realm = Realm.GetInstance(config);

加密

请注意我们许可证的出口合规部分,因为如果您位于有美国出口限制或禁运的国家/地区,它会对使用Realm进行限制。

Realm支持在创建Realm时通过提供64字节加密密钥,使用AES-256 + SHA2加密磁盘上的数据库文件。

var config = new RealmConfiguration("Mine.realm");
config.EncryptionKey = new byte[64] // key MUST be exactly this size
{
  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
};

var realm = Realm.GetInstance(config);  // will create/open encrypted realm "Mine.realm"

这使得存储在磁盘上的所有数据都可以根据需要使用AES-256进行透明加密和解密,并使用SHA-2 HMAC进行验证。每次获得Realm实例时都必须提供相同的加密密钥。

您应该使用应用程序独有的密钥,而不是上面示例中的密钥此外,如果您需要为每个用户提供唯一密钥,请查看Xamarin.Auth API

请注意,如果指定了错误的密钥或未能为加密的Realm指定密钥,在调用GetInstance时会出现RealmFileAccessErrorException

使用加密领域时,性能受到很小影响(通常低于10%)。

同步

您是否希望使用Realm Mobile Platform同步所有Realm数据库?所有与同步相关的文档已移至我们的平台文档中

例子

您可以在examples文件夹中找到我们的存储库中的许多示例

目前的限制

Realm通常会尝试尽可能少的限制,我们会根据社区的反馈不断添加新功能。但是,Realm仍有一些局限性。这些已编译如下。

有关已知问题的更全面列表,请参阅我们的GitHub问题。

一般限制

Realm旨在在灵活性和性能之间取得平衡。为了实现该目标,对在域中存储信息的各个方面施加了实际限制。例如:

  1. 类名的长度必须介于0到57个字节之间。支持UTF-8字符。如果超出此限制,将在您的应用初始化时抛出异常。
  2. 属性名称的长度必须介于0到63个字节之间。支持UTF-8字符。如果超出此限制,将在您的应用初始化时抛出异常。
  3. iOS限制:所有打开的Realm文件的总大小不能大于允许应用程序在iOS中映射的内存量 - 这会更改每个设备,并取决于该时间点内存空间的碎片程度(那里)是关于这个问题的雷达:rdar:// 17119975)。如果需要存储更多数据,可以拆分为多个Realm文件,并根据需要打开和关闭它们。

主题

尽管Realm文件可以由多个线程同时访问,但您无法在线程之间移交Realms,Realm对象,查询和结果。阅读有关Realm线程的更多信息。

文件大小和中间版本的跟踪

您应该期望Realm数据库在磁盘上占用的空间少于等效的SQLite数据库。如果您的Realm文件比预期的要大得多,可能是因为您有一个RealmObject指的是数据库中较旧版本的数据。

为了给您一致的数据视图,Realm只更新在运行循环迭代开始时访问的活动版本。这意味着如果您从Realm读取一些数据,然后在其他线程上写入Realm时在长时间运行的操作中阻塞该线程,则该版本永远不会更新,并且Realm必须保留您的数据的中间版本可能实际上并不需要,导致每次写入时文件大小增加。额外的空间最终将被未来的写作重用。

如果您希望确定性地回收空间,请参阅压缩领域

常问问题

Realm开源吗?

是! Realm的内部C ++存储引擎及其上的语言SDK完全是开源的,并在Apache 2.0下获得许可。Realm还可选择包含一个闭源Realm Platform Extensions组件,但不需要将Realm用作嵌入式数据库。

我在构建应用程序时看到了对Mixpanel的网络调用,那是什么?

当您在包含RealmObject的程序集上运行Realm程序集编织器时,Realm会收集匿名分析这是完全匿名的,可以通过标记您使用的Realm版本以及您使用的操作系统以及我们可以弃用支持的内容来帮助我们改进产品。当您的应用程序处于生产阶段或在用户的设备上运行时,此调用不会运行 - 仅当您的程序集在构建期间由Fody编织时。您可以在我们的源代码中查看我们收集的具体方式和内容,以及它的基本原理

从Realm 3.x升级

Realm 4.x简化了Realm包的体系结构,因此它使一些依赖关系变得过时。要更新和清理项目,请按照下列步骤操作:

  1. Realm程序包更新到最新版本。
  2. 删除Realm.DatabaseRealm.DataBinding包。
  3. 删除$(SolutionDir)/Tools文件夹 - 它以前包含不再使用的Realm weaver的副本。
  4. Realm weaver的名字改为just Realm编辑您的FodyWeavers.xml文件以更改<RealmWeaver />为just <Realm />
  5. 查看更改日志,了解可能影响项目的更改并相应地更新代码。

故障排除

崩溃报告

我们鼓励您在应用程序中使用崩溃报告器。许多Realm操作可能在运行时失败(与任何其他磁盘IO一样),因此从应用程序收集崩溃报告将有助于确定您(或我们)可以改进错误处理和修复崩溃错误的区域。

大多数商业崩溃记者都可以选择收集日志。我们强烈建议您启用此功能。在抛出异常和不可恢复的情况时,Realm会记录元数据信息(但没有用户数据),这些消息可以在出现问题时帮助调试。

报告领域问题

如果您发现Realm存在问题,请在GitHub上提交问题或发送电子邮件至help@realm.io,尽可能多地了解我们以了解并重现您的问题。

以下信息对我们非常有用:

  1. 目标。
  2. 预期成绩。
  3. 实际结果。
  4. 重现步骤。
  5. 突出问题的代码示例(我们可以编译的完整项目是理想的)
  6. Realm,macOS或Windows&Visual Studio的版本。
  7. Xcode如果定位iOS,NDK如果定位到Android。
  8. 发生错误的平台,操作系统版本和体系结构(例如,64位iOS 8.1)。
  9. 崩溃日志和堆栈跟踪。有关详情,请参阅上面的崩溃报告

在类异常中获取No属性

您可能会看到一条System.InvalidOperationException消息“ 类中没有属性,链接器是否剥离了它?”。

有三个已知原因:

  1. 你要么没有编织的RealmObjects,可能是因为Fody出了问题,或者
  2. 您的RealmObjects的属性被剥离,因此Realm看起来是空的。
  3. 您正在使用某种代码混淆工具干扰模型名称检测(非Xamarin平台)

在第一种情况下,RealmSchema将抛出异常有关详细信息,请参阅失败编织

在第二种情况下,异常将从ObjectSchema抛出链接器行为设置为“全部链接”时,我们遇到了一些用户遇到问题,并且他们缺少[Preserve(AllMembers = true)]类声明属性。链接器将仅保留代码中显式引用的成员。这意味着如果您有一个可以持久保存但未在任何地方引用的属性,则可能会将其删除,从而导致架构与数据库的架构不匹配。

在第三种情况下,抛出异常,因为混淆工具导致我们的库无法检测到类和属性名称。我们依靠这些类名来生成模式。要解决此问题,请将代码混淆工具设置为忽略模型类,以免混淆。

默认情况下,境界构建模式来描述所有你的RealmObject中所有组件的子类。这种情况懒洋洋,因此不会被触发,直到你的第一次调用的GetInstance() 它每次运行最多只执行一次,将结果缓存在内存中。

对于给定的程序集,如果您只想存储某些类,可以使用ObjectClasses指定子集并在GetInstance(myConf)中使用该配置这避免了所有人的架构构建,因此也将避免异常。

否则,如果您有未使用的类,请添加该Preserve属性。

Fody:发生了未处理的异常

当您已经构建了一个项目并且只添加了一个新的RealmObject子类时,可以很容易地触发这种常见的构建失败

选择Build或Run不会足以重建项目来调用Fody Weaver。

只需在您的项目中选择Rebuild,它应该构建没有错误。

没有编织

您可能会在构建日志中看到有关未编织的类的警告。这表明Fody编织包装未正确安装。

首先,检查FodyWeavers.xml文件是否包含条目Realm

Fody的安装也可能失败了。这在Visual Studio 2015和版本3.2之前的NuGet Package Manager版本中经历过。要诊断这一点,请使用文本编辑器检查您.csproj是否有导入行Fody.targets,例如:

<Import Project="..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets"
    Condition="Exists('..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" />

只需升级到更高版本的NuGet Package Manager即可解决此问题。

如果这不起作用,那么Fody似乎有问题Microsoft.Bcl.Build.targets从.csproj文件中删除以下行可能会有所帮助:

<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />

有关此内容的更多详细信息,请参阅此StackOverflow答案

WriteAsync问题疑难解答

在里面WriteAsync,我们检查一个非null的SynchronizationContext.Current来指示你是否可能在UI线程上。这种检查可能会被人们也在Current其工作线程中设置的完善的编程模式所混淆,因此他们可以使用Post方法在UI线程上运行代码。这种模式可以追溯到2007年的MSDN博客帖子

如果你已经设置它不是问题,SynchronisationContext.Current但它会导致WriteAsync在线程池上再次分派,这可能会创建另一个工作线程。因此,如果您Current在线程中使用,请考虑调用Write而不是WriteAsync

领域核心二进制文件无法下载

构建 Realm时,该过程的一部分包括将核心库作为静态二进制文件下载并将其集成到Realm项目中。这是wrappers建筑物的一部分Makefile

据报道,在某些情况下,核心二进制文件无法下载,并出现以下错误:

Downloading core failed. Please try again once you have an Internet connection.

由于以下任何原因可能会发生此错误:

  1. 您的IP地址范围来自美国禁运列表中的区域为了遵守美国法律,尚未在该地区提供Realm。有关更多信息,请参阅我们的许可证
  2. 您位于中国大陆,由于国家/地区的防火墙,您目前无法正确访问CloudFlare或Amazon AWS S3服务。有关更多信息,在我们的其他产品上查看此问题
  3. Amazon AWS S3可能遇到服务问题。请查看AWS Service Health仪表板,稍后再试。
posted @ 2019-07-10 19:44  为敢技术  阅读(762)  评论(0编辑  收藏  举报