请原谅我,如果这是一个显而易见的问题,但我一般都是小马和数据库的新手,并没有找到回答这个问题的文档的正确部分。
我正在尝试使用公司和这些公司设有办事处的地点创建数据库。这是一种多对多的关系,因为每个公司都在多个地点,每个地点都可以托管多家公司。我是这样定义我的实体的:
from pony import orm
class Company(db.Entity):
'''A company entry in database'''
name = orm.PrimaryKey(str)
locations = orm.Set('Location')
class Location(db.Entity):
'''A location for a company'''
name = orm.PrimaryKey(str)
companies = orm.Set('Company')
理想情况下,我希望能够编写一个将公司添加到数据库的功能,同时还添加该公司所在位置的列表,同时确保添加新的位置实例(如果他们没有&#39) ; t已经存在。我可以很快想到两种方法。
首先是尝试输入该位置,即使它存在并处理异常:
@orm.db_session
def add_company(name, locations):
loc_entities = []
for l in locations:
try:
loc = Location[l]
except orm.core.ObjectNotFound:
loc = Location(name=l)
else:
loc_entities.append(loc)
comp = Company(name=name, locations=loc_entities)
其次是查询数据库并询问这些位置是否存在:
@orm.db_session
def add_company2(name, locations):
old_loc_entities = orm.select(l for l in Location if l.name in locations)[:]
old_locations = [l.name for l in old_loc_entities]
new_locations = set(locations) - (set(locations) & set(old_locations))
loc_entities = [Location(name=l) for l in new_locations] + old_loc_entities
comp = Company(name=name, locations=loc_entities)
在这两个中,我猜测更简单的方法就是简单地处理异常,但这会遇到N + 1问题吗?我注意到,通过使用名称作为主键,我每次使用索引访问实体时都会进行查询。当我让小马选择顺序ID时,我似乎不需要查询。我还没有对任何大型数据集进行测试,所以我还没有进行过基准测试。
答案 0 :(得分:4)
我注意到,通过使用名称作为主键,我每次使用索引访问实体时都会进行查询。当我让小马选择顺序ID时,我似乎不需要查询。
内部Pony以与字符串主键相同的方式缓存顺序主键,因此我认为应该没有区别。每个db_session
都有单独的缓存(称为"身份映射")。读取对象后,同一db_session
内的主键(或任何其他唯一键)的任何访问都应直接从标识映射返回相同的对象,而不发出新查询。 db_session
结束后,相同密钥的另一次访问将发出新查询,因为可以通过并发事务在数据库中修改该对象。
关于你的方法,我认为它们都是有效的。如果一家公司只有几个位置(比如说,大约十个),我会使用第一种方法,因为它对我来说感觉更加苛刻。它确实导致N + 1查询,但是通过主键检索对象的查询非常快速且易于服务器执行。使用get
方法可以将代码表达得更紧凑:
@orm.db_session
def add_company(name, locations):
loc_entities = [Location.get(name=l) or Location(name=l)
for l in locations]
comp = Company(name=name, locations=loc_entities)
使用单个查询检索所有现有位置的第二种方法对我来说过早优化,但如果每秒创建数百家公司,并且每家公司都有数百个位置,则可以使用它。
答案 1 :(得分:3)
我知道这是"获取或创建"模式,无论是ORM还是语言,都必须实现它。
这是我的#34;获取或创建"对于小马。
class GetMixin():
@classmethod
def get_or_create(cls, params):
o = cls.get(**params)
if o:
return o
return cls(**params)
class Location(db.Entity, GetMixin):
'''A location for a company'''
name = orm.PrimaryKey(str)
companies = orm.Set('Company')
Mixin在docs上解释。
然后您的代码将如下所示:
@orm.db_session
def add_company(name, locations):
loc_entities = [Location.get_or_create(name=l) for l in locations]
comp = Company(name=name, locations=loc_entities)