简体   繁体   English

通过加/减来更新 Amplify Datastore 项目的值?

[英]Update Amplify Datastore items' value by adding / subtracting?

I want to use datastore to update a value based on the current value stored in the database.我想使用数据存储根据存储在数据库中的当前值更新值。 In the documentation it shows a retail counter use case.文档中,它显示了一个零售柜台用例。 But how are you supposed to implement this kind of set up?但是你应该如何实现这种设置呢?

A small example:一个小例子:

I have an inventory table which contains an item and a counter for an item:我有一个库存表,其中包含一个项目和一个项目的计数器:

type Inventory @model {
id: ID!
name: String!
count: Int!}

If i want to save a new Inventory I can do this:如果我想保存一个新的Inventory ,我可以这样做:

Inventory inventory = Inventory(id,'product1',10);
Amplify.Datastore.save(inventory);

Then, I know to update i can use the Amplify.Datastore.save(inventoryUpdatedModel) with the same ID, and Amplify will update the item in storage.然后,我知道更新我可以使用具有相同 ID 的Amplify.Datastore.save(inventoryUpdatedModel) ,并且 Amplify 将更新存储中的项目。 So far so good.到目前为止,一切都很好。

So what if I have multiple devices changing the same value as in the example?那么如果我有多个设备更改与示例中相同的值怎么办?

Start: count = 10;开始:count = 10;

1: Device 1 -> Adds 5 to count (10 + 5 = 15) 1:设备 1 -> 计数加 5 (10 + 5 = 15)

2: Device 2 -> Subtracts 3 from count (10 - 3 = 7) 2:设备 2 -> 从计数中减去 3 (10 - 3 = 7)

3: Both Devices update Datastore and read values. 3:两个设备都更新 Datastore 并读取值。

Currently, only the latest value (7 in the example) is shown, where the required answer would be 12. (10+5-3 = 12)目前,仅显示最新值(示例中为 7),其中所需答案为 12。(10+5-3 = 12)

So far ive tried:到目前为止,我尝试过:

  1. Query the database just before it saves, but this has obvious concurrency issues when multiple devices are performing operations at the same time.在保存之前查询数据库,但是当多个设备同时执行操作时,这会出现明显的并发问题。

  2. Add a new table called updateInventoryCount(inventoryID,numberToAdd), then process the data in this table one item at a time, on only one device.添加一个名为 updateInventoryCount(inventoryID,numberToAdd) 的新表,然后仅在一台设备上一次处理该表中的数据。 Balancing the value of any updates to the same ID and then removing them works actually but its nowhere near a perfect solution.平衡对同一 ID 的任何更新的价值然后删除它们实际上是可行的,但它远非完美的解决方案。

What I would love is something like Amplify.Datastore.updateValue(ID,-3);我想要的是Amplify.Datastore.updateValue(ID,-3); Amplify -> Takes old value, subtracts 3, updates appsync with same command放大 ->取旧值,减去 3,使用相同的命令更新 appsync

The direction i chose to go went like this, please feel free to let me know if i'm way off the mark!我选择 go 的方向是这样的,如果我离题了,请随时告诉我!

Basically: Run a service to detect changes in a supplementary table synced with AWS, and only allow this service to change data locally, rather than allowing AWS to change your locally protected table.基本上:运行一项服务来检测与 AWS 同步的补充表中的更改,并且只允许该服务在本地更改数据,而不是允许 AWS 更改您在本地受保护的表。

PROS:优点:

  • doesn't rely on any server code at all.根本不依赖任何服务器代码。
  • Intermittent network is fine, updates remain the same across multiple devices,间歇性网络很好,更新在多个设备上保持不变,
  • Uses the flutter amplify api only仅使用 flutter 放大 api

CONS:缺点:

  • will create many more writes to AWS将创建更多对 AWS 的写入
  • is convoluted令人费解

Make models : Couldnt get models to grab related objects directly through the codegen'd code, (even though it looks like the plumbing is there to do just that..) so im using 'table row' ID's and performing two Amplify.DataStore.query(model.classType) operations to get my local nodes' inventory and then generic ticketitem Data制作模型:无法让模型直接通过 codegen'd 代码获取相关对象,(即使看起来管道就是这样做的......)所以我使用“表行”ID 并执行两个Amplify.DataStore.query(model.classType)操作来获取我的本地节点的inventory ,然后是通用ticketitem数据

type Inventory @model {
  id:ID!
  clientid: String!
  inventoryclientid: String!
  quantity:Int!
  sellprice: Float!
  ticketItemID: String!}

 type TicketItem @model {
      id:ID!
      name: String!
      buyprice: Float!
      genericnfo: String!
      }

type SyncedMutations @model {
    id:ID!
    clientid: String!
    ownerclientid: String!
    tablename: String!
    itemid: String!
    mutation: String!
    from: String!
    completed: [String]
    itemdata: String!
}

then: amplify codegen models然后: amplify codegen models

I also had to use selectiveSync on startup like so:我还必须像这样在启动时使用selectiveSync:

    AmplifyDataStore amplifyDataStore = AmplifyDataStore(modelProvider: 
    ModelProvider.instance, syncExpressions: [    
    DataStoreSyncExpression(SyncedMutations.classType, () => SyncedMutations.OWNERCLIENTID.eq(curClient))     
    DataStoreSyncExpression(Inventory.classType, () => Inventory.CLIENTID.eq(curClient)),
    ]);
    
      Amplify.addPlugin(amplifyDataStore);

then i set up a service on start up to subscribe and listen to events happening to the SyncedMutations table, and on any create operation, to update the local inventory table.然后我在启动时设置了一个服务来订阅和监听SyncedMutations表发生的事件,并在任何创建操作中更新本地inventory表。 the code is as follows (needs a clean up):代码如下(需要清理):

