#
Entity
An entity is a Dart object that can be stored in a collection. An entity can be a simple class with a few fields or a complex class with nested objects. Every entity in an ObjectRepository
is actually converted to a Document
before storing in the underlying NitriteCollection
. When an entity is converted to a Document
, the fields of the entity are mapped to the fields of the Document
. While retrieving an entity from the ObjectRepository
, the Document
is converted back to the entity.
Nitrite uses a NitriteMapper
implementation to convert an entity to a Document
and vice versa. By default, Nitrite uses an EntityConverter
based implementation of NitriteMapper
to convert an entity to a Document
. You can also provide your own implementation of NitriteMapper
to convert an entity to a Document
.
More on NitriteMapper
can be found here.
#
Annotations
Nitrite uses annotations to define an entity. There are several annotations available to define an entity. These annotations are:
@Entity
@Id
@Index
Flutter does not support reflection. So, Nitrite uses code generators to process these annotations and generates the metadata for an entity. The entity generator generates a mixin for an entity class in a separate file that is named after the source file, but with .no2.dart
extension.
The name of the mixin is like - _$
+ ClassName
+ EntityMixin
. For example, if the entity class name is Person
, the mixin name will be _$PersonEntityMixin. The generated mixin also implements NitriteEntity
interface which defines few getters to access the metadata of the entity. You need to add this mixin to your entity class to make it a Nitrite entity.
The entity generator is a separate package and more on the entity generator can be found here.
#
@Entity
The @Entity
annotation marks a class as an entity class. The annotation has the following properties:
name
- The name of the collection to which the entity belongs. If not specified, the name of the class is used as the collection name.indices
- An array of@Index
annotations. The indices are created on the fields specified in the@Index
annotation.
import 'package:nitrite/nitrite.dart';
@Entity()
class Book {
@Id()
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
import 'package:nitrite/nitrite.dart';
@Entity(name: 'books', indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['description'], type: IndexType.fullText),
Index(fields: ['price', 'publisher']),
])
class Book {
@Id()
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
The above code generates the following code in book.no2.dart
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [];
@override
EntityId get entityId => EntityId(
"bookId",
false,
);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "books";
@override
List<EntityIndex> get entityIndexes => const [
EntityIndex(["tags"], IndexType.nonUnique),
EntityIndex(["description"], IndexType.fullText),
EntityIndex(["price", "publisher"], IndexType.unique),
];
@override
EntityId get entityId => EntityId(
"bookId",
false,
);
}
Now you can use the generated mixin in your entity class:
import 'package:nitrite/nitrite.dart';
part 'book.no2.dart';
@Entity()
class Book with _$BookEntityMixin {
@Id()
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
import 'package:nitrite/nitrite.dart';
part 'book.no2.dart';
@Entity(name: 'books', indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['description'], type: IndexType.fullText),
Index(fields: ['price', 'publisher']),
])
class Book with _$BookEntityMixin {
@Id()
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
#
@Id
The @Id
annotation marks a field as the unique identifier of the entity. It is a field level annotation and takes optional fieldName
parameter. The fieldName
parameter is used to specify the name of the field in the document. If the fieldName
parameter is not specified, then the name of the field in the class will be used as the name of the field in the document.
import 'package:nitrite/nitrite.dart';
@Entity()
class Book with _$BookEntityMixin {
@Id()
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
import 'package:nitrite/nitrite.dart';
@Entity()
class Book with _$BookEntityMixin {
@Id(fieldName: 'book_id')
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
The above code generates the following code in book.no2.dart
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [];
@override
EntityId get entityId => EntityId(
"bookId",
false,
);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [];
@override
EntityId get entityId => EntityId(
"book_id",
false,
);
}
#
Embedded Id
Nitrite also supports embedded id. The embedded id is used when the entity has a composite primary key.
The @Id
annotation can also be used to mark a field as an embedded id. In case of embedded id, the field should be marked with @Id
annotation and the fieldName
and embeddedFields
parameters should be specified. The fieldName
parameter is used to specify the name of the field in the document. The embeddedFields
parameter is used to specify the name of the fields in the embedded object.
import 'package:nitrite/nitrite.dart';
@Entity()
class Book with _$BookEntityMixin {
@Id(fieldName: 'book_id', embeddedFields: ['isbn', 'title'])
BookId? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
class BookId {
String? isbn;
String? title;
String? author;
}
The above code generates the following code in book.no2.dart
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [];
@override
EntityId get entityId => EntityId(
"book_id",
false,
embeddedFields: ["isbn", "title"],
);
}
The above @Id
annotation will create a unique compound index on the fields - book_id.isbn
and book_id.title
.
#
Data Type
The data type of the field marked with @Id
annotation should be one of the following:
num
int
double
String
Runes
bool
DateTime
Duration
NitriteId
In case of embedded id, the data type of the field marked with @Id
annotation should be a class.
#
@Index
@Index
annotation is used to declare an index on an entity field. It is a class level annotation and takes mandatory fields
parameter and optional type
parameter. The fields
parameter is used to specify the names of the fields in the document to be indexed. The type
parameter is used to specify the type of the index. If the type
parameter is not specified, then the type of the index will be IndexType.unique
.
import 'package:nitrite/nitrite.dart';
@Entity(indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['description'], type: IndexType.fullText),
Index(fields: ['price', 'publisher']),
])
class Book with _$BookEntityMixin {
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
The above code generates the following code in book.no2.dart
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [
EntityIndex(["tags"], IndexType.nonUnique),
EntityIndex(["description"], IndexType.fullText),
EntityIndex(["price", "publisher"], IndexType.unique),
];
@override
EntityId get entityId => EntityId(
"bookId",
false,
);
}
The above @Index
annotations will create the following indices:
tags
- Non-unique simple indexdescription
- Full-text indexprice
andpublisher
- Unique compound index
@Index
annotation can be used on super class as well. When the entity class extends a super class, the @Index
annotations of the super class are also processed by the entity generator.
import 'package:nitrite/nitrite.dart';
@Entity(indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['price', 'publisher']),
])
class Book extends BaseEntity with _$BookEntityMixin {
String? bookId;
String? publisher;
double? price;
List<String> tags = [];
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
super.description,
]);
}
@Index(fields: ['description'], type: IndexType.fullText)
abstract class BaseEntity {
String? description;
BaseEntity([this.description]);
}
The above code generates the following code in book.no2.dart
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'book.dart';
// **************************************************************************
// NitriteEntityGenerator
// **************************************************************************
mixin _$BookEntityMixin implements NitriteEntity {
@override
String get entityName => "Book";
@override
List<EntityIndex> get entityIndexes => const [
EntityIndex(["tags"], IndexType.nonUnique),
EntityIndex(["price", "publisher"], IndexType.unique),
EntityIndex(["description"], IndexType.fullText),
];
@override
EntityId? get entityId => null;
}
#
EntityDecorator
EntityDecorator
is used to add metadata to an entity. If you cannot modify the entity class to add annotations, then you can use EntityDecorator
to add metadata to an entity. You can use EntityDecorator
to add id, indices and collection name for an entity.
While creating or opening a repository, you can pass an instance of EntityDecorator
to the getRepository()
method on Nitrite
class. Nitrite will extract the metadata from the EntityDecorator
instance and use it to create the repository.
ObjectRepository<Product> repository = await db.getRepository<Product>(entityDecorator: ProductDecorator());
To write an EntityDecorator
for an entity, you need to implement the EntityDecorator
interface. Let's take an example of a Product
entity.
class Product {
ProductId? productId;
Manufacturer? manufacturer;
String? productName;
double? price;
Product([
this.productId,
this.manufacturer,
this.productName,
this.price,
]);
}
class ProductId {
String? uniqueId;
String? productCode;
ProductId([
this.uniqueId,
this.productCode,
]);
}
class Manufacturer {
String? name;
String? uniqueId;
Manufacturer([
this.name,
this.uniqueId,
]);
}
The Product
entity has an embedded id ProductId
and two indices on a nested object Manufacturer
. To write an EntityDecorator
for the Product
entity with collection name 'products', you need to implement the EntityDecorator
interface as follows.
class ProductDecorator extends EntityDecorator<Product> {
@override
String get entityName => "products";
@override
EntityId? get idField => EntityId(
"productId",
false,
embeddedFields: ["uniqueId", "productCode"],
);
@override
List<EntityIndex> get indexFields => const [
EntityIndex(["manufacturer.name"], IndexType.nonUnique),
EntityIndex(["productName", "manufacturer.uniqueId"]),
];
}
which is equivalent to the following entity class when code generator is used:
@Entity(name: 'products', indices: [
Index(fields: ['manufacturer.name'], type: IndexType.nonUnique),
Index(fields: ['productName', 'manufacturer.uniqueId']),
])
class Product with _$ProductEntityMixin {
@Id(fieldName: 'productId', embeddedFields: ['uniqueId', 'productCode'])
ProductId? productId;
Manufacturer? manufacturer;
String? productName;
double? price;
Product([
this.productId,
this.manufacturer,
this.productName,
this.price,
]);
}
class ProductId {
String? uniqueId;
String? productCode;
ProductId([
this.uniqueId,
this.productCode,
]);
}
class Manufacturer {
String? name;
String? uniqueId;
Manufacturer([
this.name,
this.uniqueId,
]);
}
#
EntityId
EntityId
is used to specify the id field of an entity. It takes mandatory fieldName
parameter and optional embeddedFields
parameter in case of embedded id. The fieldName
parameter is used to specify the name of the field in the document. The embeddedFields
parameter is used to specify the name of the fields in the embedded object.
@override
EntityId? get idField =>
EntityId('productId', false, ['uniqueId', 'productCode']);
Here productId
is the name of the field in the document and uniqueId
and productCode
are the names of the fields in the embedded object. This will create a unique compound index on the fields - productId.uniqueId
and productId.productCode
.
#
EntityIndex
EntityIndex
is used to specify the indices on an entity. It takes mandatory type
parameter and fields
parameter. The type
parameter is used to specify the type of the index. The fields
parameter is used to specify the name of the fields in the document to be indexed.
@override
List<EntityIndex> get indexFields => const [
EntityIndex(['manufacturer.name'], IndexType.nonUnique),
EntityIndex(['productName', 'manufacturer.uniqueId']),
];
Here manufacturer.name
is the name of the field in the document and productName
and manufacturer.uniqueId
are the names of the fields in the document. This will create a non-unique index on the field manufacturer.name
and a unique compound index on the fields - productName
and manufacturer.uniqueId
.
#
Entity Name
Entity name is used to specify the name of the collection in which the entity will be stored. It is a string value and can be specified using entityName
getter.
@override
String get entityName => "products";
Here products
is the name of the NitriteCollection
in which the product document will be stored.