diff --git a/src/common/database/api/crud.py b/src/common/database/api/crud.py index 0bbd76083..a82b2a3a5 100644 --- a/src/common/database/api/crud.py +++ b/src/common/database/api/crud.py @@ -108,20 +108,18 @@ class CRUDBase: instance = result.scalar_one_or_none() if instance is not None: - # 预加载所有字段 - for column in self.model.__table__.columns: - try: - getattr(instance, column.name) - except Exception: - pass - - # 转换为字典并写入缓存 + # ✅ 在 session 内部转换为字典,此时所有字段都可安全访问 + instance_dict = _model_to_dict(instance) + + # 写入缓存 if use_cache: - instance_dict = _model_to_dict(instance) cache = await get_cache() await cache.set(cache_key, instance_dict) + + # 从字典重建对象返回(detached状态,所有字段已加载) + return _dict_to_model(self.model, instance_dict) - return instance + return None async def get_by( self, @@ -159,21 +157,18 @@ class CRUDBase: instance = result.scalar_one_or_none() if instance is not None: - # 触发所有列的加载,避免 detached 后的延迟加载问题 - # 遍历所有列属性以确保它们被加载到内存中 - for column in self.model.__table__.columns: - try: - getattr(instance, column.name) - except Exception: - pass # 忽略访问错误 - - # 转换为字典并写入缓存 + # ✅ 在 session 内部转换为字典,此时所有字段都可安全访问 + instance_dict = _model_to_dict(instance) + + # 写入缓存 if use_cache: - instance_dict = _model_to_dict(instance) cache = await get_cache() await cache.set(cache_key, instance_dict) + + # 从字典重建对象返回(detached状态,所有字段已加载) + return _dict_to_model(self.model, instance_dict) - return instance + return None async def get_multi( self, @@ -222,21 +217,16 @@ class CRUDBase: result = await session.execute(stmt) instances = list(result.scalars().all()) - # 触发所有实例的列加载,避免 detached 后的延迟加载问题 - for instance in instances: - for column in self.model.__table__.columns: - try: - getattr(instance, column.name) - except Exception: - pass # 忽略访问错误 - - # 转换为字典列表并写入缓存 + # ✅ 在 session 内部转换为字典列表,此时所有字段都可安全访问 + instances_dicts = [_model_to_dict(inst) for inst in instances] + + # 写入缓存 if use_cache: - instances_dicts = [_model_to_dict(inst) for inst in instances] cache = await get_cache() await cache.set(cache_key, instances_dicts) - - return instances + + # 从字典列表重建对象列表返回(detached状态,所有字段已加载) + return [_dict_to_model(self.model, d) for d in instances_dicts] async def create( self, diff --git a/src/common/database/api/query.py b/src/common/database/api/query.py index 408ad6b2f..02cca7c12 100644 --- a/src/common/database/api/query.py +++ b/src/common/database/api/query.py @@ -205,21 +205,16 @@ class QueryBuilder(Generic[T]): result = await session.execute(self._stmt) instances = list(result.scalars().all()) - # 预加载所有列以避免detached对象的lazy loading问题 - for instance in instances: - for column in self.model.__table__.columns: - try: - getattr(instance, column.name) - except Exception: - pass - - # 转换为字典列表并写入缓存 + # ✅ 在 session 内部转换为字典列表,此时所有字段都可安全访问 + instances_dicts = [_model_to_dict(inst) for inst in instances] + + # 写入缓存 if self._use_cache: - instances_dicts = [_model_to_dict(inst) for inst in instances] cache = await get_cache() await cache.set(cache_key, instances_dicts) - - return instances + + # 从字典列表重建对象列表返回(detached状态,所有字段已加载) + return [_dict_to_model(self.model, d) for d in instances_dicts] async def first(self) -> T | None: """获取第一个结果 @@ -243,21 +238,19 @@ class QueryBuilder(Generic[T]): result = await session.execute(self._stmt) instance = result.scalars().first() - # 预加载所有列以避免detached对象的lazy loading问题 if instance is not None: - for column in self.model.__table__.columns: - try: - getattr(instance, column.name) - except Exception: - pass - - # 转换为字典并写入缓存 - if instance is not None and self._use_cache: + # ✅ 在 session 内部转换为字典,此时所有字段都可安全访问 instance_dict = _model_to_dict(instance) - cache = await get_cache() - await cache.set(cache_key, instance_dict) + + # 写入缓存 + if self._use_cache: + cache = await get_cache() + await cache.set(cache_key, instance_dict) + + # 从字典重建对象返回(detached状态,所有字段已加载) + return _dict_to_model(self.model, instance_dict) - return instance + return None async def count(self) -> int: """统计数量 diff --git a/src/person_info/relationship_fetcher.py b/src/person_info/relationship_fetcher.py index 89c07a3ec..fbf98436f 100644 --- a/src/person_info/relationship_fetcher.py +++ b/src/person_info/relationship_fetcher.py @@ -196,31 +196,18 @@ class RelationshipFetcher: if relationship: # 将SQLAlchemy对象转换为字典以保持兼容性 - # 使用 inspect 安全访问 detached 对象,避免触发 lazy loading - from sqlalchemy import inspect as sa_inspect - - rel_data = {} + # 直接使用 __dict__ 访问,避免触发 SQLAlchemy 的描述符和 lazy loading + # 方案A已经确保所有字段在缓存前都已预加载,所以 __dict__ 中有完整数据 try: - # 使用 inspect 获取对象状态,避免触发 ORM 机制 - state = sa_inspect(relationship) rel_data = { - "user_aliases": state.attrs.user_aliases.value if hasattr(state.attrs, "user_aliases") else None, - "relationship_text": state.attrs.relationship_text.value if hasattr(state.attrs, "relationship_text") else None, - "preference_keywords": state.attrs.preference_keywords.value if hasattr(state.attrs, "preference_keywords") else None, - "relationship_score": state.attrs.relationship_score.value if hasattr(state.attrs, "relationship_score") else None, + "user_aliases": relationship.__dict__.get("user_aliases"), + "relationship_text": relationship.__dict__.get("relationship_text"), + "preference_keywords": relationship.__dict__.get("preference_keywords"), + "relationship_score": relationship.__dict__.get("relationship_score"), } except Exception as attr_error: logger.warning(f"访问relationship对象属性失败: {attr_error}") - # 如果 inspect 也失败,尝试使用 __dict__ 直接访问 - try: - rel_data = { - "user_aliases": relationship.__dict__.get("user_aliases"), - "relationship_text": relationship.__dict__.get("relationship_text"), - "preference_keywords": relationship.__dict__.get("preference_keywords"), - "relationship_score": relationship.__dict__.get("relationship_score"), - } - except Exception: - rel_data = {} + rel_data = {} # 5.1 用户别名 if rel_data.get("user_aliases"): @@ -284,26 +271,18 @@ class RelationshipFetcher: return "" # 将SQLAlchemy对象转换为字典以保持兼容性 - # 使用 inspect 安全访问 detached 对象,避免触发 lazy loading - from sqlalchemy import inspect as sa_inspect - + # 直接使用 __dict__ 访问,避免触发 SQLAlchemy 的描述符和 lazy loading + # 方案A已经确保所有字段在缓存前都已预加载,所以 __dict__ 中有完整数据 try: - state = sa_inspect(stream) - stream_data = { - "group_name": state.attrs.group_name.value if hasattr(state.attrs, "group_name") else None, - "stream_impression_text": state.attrs.stream_impression_text.value if hasattr(state.attrs, "stream_impression_text") else None, - "stream_chat_style": state.attrs.stream_chat_style.value if hasattr(state.attrs, "stream_chat_style") else None, - "stream_topic_keywords": state.attrs.stream_topic_keywords.value if hasattr(state.attrs, "stream_topic_keywords") else None, - } - except Exception as e: - logger.warning(f"访问stream对象属性失败: {e}") - # 回退到 __dict__ 访问 stream_data = { "group_name": stream.__dict__.get("group_name"), "stream_impression_text": stream.__dict__.get("stream_impression_text"), "stream_chat_style": stream.__dict__.get("stream_chat_style"), "stream_topic_keywords": stream.__dict__.get("stream_topic_keywords"), } + except Exception as e: + logger.warning(f"访问stream对象属性失败: {e}") + stream_data = {} impression_parts = []