如何在Google App Engine Datastore上使用Go语言实现动态属性?

huangapple go评论85阅读模式
英文:

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

问题

我想在Go语言中实现类似Python在App Engine上支持的Expando Model

有时候你可能不想提前声明属性。一个特殊的模型子类,Expando,改变了其实体的行为,使得任何被赋值的属性(只要不以下划线开头)都会保存到Datastore中。

在Go语言中,我该如何实现这个功能呢?

英文:

I want to do something like the Expando Model that python supports on app engine.

> Sometimes you don't want to declare your properties ahead of time. A
> special model subclass, Expando, changes the behavior of its entities
> so that any attribute assigned (as long as it doesn't start with an
> underscore) is saved to the Datastore.

How can I do this in Go?

答案1

得分: 8

事先注意:

有两个API。导入路径为appengine/datastore的API使用通道作为参数。导入路径为google.golang.org/appengine/datastore的API使用切片。根据您的情况调整下面的示例。有关详细信息,请参阅此问题:https://stackoverflow.com/questions/32271148/how-to-correctly-import-golang-appengine


动态属性实体的关键是PropertyLoadSaver接口。通过实现此接口,您可以在保存时动态地构造要保存的实体的属性。

此外,为了不必自己做这个,Go AppEngine平台提供了一个PropertyList类型,它基本上是一个属性Property的列表(切片),并且它也实现了PropertyLoadSaver

因此,Go中的Expando模型是PropertyList。只需添加您希望实体具有的属性,并保存此PropertyList值。

这是一个示例:

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)

此示例保存了一个名为"DynEntity"的实体,具有两个动态属性:"time""email"

由于PropertyList类型是一个切片,您还可以使用内置的append()函数向其添加属性,因此您还可以像这样初始化props

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"})

map转换为动态实体

PropertyLoadSaver接口并不复杂,我们可以自己实现它。在下面的示例中,我在一个自定义类型上实现了它,该类型是一个简单的map

type DynEnt map[string]interface{}

func (d *DynEnt) Load(props []datastore.Property) error {
    // 注意:您可能希望从映射中清除当前值或创建一个新映射
    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
}

以下是使用旧的使用通道而不是切片的接口实现的示例:

type DynEnt map[string]interface{}

func (d *DynEnt) Load(ch <-chan datastore.Property) error {
    // 注意:您可能希望从映射中清除当前值或创建一个新映射
    for p := range ch { // 读取直到通道关闭
        (*d)[p.Name] = p.Value
    }
    return nil
}

func (d *DynEnt) Save(ch chan<- datastore.Property) error {
    defer close(ch) // 必须关闭通道
    for k, v := range *d {
        ch <- datastore.Property{Name: k, Value: v}
    }
    return nil
}

现在,我们可以像在Go中使用任何其他映射一样使用我们的DynEnt类型,并且由于它实现了PropertyLoadSaver,因此它可以保存为实体(并且可以将_任何_实体加载到其中):

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)
英文:

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: https://stackoverflow.com/questions/32271148/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: &quot;time&quot;, Value: time.Now()},
	datastore.Property{Name: &quot;email&quot;, Value: &quot;me@myhost.com&quot;},
}

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

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

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:&quot;time&quot;, Value: time.Now()})
props = append(props, datastore.Property{Name:&quot;email&quot;, Value: &quot;me@myhost.com&quot;})

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 &lt;-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&lt;- datastore.Property) error {
	defer close(ch) // Channel must be closed
	for k, v := range *d {
		ch &lt;- 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{&quot;email&quot;: &quot;me@myhost.com&quot;, &quot;time&quot;: time.Now()}

k := datastore.NewIncompleteKey(c, &quot;DynEntity&quot;, nil)
key, err := datastore.Put(c, k, &amp;d)
c.Infof(&quot;%v %v&quot;, key, err)

huangapple
  • 本文由 发表于 2015年4月27日 08:44:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/29885431.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定