drift icon indicating copy to clipboard operation
drift copied to clipboard

How does BaseDao handle column

Open TshineZheng opened this issue 6 years ago • 21 comments

class BaseTable extends Table{
  BoolColumn get uploaded => boolean().withDefault(const Constant(false))();
}

class TableA extends BaseTable {
  TextColumn get type => text().nullable()();
  ...
}

class TableB extends BaseTable {
  TextColumn get name => text().nullable()();
  ...
}

how should i handle uploaded in BaseDao?

TshineZheng avatar Jan 11 '20 01:01 TshineZheng

I'm not sure what you mean with "handle uploaded in BaseDao". Can you be more specific on what exactly you're trying to do?

Be aware that inheritance with table classes is only supported so that you can apply columns to multiple tables at once. There's no such thing as table inheritance in sql (not in sqlite, at least). So you couldn't write a @UseDao(tables: [BaseTable]) and write queries that operate on TableA and TableB, if that's what you're trying to do.

simolus3 avatar Jan 11 '20 10:01 simolus3

i want write a method in BaseDao like :

Future<void> setUploaded(String id) {
  // change uploaded column
}

If I didn't, I'd have to write almost identical code in both places(TableADao、TableBDao、ETC...)

TshineZheng avatar Jan 13 '20 04:01 TshineZheng

I have the same problem: I have used your example BaseDao:

import 'package:moor/moor.dart';

abstract class BaseDao<T extends Table, D extends DataClass,
    DB extends GeneratedDatabase> extends DatabaseAccessor<DB> {
  final TableInfo<T, D> _table;

  BaseDao(DB db, this._table) : super(db);

  Future<void> insertOne(Insertable<D> value) => into(_table).insert(value);

  Future<List<D>> getAll() => select(_table).get();

  Future<void> createOrUpdateEntity(Insertable<D> entity) =>
      into(_table).insertOnConflictUpdate(entity);
}

So i can get all entities and create entities.

But i also need to delete entities, but at the moment i need to implement almost identical code for every entity:

@UseDao(tables: [UserEntities])
class UserDao extends BaseDao<UserEntities, UserEntity, SyncDatabase>
    with _$UserDaoMixin {
  UserDao(SyncDatabase db, TableInfo<UserEntities, UserEntity> table)
      : super(db, table);

  Future<void> deleteById(String id) {
    return (delete(userEntities)..where((user) => user.id.equals(id))).go();
  }
}

because the baseDao don't know that the entity has an id.

But what i want is:

abstract class EntityWithId extends DataClass {
  String id;
}

abstract class BaseEntity extends Table {
  TextColumn get id => text().clientDefault(() => uuid.v4())();
  DateTimeColumn get deletedAt => dateTime().nullable()();

  @override
  Set<Column> get primaryKey => {id};
}

abstract class BaseDao<T extends BaseEntity, D extends EntityWithId,
    DB extends GeneratedDatabase> extends DatabaseAccessor<DB> {
  final TableInfo<T, D> _table;

  BaseDao(DB db, this._table) : super(db);

  Future<void> insertOne(Insertable<D> value) => into(_table).insert(value);

  Future<List<D>> getAll() => select(_table).get();

  Future<void> createOrUpdateEntity(Insertable<D> entity) =>
      into(_table).insertOnConflictUpdate(entity);

  Future<void> deleteById(String id) {
    return (delete(_table)..where((user) => user.id.equals(id))).go();
  }
}

Or is there another way to archieve this?

rubiktubik avatar Jul 27 '20 12:07 rubiktubik

You can use

