如何为GAE数据存储数据模型创建两个唯一的,可查询的字段?

时间:2011-07-05 14:36:21

标签: python google-app-engine transactions google-cloud-datastore

首先进行一点设置。上周我无法实现我构建的特定方法,这将允许我管理与一个db.Model对象关联的两个唯一字段。由于这是不可能的,我创建了一个父实体类和一个子实体类,每个类都为key_name分配了一个唯一值。您可以找到我之前的问题located here,其中包括我的示例代码和我的插入过程的一般说明。

在我原来的问题上,有人评论说我的解决方案无法解决我需要两个与一个db.Model对象关联的唯一字段的问题。

我的实现尝试通过实现创建ParentEntity的静态方法来解决此问题,并将其key_name属性分配给我的一个唯一值。在我的过程的第二步中,我创建了一个子实体,并将父实体分配给父参数。这两个步骤都在db事务中执行,因此我认为这会强制唯一性约束工作,因为我的两个值都存储在两个独立模型中的两个单独的key_name字段中。

评论者指出,此解决方案不起作用,因为当您将父级设置为子实体时,key_name在整个模型中不再是唯一的,而是在父子条目中是唯一的。长号...

我相信我可以通过改变这两个模型彼此关联的方式来解决这个新问题。

首先,我创建一个如上所述的父对象。接下来,我创建一个子实体,并将其第二个唯一值赋给它的key_name。不同之处在于第二个实体具有父模型的引用属性。我的第一个实体被分配给引用属性但不分配给父参数。这不会强制一对一引用,但它确实保持我的两个值都是唯一的,只要我可以在事务中控制插入过程,我就可以管理这些对象的一对一性质。

这个新解决方案仍然存在问题。根据GAE数据存储区文档,如果更新中的各个实体不属于同一实体组,则无法在一个事务中执行多个数据库更新。由于我不再将我的第一个实体作为第二个实体的父级,因此它们不再是同一实体组的一部分,也不能插入同一个事务中。

我回到原点。我该怎么做才能解决这个问题?具体来说,我该怎么做才能强制执行与一个Model实体关联的两个唯一值。如你所见,我愿意有点创意。可以这样做吗?我知道这将涉及一个开箱即用的解决方案,但必须有办法。

以下是我上周发布的问题的原始代码。我添加了一些注释和代码更改,以实现我第二次尝试解决此问题。

class ParentEntity(db.Model):
    str1_key =  db.StringProperty()
    str2 =      db.StringProperty()

    @staticmethod
    def InsertData(string1, string2, string3):
        try:
            def txn():
                #create first entity
                prt = ParentEntity(
                    key_name=string1, 
                    str1_key=string1, 
                    str2=string2)
                prt.put()

                #create User Account Entity
                    child = ChildEntity(
                    key_name=string2, 
                    #parent=prt, #My prt object was previously the parent of child
                    parentEnt=prt,
                    str1=string1, 
                    str2_key=string2,
                    str3=string3,)
                child.put()
                return child
            #This should give me an error, b/c these two entities are no longer in the same entity group. :(
            db.run_in_transaction(txn)
        except Exception, e:
            raise e

class ChildEntity(db.Model):
    #foreign and primary key values
    str1 =      db.StringProperty()
    str2_key =  db.StringProperty()

    #This is no longer a "parent" but a reference
    parentEnt = db.ReferenceProperty(reference_class=ParentEntity)
    #pertinent data below
    str3 =      db.StringProperty()

2 个答案:

答案 0 :(得分:1)

您描述的系统将以交易性为代价。请注意,第二个实体不再是子实体 - 它只是具有ReferenceProperty的另一个实体。

此解决方案可能足以满足您的需求 - 例如,如果您需要强制每个用户都有唯一的电子邮件地址,但这不是您的主要用户标识符,则可以在“电子邮件”中插入记录首先是表,然后如果成功,则插入主记录。如果在第一次操作之后但在第二次操作之前发生故障,则您的电子邮件地址与没有记录相关联。您可以简单地忽略此记录,或者记录时间戳,并允许在一段时间后回收它(例如,30秒,前端请求的最大长度)。

如果您对事务性和唯一性的要求比此强,那么还有其他选项会增加复杂程度,例如实现某种形式的distributed transactions,但实际上您不太可能需要它。如果您可以告诉我们有关记录性质和唯一密钥的更多信息,我们可能会提供更详细的建议。

答案 1 :(得分:0)

我的头稍微刮了一下,昨晚我决定采用以下解决方案。我认为这仍然为许多情况提供了一些不良开销,但是,我认为开销可能是我可以接受的。

下面发布的代码是我问题中代码的进一步修改。最值得注意的是,我创建了另一个Model类,名为 EGEnforcer (代表Entity Group Enforcer。)

这个想法很简单。如果事务只能更新多个记录(如果它们与一个实体组相关联),我必须找到一种方法将包含我的唯一值的每个记录与同一个实体组相关联。

为此,我在应用程序最初启动时创建一个EGEnforcer条目。然后,当需要在我的模型中创建一个新条目时,我查询 EGEnforcer 以查找与我的配对模型相关联的记录。在我获得 EGEnforcer 记录后,我将其作为两个记录的父级。中提琴!我的数据现在都与同一个实体组相关联。

由于* key_name *参数仅在parent-key_name组中是唯一的,因此这应该强制我的唯一性约束,因为我的所有 FirstEntity (以前的 ParentEntity )条目都将拥有相同的父母。同样,我的 SecondEntity (之前的 ChildEntity )也应该具有存储为key_name的唯一值,因为父级也始终相同。

由于两个实体也具有相同的父级,因此我可以在同一事务中执行这些条目。如果一个人失败了,他们都会失败。

#My new class containing unique entries for each pair of models associated within one another.
class EGEnforcer(db.Model): 
KEY_NAME_EXAMPLE = 'arbitrary unique value'

    @staticmethod
    setup():
        ''' This only needs to be called once for the lifetime of the application. setup() inserts a record into EGEnforcer that will be used as a parent for FirstEntity and SecondEntity entries.  '''
        ege = EGEnforcer.get_or_insert(EGEnforcer.KEY_NAME_EXAMPLE)
    return ege

class FirstEntity(db.Model):
    str1_key =  db.StringProperty()
    str2 =      db.StringProperty()

    @staticmethod
    def InsertData(string1, string2, string3):
        try:
            def txn():
                ege = EGEnforcer.get_by_key_name(EGEnforcer.KEY_NAME_EXAMPLE)
                prt = FirstEntity(
                    key_name=string1, 
                    parent=ege) #Our EGEnforcer record.
                prt.put()

                child = SecondEntity(
                    key_name=string2, 
                    parent=ege, #Our EGEnforcer record.
                    parentEnt=prt,
                    str1=string1, 
                    str2_key=string2,
                    str3=string3)
                child.put()
                return child
        #This works because our entities are now part of the same entity group
            db.run_in_transaction(txn)
        except Exception, e:
            raise e

class SecondEntity(db.Model):
    #foreign and primary key values
    str1 =      db.StringProperty()
    str2_key =  db.StringProperty()

    #This is no longer a "parent" but a reference
    parentEnt = db.ReferenceProperty(reference_class=ParentEntity)

#Other data...
    str3 =      db.StringProperty()

一个简短的说明 - 尼克约翰逊坚持我对这个解决方案的需求:

  

这个解决方案可能就足够了   您的需求 - 例如,如果您需要   强制每个用户都有   唯一的电子邮件地址,但事实并非如此   您的用户的主要标识符,   你可以将记录插入到   首先是'电子邮件'表,然后是那个   成功,插入您的主要记录。

这正是我的需要,但我的解决方案显然与您的建议略有不同。我的方法允许事务完全发生或完全失败。具体而言,当用户创建帐户时,他们首先登录其Google帐户。接下来,如果在 SecondEntity 中没有与其Google帐户关联的条目(实际情况 UserAccount ,那么他们将被强制进入帐户创建页面。)如果插入如果进程失败,它们将被重定向到创建页面,并说明此失败的原因。

这可能是因为他们的ID不是唯一的,或者可能是事务超时。如果插入新用户帐户时超时,我会想知道它,但我会在不久的将来实施某种形式的检查和平衡。现在我只想上线,但这种独特性限制是绝对必要的。

由于我的方法仅限于创建帐户,并且我的用户帐户数据一旦创建就不会更改,我相信这应该可以工作并且可以在很长一段时间内很好地扩展。如果这不正确,我愿意接受评论。