Flutter SDK支持 Dart 语言数据类型、 BSON 类型的有限子集和 UUID。
要了解如何将特定数据类型映射到 App Services 模式中的 BSON 类型,请参阅 Atlas App Services 文档中的数据模型映射。
Dart 类型
Realm 支持以下 Dart 类型:
intdoubleboolStringDateTime
日期时间
当您在 Realm Flutter SDK 中使用 DateTime 时,您可以在模型中声明该类型,就像声明任何其他 Dart 类型一样:
() class _Vehicle { () late ObjectId id; late String nickname; late DateTime dateLastServiced; }
但是,值得重点注意的是,Realm 以 UTC 格式存储 DateTime。当使用 DateTime 时,您必须在 UTC 中创建它,或在存储之前使用 .toUtc() 进行转换。如果您的应用程序需要,您可以在从 Realm 读取时将其转换回本地或目标时区。
// Create a Realm object with date in UTC, or convert with .toUtc() before storing final subaruOutback = realm.write<Vehicle>(() { return realm.add( Vehicle(ObjectId(), 'Subie', DateTime.utc(2022, 9, 18, 12, 30, 0))); }); final fordFusion = Vehicle(ObjectId(), 'Fuse', DateTime(2022, 9, 18, 8, 30, 0).toUtc()); realm.write(() { realm.add(fordFusion); }); // When you query the object, the `DateTime` returned is UTC final queriedSubaruOutback = realm.all<Vehicle>().query('nickname == "Subie"')[0]; // If your app needs it, convert it to Local() or the desired time zone final localizedSubieDateLastServiced = queriedSubaruOutback.dateLastServiced.toLocal();
引用 Realm 对象
您也可以从一个对象中引用另外一个或多个 Realm 对象。有关详细信息,请参阅关系属性文档。
集合
一个 Realm 集合包含零个或多个 Realm 支持的数据类型的实例。在 Realm 集合中,集合中的所有对象均为同一类型。
您可以使用 Realm 的查询语言对任意集合进行过滤和排序。集合是实时对象,因此它们始终反映 Realm 实例的当前状态。当在集合或其 Realm 中添加新元素或删除元素时,集合的内容会更新。
您还可以订阅变更通知以监听集合中的更改。
Realm 具有以下类型的集合:
RealmList
Realm 对象可以包含任何受支持数据类型的列表。Realm 使用 RealmList 数据类型来存储数据。
当您将 RealmObjects 作为 RealmList 中的项目包含在内时,它表示对多关系。
如果从数据库中删除对象,则会将其从所在的任何 RealmList 中删除。因此,RealmObject 类型的 RealmList 绝不会包含 null 值。此外,RealmList 可以包含对同一 RealmObject 的多个引用。
基元类型的 RealmList 可以包含 null 值。如果您不想在列表中允许 null 值,则可以在列表声明中使用不可为 null 的类型(例如,使用 List<int> 而不是 List<int?>)。
RealmList 是可变的,您可以在写事务中添加和删除 RealmList 上的元素。
将 RealmList 添加到模式
您可以在您的 Realm 对象模式中添加一个 RealmList,方法是在您的 Realm 对象模型中定义一个类型为 List<T> 的属性,其中 T 可以是任何受支持的 Realm 数据类型(其他集合除外)。
() class _Player { () late ObjectId id; late String username; // `inventory` property of type RealmList<Item> // where Items are other RealmObjects late List<_Item> inventory; // `traits` property of type RealmList<String> // where traits are Dart Strings. late List<String> traits; } () class _Item { () late ObjectId id; late String name; late String description; }
使用 RealmList
在版本 2.0.0 中进行了更改:使用 Dynamic.getList() 按属性名称获取 RealmList
以下示例演示了 RealmList 的一些基本用法。有关所有可用方法的更多信息,请参阅RealmList 参考文档。
final artemis = realm.write(() => realm.add(Player(ObjectId(), 'Art3mis', inventory: [ Item(ObjectId(), 'elvish sword', 'sword forged by elves'), Item(ObjectId(), 'body armor', 'protects player from damage'), ], traits: [ 'brave', 'kind' ]))); // Get a RealmList by property name with dynamic.getList() final inventory = artemis.dynamic.getList('inventory'); // Use RealmList methods to filter results RealmList<String> traits = artemis.traits; final brave = traits.firstWhere((element) => element == 'brave'); final elvishSword = artemis.inventory.where((item) => item.name == 'elvish sword').first; // Query RealmList with Realm Query Language final playersWithBodyArmor = realm.query<Player>("inventory.name == \$0", ['body armor']); print("LEN ${playersWithBodyArmor.length}");
RealmSet
Realm对象可以包含任何受支持的数据类型集,但其他集合除外。Realm使用 RealmSet 数据类型来存储数据。在 RealmSet 集合中,所有值都是唯一的。RealmSet 使用其他特定于 Realm 的属性和方法扩展了原生Dart Set 数据类型。
当您将 RealmObjects 作为 RealmSet 中的项目包含在内时,它表示对多关系。
RealmSet 是可变的,您可以在写事务(write transaction)中添加和删除 RealmSet 上的元素。
在模式中添加 RealmSet
您可以在您的 Realm 对象模式中添加一个 RealmSet,方法是在 Realm 对象模型中定义一个类型为 Set<T> 的属性,其中 T 可以是任何受支持的 Realm 数据类型,但其他集合除外。
在模式中定义 RealmSet 时:
可以将一组基元类型构成的集合(set)定义为可为 null 或不可为 null。例如,
Set<int>和Set<int?>在 Realm 模式中均有效。RealmObject和RealmValue类型的集合只能是非空的。 例如,Set<RealmValue>有效,但Set<RealmValue?>无效。在模式中定义集合(set)时,不能定义默认值。例如,
Set mySet = {0,1,2}是无效的。
() class _RealmSetExample { late Set<String> primitiveSet; late Set<int?> nullablePrimitiveSet; late Set<_SomeRealmModel> realmObjectSet; } () class _SomeRealmModel { late ObjectId id; }
使用 RealmSet
在版本 2.0.0 中进行了更改:使用 Dynamic.getSet() 按属性名称获取 RealmSet
以下示例演示了 RealmSet 的一些基本用法。有关所有可用方法的更多信息,请参阅RealmSet 参考文档。
final realm = Realm( Configuration.local([RealmSetExample.schema, SomeRealmModel.schema])); // Pass native Dart Sets to the object to create RealmSets final setExample = RealmSetExample( primitiveSet: {'apple', 'pear'}, nullablePrimitiveSet: {null, 2, 3}, realmObjectSet: {SomeRealmModel(ObjectId())}); // Add RealmObject to database realm.write(() => realm.add(setExample)); // Once you add the sets, they are of type RealmSet RealmSet primitiveSet = setExample.primitiveSet; // Modify RealmSets of RealmObjects in write transactions realm.write(() { // Add element to a RealmSet with RealmSet.add() setExample.realmObjectSet.add(SomeRealmModel(ObjectId())); // Remove element from a RealmSet with RealmSet.remove() setExample.primitiveSet.remove('pear'); }); // Check if a RealmSet contains an element with RealmSet.contains() if (setExample.primitiveSet.contains('apple')) { print('Set contains an apple'); } // Get RealmSet by property name with dynamic.getSet() final getSetResult = setExample.dynamic.getSet('primitiveSet'); // Check number of elements in a RealmSet with RealmSet.length print( 'Set now has ${getSetResult.length} elements'); // Prints 'Set now has 1 elements' // Query RealmSets using Realm Query Language final results = realm.query<RealmSetExample>('\$0 IN nullablePrimitiveSet', [null]);
RealmMap
1.7.0 版本的新增功能。
RealmMap 是一个包含 <String, T> 键值对的集合,其中 T 是 SDK 支持的任何数据类型。除非使用百分比编码,否则映射键不能包含. 或以$ 开头。
RealmMap 是可变的,您可以通过写事务在 RealmMap 中添加和删除元素。您可以使用更改侦听器侦听 RealmMap 条目更改。
在模式中添加 RealmMap
您可以在您的 Realm 对象模式中添加一个 RealmMap,方法是在您的 Realm 对象模型中定义一个类型为 RealmMap<String, T> 的属性,其中 T 可以是任何受支持的 Realm 数据类型(其他集合除外)。
() class _MapExample { late Map<String, int> map; late Map<String, int?> nullableMap; }
使用 RealmMap
在版本 2.0.0 中进行了更改:使用 Dynamic.getMap() 按属性名称获取 RealmMap
以下示例演示了 RealmMap 的一些基本用法。有关所有可用方法的更多信息,请参阅 RealmMap 参考 文档。
final realm = Realm(Configuration.local([MapExample.schema])); // Pass native Dart Maps to the object to create RealmMaps final mapExample = MapExample( map: { 'first': 1, 'second': 2, 'third': 3, }, nullableMap: { 'first': null, 'second': 2, 'third': null, }, ); // Add RealmObject to the database realm.write(() => realm.add(mapExample)); // Once you add maps, they are of type RealmMap RealmMap map = mapExample.map; // Modify RealmMaps in write transactions realm.write(() { // Update value by key with .update() or [value] = newValue mapExample.nullableMap['second'] = null; mapExample.map.update('first', (value) => 5); mapExample.nullableMap.update('fourth', (v) => 4, ifAbsent: () => null); // Add a new map entry with .addEntries() const newMap = {'fourth': 4}; mapExample.map.addEntries(newMap.entries); }); // Check a RealmMap with .containsKey() or .containsValue() if (mapExample.map.containsKey('first')) { print('Map contains key "first"'); } else if (mapExample.map.containsValue(null)) { print('Map contains null value'); } else { print('These aren\'t the maps you\'re looking for'); } // Get a RealmMap by property name with dynamic.getMap() final getPrimitiveMap = mapExample.dynamic.getMap('map'); // Check the number of elements in a RealmMap with RealmMap.length print( 'Map contains ${getPrimitiveMap.length} elements'); // Prints 'Map contains 4 elements' // Query RealmMaps using Realm Query Language final results = realm.query<MapExample>('map.first == \$0', [5]);
RealmResults
A RealmResults 集合表示查询操作的延迟计算结果。与 RealmList 不同,结果是不可变的:您无法在结果集合上添加或删除元素。这是因为结果集合的内容是由对数据库的查询确定的。
Realm.all() 和Realm. 查询()返回 RealmResults。有关查询 Realm 的更多信息,请参阅读取操作。
RealmResults<Player> players = realm.all<Player>(); RealmResults<Player> bravePlayers = realm.query<Player>('ANY traits == \$0', ['brave']);
结果为延迟计算出的结果
Realm 仅在您实际请求该查询的结果时才会运行查询,例如通过访问结果集合的元素。此延迟求值机制可让您编写优雅、高性能的代码来处理大型数据集和复杂查询。
集合的实时性
与活动对象一样,Realm 集合通常也为实时集合:
实时结果集合始终会反映关联查询的当前结果。
RealmObjects的实时列表始终反映 Realm 实例上关系的当前状态。
但是,集合在两种情况下会处于非实时状态:
集合是非托管的:它是 Realm 对象的
RealmList属性,但尚未添加到 Realm 中或者是从 Realm 中复制得到的。集合已被冻结。
与监听集合变更相结合,实时集合可以实现简洁的响应式代码。例如,假设您的视图显示查询结果。您可以在视图类中保留对结果集合的引用,然后根据需要读取结果集合,而不必刷新结果集合或验证其是否为最新状态。
重要
索引可能会更改
由于结果会自动更新自身,因此请勿存储集合中对象的位置索引或集合中对象的数量。存储的索引或数量值在您使用时可能已过期。
其他支持的数据类型
ObjectId
ObjectId 是 MongoDB 特定的 12 字节唯一值,您可以将其作为对象的标识符。ObjectId 是可索引的,可以将其作为主键。
要将属性定义为 ObjectID,请在对象模型中将其类型设置为 ObjectId。
() class _ObjectIdPrimaryKey { () late ObjectId id; }
调用 ObjectId() 以设置您对象的任何唯一标识符属性。或者,向 ObjectId() 传递一个字符串,以将唯一标识符属性设置为特定值。
final id = ObjectId(); final object = ObjectIdPrimaryKey(id);
UUID
UUID(通用唯一标识符)是一个 16 字节的唯一值。您可以使用 UUID 作为对象的标识符。UUID 是可索引的,您可以将其用主节点 (primary node in the replica set)键。
注意
使用 UUID 而不是 ObjectId
通常,您可将 UUID 用于用作唯一标识符的任意字段。如果要迁移未存储在 MongoDB 中的数据,使用 UUID 可能非常有用,这是因为对象的唯一标识符可能已是 UUID 类型。或者,对于 MongoDB 中已存在的数据集合,使用 ObjectId 可能非常有用。
要将属性定义为 UUID,请在对象模型中将其类型设置为 Uuid。
() class _UuidPrimaryKey { () late Uuid id; }
要将对象的任何唯一标识符属性设置为随机值,请调用任一 Uuid 方法来创建一个UUID,例如 Uuid.v4()。
final myId = Uuid.v4(); final object = UuidPrimaryKey(myId);
Decimal128
Dart没有原生的十进制类型。您可以使用 Decimal128,它是 IEEE-754 的 128 位实施。定义十进制类型时,请使用 Decimal128 BSON类型。
使用 Decimal128 时,请注意,Dart compareTo() 方法实现了模仿 Dart double 类型的全排序。这意味着使用 compareTo() 时,以下情况成立:
所有
NaN值均被视为等于且大于任何数值。-Decimal128.zero小于Decimal128.zero(和整数 0),但大于任何非零负值。负无穷大小于所有其他值,正无穷大大于所有非 NaN 值。
所有其他值均使用其数值进行比较。
RealmValue
重要
Flutter SDK v 2.0.0 对 RealmValue 的更改
Flutter SDK 版本2.0.0 更新RealmValue以允许List或Map类型的RealmValue ,这在对非结构化数据进行建模时具有更大的灵活性。 有关更多信息,请参阅对非结构化数据进行建模。
此更新还包括以下重大更改,这些更改可能会影响您的应用程序升级到2.0.0或更高版本时:
RealmValue.type现在是RealmValueType的枚举,而不是Type的枚举。RealmValue.uint8List已重命名为RealmValue.binary。
有关如何将现有应用从早期版本升级到 v 2.0.0或更高版本的更多信息,请参阅升级到Flutter SDK v 2.0.0 。
RealmValue数据类型是一种混合数据类型,可以表示除嵌入式对象之外的任何其他有效数据类型。在Flutter SDK v2.0.0 中在更高版本中,RealmValue 可以表示 List<RealmValue> 或 Map<String, RealmValue>。
定义 RealmValue 属性
要定义RealmValue属性,请在对象模型中设置其类型。 RealmValue是可索引的,但不能是主键。 您还可以将属性定义为类型为RealmValue的集合(列表、集或映射)。
() class _RealmValueExample { () late RealmValue singleAnyValue; late List<RealmValue> listOfMixedAnyValues; late Set<RealmValue> setOfMixedAnyValues; late Map<String, RealmValue> mapOfMixedAnyValues; }
注意
RealmValue 不可为空,但可以包含 Null 值
在定义 Realm 对象模式时,您不能创建一个可为 null 的 RealmValue。但是,如果您希望 RealmValue 属性包含 null 值,则可以使用特殊的 RealmValue.nullValue() 属性。
创建 RealmValue
要将RealmValue添加到 Realm 对象,请对数据调用RealmValue.from()或RealmValue.nullValue()以设置空值。
final realm = Realm(Configuration.local([RealmValueExample.schema])); realm.write(() { // Use 'RealmValue.from()' to set values var anyValue = realm.add(RealmValueExample( // Add a single `RealmValue` value singleAnyValue: RealmValue.from(1), // Add a list of `RealmValue` values listOfMixedAnyValues: [Uuid.v4(), 'abc', 123].map(RealmValue.from), // Add a set of `RealmValue` values setOfMixedAnyValues: { RealmValue.from('abc'), RealmValue.from('def') }, // Add a map of string keys and `RealmValue` values mapOfMixedAnyValues: { '1': RealmValue.from(123), '2': RealmValue.from('abc') })); // Use 'RealmValue.nullValue()' to set null values var anyValueNull = realm.add(RealmValueExample( singleAnyValue: RealmValue.nullValue(), listOfMixedAnyValues: [null, null].map(RealmValue.from), setOfMixedAnyValues: {RealmValue.nullValue()}, mapOfMixedAnyValues: {'null': RealmValue.nullValue()}));
访问 RealmValue 数据类型
在版本2.0.0中进行了更改: RealmValueType枚举取代了RealmValue.type 。 RealmValue.binary替换了RealmValue.uint8List 。
要访问存储在RealmValue中的数据,您可以使用:
RealmValue.value,它会返回一个Object?。RealmValue.as<T>,它会获取数据并将其转换为所需的类型。
for (var obj in data) { for (var mixedValue in obj.listOfMixedAnyValues) { // Use RealmValue.value to access the value final value = mixedValue.value; if (value is int) { sum = sum + value; } else if (value is String) { combinedStrings += value; } // Use RealmValue.as<T> to cast value to a specific type try { final intValue = mixedValue.as<int>(); sum = sum + intValue; } catch (e) { log('Error casting value to int: $e'); } } }
您可以通过访问type属性来检查当前存储在RealmValue属性中的数据类型。 从 Flutter SDK v 2.0.0开始,此函数会返回RealmValueType枚举。 在早期 SDK 版本中,SDK 返回RealmValue.value.runtimeType 。
以下示例使用RealmValueType根据数据类型运行计算。
final data = realm.all<RealmValueExample>(); for (var obj in data) { final anyValue = obj.singleAnyValue; // Access the RealmValue.type property switch (anyValue.type) { // Work with the returned RealmValueType enums case RealmValueType.int: approximateAge = DateTime.now().year - anyValue.as<int>(); break; case RealmValueType.dateTime: approximateAge = (DateTime.now().difference(anyValue.as<DateTime>()).inDays / 365) .floor(); break; case RealmValueType.string: final birthday = DateTime.parse(anyValue.as<String>()); approximateAge = (DateTime.now().difference(birthday).inDays / 365).floor(); break; // Handle other possible types ... default: log('Unhandled type: ${anyValue.type}'); } }
混合集合
在版本2.0.0中进行了更改: RealmValue属性可以包含混合数据的列表或地图。
在版本2.0.0及更高版本中,混合数据类型可以保存混合元素的集合(列表或映射,但不是设立)。 您可以使用混合集合对非结构化或可变数据进行建模。 有关更多信息,请参阅对非结构化数据进行建模。
您最多可以嵌套100级混合集合。
您可以查询混合集合属性并注册变更监听器,就像查询普通集合一样。
您可以查找并更新单个混合集合元素
不能在混合集合中存储集合或嵌入式对象。
要使用混合集合,请在数据模型中定义混合类型属性。 然后,使用RealmValue.from()创建列表或地图集合。
realm.write(() { realm.add(RealmValueCollectionExample( // Set the RealmValue as a map of mixed data singleAnyValue: RealmValue.from({ 'int': 1, // You can nest RealmValues in collections 'listOfInt': [2, 3, 4], 'mapOfStrings': {'1': 'first', '2': 'second'}, // You can also nest collections within collections 'mapOfMaps': [ { 'nestedMap_1': {'1': 1, '2': 2}, 'nestedMap_2': {'3': 3, '4': 4} } ], 'listOfMaps': [ { 'nestedList_1': [1, 2, 3], 'nestedList_2': [4, 5, 6] } ] })));
Uint8List
Uint8List 是来自 dart:typed_data 的二进制数据类型。您可以在对象模型和属性值中使用此数据类型。
要将某个属性定义为 Uint8List,您必须首先导入 dart:typed_data。然后,在对象模型中将对象的类型设置为 Uint8List。
() class _BinaryExample { late String name; late Uint8List requiredBinaryProperty; late Uint8List? nullableBinaryProperty; }
要在 Realm 对象中添加 Uint8List,请对数据调用 Uint8List.fromList()。
final realm = Realm(Configuration.local([BinaryExample.schema])); realm.write(() { realm.addAll([ BinaryExample("Example binary object", Uint8List.fromList([1, 2])) ]); });
嵌入式对象
Realm 将每个嵌入式对象视作父对象内的嵌套数据。嵌入式对象继承其父对象的生命周期。它不能作为一个独立的 Realm 对象存在。嵌入式对象具有以下属性:
在删除嵌入式对象的父对象时,或者在父对象不再引用嵌入式对象时,将删除这些嵌入式对象。
您不能将嵌入式对象重新分配给不同的父对象。
您不能从多个父对象链接到某个嵌入式对象。
如要查询某个嵌入式对象,您只能通过其父对象来进行访问。
通过将 ObjectType.embeddedObject 传递给 @RealmModel() 注解来声明嵌入式对象。在父对象的 RealmModel 中定义嵌入式对象时,这些嵌入式对象必须可为 null。您还必须在 Realm 的配置中包含嵌入式对象的模式。
以下示例演示了如何在 Realm 模式中对嵌入式对象进行建模。_Address 模型嵌入在 _Person 模型中。
// The generated `Address` class is an embedded object. (ObjectType.embeddedObject) class _Address { late String street; late String city; late String state; late String country; } () class _Person { () late ObjectId id; late String name; // Embedded object in parent object schema late _Address? address; // Must be nullable }
您可以使用 parent 属性访问嵌入式对象的父对象。
以下示例展示了使用嵌入式对象时需要特别注意的一些事项。该示例使用了上述模式中 _Address RealmModel 生成的 Address 嵌入式对象。
// Both parent and embedded objects in schema final realm = Realm(Configuration.local([Person.schema, Address.schema])); // Create an embedded object. final joesHome = Address("500 Dean Street", "Brooklyn", "NY", "USA"); final joePrimaryKey = ObjectId(); final joe = Person(joePrimaryKey, "Joe", address: joesHome); realm.write(() => realm.add(joe)); // Update an embedded object property. realm.write(() { joe.address?.street = "800 Park Place"; }); // Query a collection of embedded objects. // You must access the embedded object through the parent RealmObject type. final peopleWithNewYorkHomes = realm.query<Person>("address.state = 'NY'"); // Overwrite an embedded object. // Also deletes original embedded object from realm. final joesNewHome = Address("12 Maple Way", "Toronto", "ON", "Canada"); realm.write(() { joe.address = joesNewHome; }); // You can access the parent object from an embedded object. final thePersonObject = joesNewHome.parent; // Delete embedded object from parent object. realm.write(() => realm.delete(joe.address!)); // Add address back for the following example. final anotherNewHome = Address("202 Coconut Court", "Miami", "FL", "USA"); realm.write(() { joe.address = anotherNewHome; }); // Deleting the parent object also deletes the embedded object. realm.write(() => realm.delete(joe));
例子
以下模型包含一些支持的数据类型。
part 'car.realm.dart'; // The generated `Address` class is an embedded object. (ObjectType.embeddedObject) class _Address { late String street; late String city; late String state; late String country; } () class _Person { () late ObjectId id; late String name; // Embedded object in parent object schema late _Address? address; // Must be nullable } () class _Car { () late ObjectId id; String? licensePlate; bool isElectric = false; double milesDriven = 0; late List<String> attributes; late _Person? owner; }