import ...

@LazySingleton()
class SyncMutationsService {
  static final SyncMutationsService _instance = SyncMutationsService._internal();
  final _clientService = locator<ClientService>();

  SyncMutationsService._internal() {
    Stream<SubscriptionEvent<SyncedMutations>> stream = Amplify.DataStore.observe(SyncedMutations.classType)
      ..listen(handleSubscription);
  }

  factory SyncMutationsService() => _instance;

  handleSubscription(SubscriptionEvent<SyncedMutations> event) async {
 if (event.eventType == EventType.create) {
    updateLocalProtectedTable(event.item);
    }
  }

  void updateLocalProtectedTable(SyncedMutations eventitem) {
    switch (eventitem.tablename) {
      case 'Inventory':
        print('Inventory table update.. ');
        updateInventoryOnEvent(eventitem);
        break;
    }
  }

  void updateInventoryOnEvent(SyncedMutations eventitem) {
    try {
      Inventory inventorydata = Inventory.fromJson(json.decode(eventitem.itemdata));

      SharedPreferencesHelper.getCurrentClientID().then((curClientID) {
        if (eventitem.completed == null || !eventitem.completed!.contains(curClientID)) {
          try {
            if (int.tryParse(eventitem.mutation) != null) {
              getCorrespondingInventoryLine(inventorydata.inventoryTicketItemID).then((oldLocalInvItem) {
                Inventory updatedInventoryItem = oldInvItem.copyWith(
                  quantity: oldLocalInvItem.quantity + int.parse(eventitem.mutation),
                );

                Amplify.DataStore.save(updatedInventoryItem).then((value) {
                  //update syncMutationLine completed client list
                  List<String> completedList = eventitem.completed ?? [];
                  completedList.add(_clientService.client.id);
                  Amplify.DataStore.save(eventitem.copyWith(completed: completedList));
                });
              });
            }
          } catch (e) {
            print('TicketItem was saved, but could not initialise stock into Inventory. ERROR: $e');
          }
        }
      });
    } catch (e) {
      print('Could not transpose inventory line data from syncMutation. ERROR: $e');
    }
  }


  Future<Inventory> getCorrespondingInventoryLine(String ticketItemID) async {
      double markup = await SharedPreferencesHelper.getBaseMarkup();

    return Amplify.DataStore.query(Inventory.classType).then((inventoryList) {
      return Amplify.DataStore.query(TicketItem.classType).then((ticketItemList) {
        TicketItem ti = ticketItemList.firstWhere((tItem) => tItem.id == ticketItemID);
        if (inventoryList.isNotEmpty) {
          return inventoryList.firstWhere((inventoryLine) => inventoryLine.inventoryTicketItemID == ticketItemID,
              orElse: () {
                return Inventory(
                    clientid: _clientService.client.id,
                    inventoryclientid: _clientService.client.id,
                    quantity: 0,
                    sellprice: markup * ti.buyprice,
                    inventoryTicketItemID: ticketItemID);
              });
        } else {
          return Inventory(
              clientid: _clientService.client.id,
              inventoryclientid: _clientService.client.id,
              quantity: 0,
              sellprice: markup * ti.buyprice,
              inventoryTicketItemID: ticketItemID);
        }
      });
    });
  }
}

Now whenever i want to adjust inventory quantities (or any other table using the same logic), i locally save directly to inventory , but then add a syncedMutations entry with the current devices' clientID which will get spawned to all the other clients changing the same information.现在,每当我想调整库存数量(或使用相同逻辑的任何其他表)时,我都会在本地直接保存到inventory ,然后添加一个带有当前设备的 clientID 的syncedMutations条目,该条目将生成给所有其他更改相同的客户端信息。 like this:像这样:

    Inventory updatedInventoryItem = oldInventoryItem.copyWith( 
   quantity:oldInventoryItem.quantity+valueOfMutation);
    
     
                  int mutationInt = valueOfMutation; // add 10, minus 4 etc
    
                  Amplify.DataStore.save(updatedInventoryItem).then((value) {
                    Amplify.DataStore.save(SyncedMutations(
                        clientid: clientService.client.id,
                        ownerclientid: clientService.actingClient.id,
                        completed: [clientService.client.id],
                        tablename: 'Inventory',
                        itemid: oldInventoryItem.ticketItemID,
                        mutation: mutationInt.toString(),
                        from: oldInventoryItem.quantity.toString(),
                        itemdata: json.encode(updatedInventoryItem.toJson())));
                  });

so lets say i have two clients which both have intermittent network, and are not necessarily on at the same time, each device changes its own inventory as and when updates happen, then upon network connection, sends each mutation operation into the cloud to find any other device with the same clientID , syncMutations then listens to the create event and makes its own update to its local (but backed up online) version of inventory.所以假设我有两个客户端都有间歇性网络,并且不一定同时打开,每个设备都会在更新发生时更改自己的库存,然后在网络连接时,将每个突变操作发送到云中以查找任何具有相同clientID的其他设备,syncMutations 然后侦听 create 事件并对其本地(但在线备份)版本的清单进行自己的更新。 It also then adds its own clientID to the list of clients that have become up to date.然后它还将自己的clientID添加到已更新的客户端列表中。

Later you could run a test to get rid of all syncedMutations which have every client in the completed list.稍后您可以运行测试以摆脱所有已完成列表中的每个客户端的所有 syncedMutations。

I'm sure there's a better or more robust method for this but it is working as expected while keeping all the logic on device.我确信有一个更好或更强大的方法可以解决这个问题,但它可以按预期工作,同时将所有逻辑保留在设备上。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM