How can I have dynamic properties in go on the google app engine datastore

心已入冬 提交于 2019-11-29 02:44:54

Note beforehand:

There are 2 APIs. The one with import path appengine/datastore uses channels as arguments. The other with import path google.golang.org/appengine/datastore uses slices. Adjust the example below to your case. See this question for details: How to correctly import Golang appengine?


The key to an entity with dynamic properties is the PropertyLoadSaver interface. By implementing this interface you can dynamically - at save time - construct the properties of the entity to be saved.

Also to not have to do this yourself the Go AppEngine platform provides a PropertyList type which is basically a list (a slice) of properties Property and it also implements PropertyLoadSaver.

So the Expando model in Go is PropertyList. Just add the properties you want your entity to have, and save this PropertyList value.

Here is an example:

c := appengine.NewContext(r)

props := datastore.PropertyList{
    datastore.Property{Name: "time", Value: time.Now()},
    datastore.Property{Name: "email", Value: "me@myhost.com"},
}

k := datastore.NewIncompleteKey(c, "DynEntity", nil)
key, err := datastore.Put(c, k, &props)
c.Infof("%v %v", key, err)

This example saves an entity named "DynEntity" with 2 dynamic properties: "time" and "email".

As the PropertyList type is a slice, you can also use the builtin append() function to add properties to it, so you could also initialize props like this:

var props datastore.PropertyList
props = append(props, datastore.Property{Name:"time", Value: time.Now()})
props = append(props, datastore.Property{Name:"email", Value: "me@myhost.com"})

Turning a map into a dynamic entity

The PropertyLoadSaver interface is not complicated, we can implement it ourselves. In the following example I implement it on a custom type which is a simple map:

type DynEnt map[string]interface{}

func (d *DynEnt) Load(props []datastore.Property) error {
    // Note: you might want to clear current values from the map or create a new map
    for _, p := range props {
        (*d)[p.Name] = p.Value
    }
    return nil
}

func (d *DynEnt) Save() (props []datastore.Property, err error) {
    for k, v := range *d {
        props = append(props, datastore.Property{Name: k, Value: v})
    }
    return
}

Here's how the implementation would look like with the "old" interface that used channels instead of slices:

type DynEnt map[string]interface{}

func (d *DynEnt) Load(ch <-chan datastore.Property) error {
    // Note: you might want to clear current values from the map or create a new map
    for p := range ch { // Read until channel is closed
        (*d)[p.Name] = p.Value
    }
    return nil
}

func (d *DynEnt) Save(ch chan<- datastore.Property) error {
    defer close(ch) // Channel must be closed
    for k, v := range *d {
        ch <- datastore.Property{Name: k, Value: v}
    }
    return nil
}

Now we can use our DynEnt type just like any other maps in Go, and since it implements PropertyLoadSaver, it can be saved as an entity (and any entities can be loaded into it):

c := appengine.NewContext(r)

d := DynEnt{"email": "me@myhost.com", "time": time.Now()}

k := datastore.NewIncompleteKey(c, "DynEntity", nil)
key, err := datastore.Put(c, k, &d)
c.Infof("%v %v", key, err)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!