Future<int> deleteById(String id) {
  final idColumn = table.columnsByName['id'] as Expression<String>;
  return (delete(_table)..where((_) => idColumn.equals(id)).go();
}

simolus3 avatar Jul 27 '20 13:07 simolus3

Thank you for your very fast reply! Your example seems promising. I will try it out with my other use-cases and see if still need the inheritance.

rubiktubik avatar Jul 27 '20 13:07 rubiktubik

My next problem is on generic inserting:

Code in my implemented Dao which i want to generalize:

Future<void> createEntryFromModel(UserEntity user) async {
    final version = await getCurrentEntityVersion();
    final userWithUpdatedSyncStatus =
        user.copyWith(syncStatus: SyncConstants.insert, version: version);
    return insert(userWithUpdatedSyncStatus);
  }

Every entity in my application has these two fields. So i need to do alot of duplicate code.

I want to create a generic insert function in BaseDao. It should insert the generic entity T:

Future<void> createEntryFromModel(T entity) async {
    final version = await getCurrentEntityVersion(); 
    final userWithUpdatedSyncStatus =
        entity.copyWith(syncStatus: SyncConstants.insert, version: version); // 1.
    return insert(userWithUpdatedSyncStatus); // 2.
  }

The code in 1. and 2. obviously not working in my generic dao. How can i set the syncStatus- and version Column on the generic T (1.) and then insert the entity (2.)

I think it would be a similar aproach like in your delete example.

rubiktubik avatar Jul 30 '20 07:07 rubiktubik

This should work, but it's some questionable Dart:

Future<void> createEntryFromModel(T entity) async {
  final copy = (entity as dynamic).copyWith;
  final version = await getCurrentEntityVersion(); 
  final result = Function.apply(copy, [], {#syncStatus: SyncConstants.insert, #version: version}) as T;
  return insert(result);
}

I would probably do it like this:

Future<void> createEntryFromModel(Insertable<T> entity) async {
  final version = await getCurrentEntityVersion();
  final columns = entity.toColumns(true);
  columns['version'] = version;
  columns['sync_status'] = const YourConverterForSyncConstants().mapToSql(SyncConstants.insert);

  return insert(RawValuesInsertable(result));
}

simolus3 avatar Jul 31 '20 11:07 simolus3

Thank you for your answer! I probably missed some important information for you :-)

version is an int and SyncConstant.insert is a simple string

I implemented your suggestion:

Future<void> insertEntityWithVersionAndSyncStatus(
      Insertable<D> entity, String syncStatus) async {
    final version = await getCurrentEntityVersion();
    final columns = entity.toColumns(true);
    columns['version'] = version as Expression<int>; //my quick guess on setting version but its  not working 😅 
    columns['sync_status'] = syncStatus as Expression<String>;  //my quick guess on setting sync_status but its  not working too 😅 

    return insert(RawValuesInsertable(columns));
  }

Can you give me a hint how to set the columns?

rubiktubik avatar Aug 03 '20 07:08 rubiktubik

Of course, sorry. The best way is to wrap them in variables:

  columns['version'] = Variable(version);
  columns['sync_status'] = Variable(syncStatus);

simolus3 avatar Aug 03 '20 18:08 simolus3

Thanks! I will try it out.

rubiktubik avatar Aug 05 '20 08:08 rubiktubik

Here is my implementation:

  Future<void> insert(Insertable<D> value) => into(_table).insert(value);

  Future<void> insertEntityWithSyncStatus(
      Insertable<D> entity, String syncStatus) async {
    final version = await getCurrentEntityVersion();
    final columns = entity.toColumns(true);
    columns['sync_status'] = Variable(syncStatus);
    columns['version'] = Variable(version + 1);

    return insert(RawValuesInsertable(columns));
  }

  Future<void> insertEntity(Insertable<D> entity) {
    return insertEntityWithSyncStatus(entity, SyncConstants.insert);
  }

But when i use the insertEntity-Method i get a StateError (Bad state: Too many elements)-Exception.

grafik

Why is it not working?

rubiktubik avatar Aug 10 '20 13:08 rubiktubik

Can you post the trace of the exception? It sounds like a .single somewhere where it's not supposed to be, but that's hard to say without checking the origin.

simolus3 avatar Aug 10 '20 16:08 simolus3

This is where the error occurs:

grafik

This is the function i forgot to post:

/// Gives the highest version number in the entities table
  /// Returns the max-version as [int]
  Future<int> getCurrentEntityVersion() async {
    final versionColumn = _table.columnsByName['version'] as Expression<int>;
    final maxVersionExpression = versionColumn.max();
    final max = await (selectOnly(_table)..addColumns([maxVersionExpression]))
        .map((row) => row.read(maxVersionExpression))
        .getSingle();
    if (max == null) {
      return 0;
    } else {
      return max;
    }
  }

Is this function the problem? It worked in my daos without problems

rubiktubik avatar Aug 12 '20 11:08 rubiktubik

Is this function the problem?

The problem actually happens during inserts, apologies for posting the broken code. Does it work if you set the type argument on Variable?

    columns['sync_status'] = Variable<String>(syncStatus);
    columns['version'] = Variable<int>(version + 1);

simolus3 avatar Aug 12 '20 18:08 simolus3

Now its working!!! Thank you for your help. Possibly i make a documentation how to create a generic Dao-Baseclass for other people. I'll see :-)

rubiktubik avatar Aug 14 '20 13:08 rubiktubik

Hey i try to write like @rubiktubik code but the generated code couldn't generate constructor to Dao with TableInfo parameter

abstract class BaseDao<T extends Table, D extends DataClass,
    DB extends GeneratedDatabase> extends DatabaseAccessor<DB> {
  final TableInfo<T, D> _table;

  BaseDao(DB db, this._table): super(db) 

  Future<D> getById(int id) {
    final idColumn = _table.columnsByName['id'] as Expression<int>;
    return (select(_table)..where((_) => idColumn.equals(id))).getSingle();
  }
}

@UseDao(tables: [Users])
class UserDao extends BaseDao<Users, User, MyDatabase>
    with _$UserDaoMixin {

  UserDao(MyDatabase db, TableInfo<Users, User> table) : super(db, table);

}

// error in  MyDatabase.g.dart generated code 
........
UserDao _userDao;
UserDao get userDao => _userDao ??= UserDao(this as MyDatabase); 
........

Abdktefane avatar Oct 06 '20 06:10 Abdktefane

@Abdktefane What error are you getting exactly? If your database class is named GymZoneDatabase, your UserDao should extend BaseDao<Users, User, GymZoneDatabase> accordingly.

simolus3 avatar Oct 06 '20 10:10 simolus3

I apologize. There was a mistake in copying the code but I have corrected it. The error is that moor generator cannot generate a constructor to BaseDao with TableInfo<T, D> _table , it only generates UserDao(MyDatabase) code in generated MyDatabase.g.dart file when call the dao be like =>

........
UserDao _userDao;
UserDao get userDao => _userDao ??= UserDao(this as MyDatabase); 
........

and thanks for fast response

Abdktefane avatar Oct 06 '20 16:10 Abdktefane

Alright, now I see the error. You can fix that by looking up the right table in the constructor:


@UseDao(tables: [Users])
class UserDao extends BaseDao<Users, User, MyDatabase>
    with _$UserDaoMixin {

  UserDao(MyDatabase db) : super(db, db.users);
}

simolus3 avatar Oct 07 '20 17:10 simolus3

it works. Thank you ❤️

Abdktefane avatar Oct 08 '20 03:10 Abdktefane

Now its working!!! Thank you for your help. Possibly i make a documentation how to create a generic Dao-Baseclass for other people. I'll see :-)

Any updates on this?

MaxiStefan avatar Jan 10 '22 13:01 MaxiStefan