【问题标题】:How can I have dynamic properties in go on the google app engine datastore如何在谷歌应用引擎数据存储中使用动态属性
【发布时间】:2021-09-15 18:07:44
【问题描述】:

我想做一些类似于 python 在应用引擎上支持的Expando Model 的事情。

有时您不想提前声明您的属性。一种 特殊模型子类 Expando 改变其实体的行为 以便分配的任何属性(只要它不以 下划线)保存到数据存储区。

如何在 Go 中做到这一点?

【问题讨论】:

    标签: google-app-engine go google-cloud-datastore


    【解决方案1】:

    事先说明:

    有 2 个 API。具有导入路径appengine/datastore 的那个使用通道作为参数。另一个导入路径google.golang.org/appengine/datastore 使用切片。根据您的情况调整以下示例。详情见这个问题: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" 的实体,该实体具有 2 个动态属性:"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 {
        // 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
    }
    

    以下是使用通道而不是切片的“旧”接口的实现方式:

    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
    }
    

    现在我们可以像使用 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)
    

    【讨论】:

    • 为什么是渠道?在文档中,PropertyLoadSaver 接口使用切片进行操作?
    • @AlexanderTrakhimenok 有 2 个 API。具有导入路径appengine/datastore 的那个使用通道作为参数。另一个导入路径google.golang.org/appengine/datastore 使用切片。根据您的情况调整上面的示例。编辑了答案。
    • 这会处理嵌套对象吗?即,p.Value 可以是另一个 DynEnt 吗?
    • @Gubbi 数据存储实体是“扁平的”(无嵌套),因此尝试保存嵌套的 DynEnt 毫无意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多