我正在编写一个项目,我需要在C ++中实现ORM解决方案的精简版本。我很擅长实现1-n关系。
例如,如果以下是类:
class A
{
...
}
class B
{
...
std::list<A> _a_list;
...
}
我已经提供了加载/保存方法来加载/保存到db。 现在,如果我采用B的情况和以下工作流程:
现在,我需要使用类似“b.save()”的更新数据库。 那么,保存更改的最佳方法是什么,即识别_a_list的添加,删除和更新。
答案 0 :(得分:3)
我的第一个想法是将所有可能的数据库操作封装为命令对象(命令模式)。这样,您可以创建任意数量的命令,直到调用Save()方法更新数据库。在这里,您需要确保将这些命令作为事务处理。快速实现将是这样的:
标题强>
#include <vector>
using namespace std;
class B;
class Cmd;
class B
{
private:
vector<Cmd*> m_commands;
public:
void AddCmd( Cmd* p_command );
void Save();
};
class Cmd
{
protected:
B* m_database;
public:
Cmd( B* p_database );
virtual void Execute() = 0;
virtual void Undo() = 0;
};
class InsertCmd : public Cmd
{
private:
int m_newEntry;
public:
InsertCmd( B* p_database, int p_newEntry );
void Execute() { cout << "insert " << m_newEntry << endl; }
void Undo() { /* undo of insert */ }
};
<强>来源:强>
#include "DbClass.h"
void B::AddCmd( Cmd* p_command )
{
m_commands.push_back(p_command);
}
void B::Save()
{
for( unsigned int i=0; i<m_commands.size(); i++ )
m_commands[i]->Execute();
}
Cmd::Cmd( B* p_database ) : m_database(p_database)
{
m_database->AddCmd(this);
}
InsertCmd::InsertCmd( B* p_database, int p_newEntry )
: Cmd(p_database), m_newEntry(p_newEntry)
{
}
测试主要:
#include "DbClass.h"
int main()
{
B database;
InsertCmd insert( &database, 10 );
database.Save();
return 0;
}
答案 1 :(得分:1)
一种策略是使用枚举来表示记录的“状态”。即
enum RecordState {
RECORD_UNMODIFIED,
RECORD_NEW,
RECORD_CHANGED,
RECORD_DELETED
};
您将为每条记录分配一个RecordState(默认情况下默认为RECORD _NEW / RECORD _UNMODIFIED),并且在调用Save()时,它将对每条记录执行适当的操作并将其状态重置为RECORD _UNMODIFIED。删除将在处理时从列表中删除。
答案 2 :(得分:1)
记录状态确实是一个好主意。
我建议:
(a)应用程序将已删除的对象保留在数组中,并且只有在调用类似ORM的代码进行保存时才会删除它们(即它执行INSERT,UPDATE和DELETE时)
OR
(b)ORM上下文需要在内部维护所有对象的幕后列表,这些对象已从磁盘中选择或在RAM中为每个数据库事务创建(或者如果不使用事务,连接)。当要求ORM保存并且INSERT,UPDATE和DELETE基于此列表时,将重复此列表。
在第二种情况下,您经常会发现一个额外的要求,即能够在系统的某些部分中将对象与ORM分离/分离,以创建状态或修改版本的持久快照对于(根据某些高级数据流模型或其他)不立即存储的对象,因此需要额外的位或枚举状态来反映分离。您可能希望能够使用ORM事务重新关联/重新附加对象,但请注意,此处可能存在完整性问题,如果需要处理它们,则处理它们的方法通常是特定于应用程序的。
请注意,在第一次保存之前删除的新创建的对象不应生成SQL DELETE,因此实际上UNMODIFED,NEW,CHANGED,DELETED的枚举通常是不够的,您还需要NEW_DELETED并且如果我的理论一致分离。
答案 3 :(得分:1)
有点晚但在我看来你需要/需要的是Unit Of Work。你当前的设计就像一个Registry,与UoW很好地搭配。
答案 4 :(得分:1)
我想分享我对如何实现“1到n”关系的看法。 Side“1”是主表,而side“n”对应于slave(子)表。我想,我们想要操纵双方的关系。从奴隶点来看,关系看起来像一个对象属性,可能具有设置/更改/清除属性指定的对象引用的能力。从主人的角度来看,相同的关系将是类似集合的属性,为我们提供了迭代/添加/删除该集合中的对象引用的方法。 由于对关系一方的任何更改必须立即从另一方提供,我们有两种选择:
在两者之间进行选择涉及回答几个重要问题:
在任何一种情况下都是一些特征,通常存在于任何类似ORM的解决方案中。
例如,IdentityMap设计模式假定您注册所有映射类的实例,这些实例应该在某种注册表中将更改呈现给数据库。这对于稍后执行“刷新”操作是必要的。当然,这需要保持记录状态。
我发现“关系实例”方法相对容易实现。您可以在我仍处于开发阶段的C ++通用ORM解决方案中找到实现:YB.ORM。特别是,看看源文件DataObject.h,DataObject.cpp和TestDataObject.cpp(文件夹lib / orm /)中的测试。
与示例代码相比,YB.ORM库在内部使用变量类型对象和静态类型的“瘦”包装器。
DataObject
类表示映射类的实例,其中映射规则在元数据描述中给出。这些对象总是在堆中分配,不可复制。它们存储数据值。它们具有指向映射表的元数据信息的链接。当然,在这些对象中保持当前状态(New
,Ghost
,Dirty
,Sync
,ToBeDeleted
,Deleted
之一。为了支持此类呈现“n”侧的关系,它们中的每一个都有一组指向RelationObject
类(slave_relations_ member)实例的指针。为了支持此类呈现“1”的关系,每个关系都有一组指向RelationObject
类(master_relations_ member)实例的共享指针。
RelationObject
类表示关系的实例。这些对象总是在堆中分配,不可复制。它们存储和枚举指向相关DataObject
实例的指针:一个指向master的指针,以及一组指向slave的共享指针。因此,它们“拥有”从属DataObject
实例,DataObject
实例“拥有”(间接)所有从属对象。注意,RelationObject
本身维护状态,以支持延迟加载。