英文:
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: "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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论