Flutter Moor Database: Joining queries proper structure - flutter

I have just started to use Moor Database for Flutter. I am going to join my two tables to get some columns from both tables.
I have checked the example that is given in docs as follow:
// we define a data class to contain both a todo entry and the associated category
class EntryWithCategory {
EntryWithCategory(this.entry, this.category);
final TodoEntry entry;
final Category category;
}
// in the database class, we can then load the category for each entry
Stream<List<EntryWithCategory>> entriesWithCategory() {
final query = select(todos).join([
leftOuterJoin(categories, categories.id.equalsExp(todos.category)),
]);
// see next section on how to parse the result
}
I am not able to understand that where to put this class. If I am creating a new class then it's giving me an error that the select keyword is not found. Also tried to import related to moor but not working.
Where I can write join queries and make this class?

Moor basically says to get the results and build the class manually. That class has no relationship with the database, so you can put it wherever you want. It is just the suggested way of doing it.
So, the select statement returns an object that you can iterate over the resulting rows, as an SQL response. And with that results build the classes that will be returned.
Look at the next example of the docs:
return query.watch().map((rows) {
return rows.map((row) {
return EntryWithCategory(
row.readTable(todos),
row.readTableOrNull(categories),
);
}).toList();
});
Because is a stream, calls watch(),and then map().
This first map returns the result of the query, properly name rows, every time one of the rows changes in the database, it will emit all the rows again.
The second map inside the first is for turning every row into a EntryWithCategory object. That way the whole function returns a list of those object updated with every change.

You can create other model for several tables.
Try this variant.
import 'package:drift/drift.dart';
part 'car_dao.g.dart';
#DriftAccessor(tables: [Cars, Bikes])
class CarDao extends DatabaseAccessor<AppDatabase> with _$CarDaoMixin {
final AppDatabase db;
CarDao(this.db) : super(db);
Future<List<CarWithBikeModel>> getCarsWithBikes() async {
final carList = await (select(cars).get();
final bikeList = await (select(bikes).get();
return CarWithBikeModel(
cars: carList,
bikes: bikeList);
}
}

Related

How to make a few collections of the same type in Isar db? [Flutter] [Isar]

Is there an option to make more than one collection with objects of the same type in Isar?
Isar doc: https://isar.dev/
how i create single Collection:
import 'package:isar/isar.dart';
#Collection()
class ProgramModel {
#Id()
int? id;
String? title;
}
I want to have second collection of ProgramModel, but i can't add another #Collection() to same model.
Tried and can use same collection class in multiple isar instances (dbs).
To start new isar instance:
Isar isarOne = await Isar.open(
name: 'dbOne',
schemas: [ContacSchema],
);
Isar isarTwo = await Isar.open(
name: 'dbTwo',
schemas: [ContacSchema],
);
In web, it creates 2 DBs inside IndexedDB.
On native also it will create 2 isar instances.
Wanted a way to use same collection class to create Multiple Collections in single isar instance, but seems that cannot be done.. so will proceed with multiple isar instances to have multiple tables.
either you could make a collection with other name of same object model, or else create this model out of the scope of the present isar(with a new isar db)
Just make a new file and implement the ProgramModel #collection there.

How to approach dynamically generated Firestore queries depending on nested user created Sub Collections?

/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/Restaurants & Bakeries/Sub Categories/Snack/Sub Categories/Abo Arab Cafe
So as you can see, this is a snippet from my current Firestore structure. So many deeply nested collections. The issue is, I want to keep going deeper as long as a collection called 'Sub Categories' is found which in that case I would render them in the UI. And when eventually I reach a level where 'Sub Categories' is not found, I will render a different UI and show the actual products (The last document "Abo Arab Cafe" contains all the products as maps). The pattern of how many Sub Categories there are is unexpectable and can be modified by the end user.
How can I keep checking for Sub Categories? How to manage my queries in a way that they are dynamically generated at each level at the client-side?
I use Flutter. Here is my current queries structure:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirebaseServices {
final FirebaseFirestore _db = FirebaseFirestore.instance;
CollectionReference mainCategoryCollectionReference() {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories');
return mainCategoryCollectionReference;
}
CollectionReference subCategoryCollectionReference(
String parentSelectedCategory) {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/$parentSelectedCategory/Sub Categories');
return mainCategoryCollectionReference;
}
bool checkIfSubCategoriesExist(CollectionReference collectionReference) {
bool subCategoriesExist;
collectionReference.get().then((value) => {
subCategoriesExist = value.docs.isNotEmpty,
print('SubCategoriesExist: $subCategoriesExist')
});
return subCategoriesExist;
}
}
This works only if I know for certain how many levels of deepness there are, but since this can be modified by the user, it won't work.
Sorry for the very long question I had no idea how to explain it properly and clearly. Thank you in advance!
The structure is all wrong, there is no point in the structure being this deeply nested. The structure of the database needs to match what has to appear in the UI.
Assuming this is a worldwide application since you are using countries then you have to do the following:
Collection
Document
Fields
Countries
Random ID
countryName - arrayOfDistrict- arrayOfGovernorates
3 Fields under each document id, containing information about the country.
Then regarding Resturants:
Collection
Document
Fields
SubCollection
subCollectionId
Fields
Resturant
Random ID
resturant_name- resturant_location - info_about_resturant
Menu
randomId
dish_name - price -...
The problem with your db structure is that it is very nested instead of making a flat structure and that right now you are harcoding the whole path.
Using the above structure, you can create a dropdown with list of countries if the user chooses Lebanon, then you get the districts and the governorates. Then you can do a call to get the resturants that are inside each district, since in the documents inside Resturant collection you can get location of each resturant and name.
After that on click of each resturant, you will get the data inside the subcollection that will contain the full menu.
I think I found the solution with the help of a friend!
Since the checkIfSubCategoriesExist function is always checking on the very last reached level(using the collectionReference argument) whether Sub Categories exists or not, he suggested that in case it does exist, I can append to its argument collectionReference the new "Sub Categories" String to the path as a variable! This way I can query on it and voila!

How can I determine how many rows were loaded into an EF6 DbContext when using Load/LoadAsync?

The DbContext DbSet<T>.Load / DbSet<T>.LoadAsync methods return void and Task respectively: they execute queries and then add the loaded/instantiated entity objects into the DbContext's DbSet and update the navigation properties and reverse-navigation of already-loaded objects, but they don't return any information about what they loaded: there doesn't seem to be a way of getting the actual count of the number of rows that were loaded.
Which is surprising, considering that the SaveChanges / SaveChangesAsync method does return the number of rows affected by any DML statements it executes.
I know there's a workaround in that I could use ToList/ToListAsync instead and then use the List<T>.Count property, but that's defeating the point of using Load/LoadAsync.
For example, consider this two-step query operation:
async Task<PageViewModel> LoadOrdersAsync( Int32 customerId, Expression<Func<Order,Boolean>> predicate )
{
// Step 1:
List<Order> orders = await this.dbContext.Orders
.Where( o => o.CustomerId == customerId )
.Where( predicate )
.ToListAsync()
.ConfigureAwait(false);
// Step 1.5:
List<Int32> orderIds = orders.Select( o => o.OrderId ).ToList();
// Step 2:
await this.dbContext.OrderItems
.Where( i => orderIds.Contains( i.OrderId ) )
.LoadAsync()
.ConfigureAwait(false);
// Done!
return new PageViewModel( orders );
}
I want to get the quantity of OrderItem entities that were loaded in the second step, but as far as I know that isn't possible without using ToList/ToListAsync.
You’re right, there is no easy way to get the number of loaded entries of the Load. It is essentially the same as ToList without creating the list and adding the loaded elements to it. If you really don’t want to use ToList, one option is to access the DbContext.ChangeTracker and get the number of entries from that:
var entriesBefore = context.ChangeTracker.Entries().Count();
// load more entities
var loaded = context.ChangeTracker.Entries().Count() - entriesBefore;
Note, that this is not accurate when you include other, related entities in your query.

Flutter: Effective way of inserting list of data in database

I have cities table and trying to insert city, upon database creation. The table structure is pretty simple, it has just id and name column.
In onCreate method of my database class, I create table with this command:
var tblCities = 'cities';
await db.execute('CREATE TABLE $tblCities (id INTEGER PRIMARY KEY, name TEXT)');
I have Cities model class with fromMap and toMap methods.
There are about 350 cities, and I wanted to insert them in the table.
Q. What is the best and easy way to do that?
This comes in my mind:
creating list of city
using for loop to iterate entire list
creating map of the city using toMap method
calling db.insert method inside the loop
I'm not sure, but this seem dumb approach so thinking about better and optimized solution...
As mentioned by #chunhunghan, you can use batch to insert bulk data.
Here's step by step guideline:
Get ready your json file e.g cities.json (create csv file of data and use csv to json converter like this)
Add cities.json file in your assets directory
Define it in pubspec.yaml like this:
assets:
- assets/cities.json
Paste this code inside onCreate method of your database class (make sure its after table creation query)
Batch batch = db.batch();
String citiesJson = await rootBundle.loadString('assets/json/cities.json');
List citiesList = json.decode(citiesJson);
citiesList.forEach((val) {
//assuming you have 'Cities' class defined
Cities city = Cities.fromMap(val);
batch.insert(tblCities, city.toMap());
});
batch.commit();
That's it! :)
There is Batch support
To avoid ping-pong between dart and native code, you can use Batch:
batch = db.batch();
batch.insert('Test', {'name': 'item'});
batch.update('Test', {'name': 'new_item'}, where: 'name = ?', whereArgs: ['item']);
batch.delete('Test', where: 'name = ?', whereArgs: ['item']);
results = await batch.commit();
official example https://github.com/tekartik/sqflite/blob/master/sqflite/example/lib/batch_test_page.dart
In your case, for loop list with batch.insert command, it's easier to maintain
for simplicity syntax, use toMap, example
batch.insert("cities", city.toMap());
detail https://www.techiediaries.com/flutter-sqlite-crud-tutorial/
If you prefer rawInsert, please reference Insert multiple records in Sqflite
You can write a raw query to insert all the data at once into the database.

Applying query filter like the one in codelab (Cloud Firestore) is not working

I'm using firestore for my project to store data , my issue is that i can not
filter data just the way codelab filter does .The result of that query gives me the whole collections docs which means the filter is not working.
I've tried many workarounds and find out that only when I cascade whereEqualto()
methods in one line I get what I want of specific docs , but this approach (cascading whereEqualTo inline) is not flexible when giving the user a way to filter searches.I just want to know why that code is not working for me.
// Does not work
Query query = mFirestore.collection("restaurants");
// Category (equality filter)
if (filters.hasCategory()) {
query = query.whereEqualTo("category", filters.getCategory());
}
// City (equality filter)
if (filters.hasCity()) {
query = query.whereEqualTo("city", filters.getCity());
}
This works:
query.whereEqualTo("realEstateType", realEstateType).whereEqualTo("propertyStatus", propertyStatus).whereEqualTo("propertyPhysicalStatus", propertyPhysicalConditionS).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
}
}
});
I figured it out ,It seems that I was not assigning to query object the filter as follows:
query = query.whereEqualTo("fieldPath" , ...);