Работа с базами данных
Здесь все, что связано с разработкой баз данных.
Как мне динамически сформировать строку подключения к базе данных?
Ответ:
Строка подключения для компонетов должна выглядеть следующим образом:
Для MSSQLServer:
Код:
WideString ConnectionString ="Provider=SQLNCLI.1;Password=<Ваш пароль>;Persist Security Info=True;User ID=<Ваш пользователь>;Initial Catalog=<имя вашей базы>;Data Source=<Имя сервера>;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=<имя локальной машины>;Use Encryption for Data=False;Tag with column collation when possible=False;MARS Connection=False;DataTypeCompatibility=0;Trust Server Certificate=False";
Для MSSQLServer с динамическим присоединением базы:
Код:
WideString ConnectionString ="Provider=SQLNCLI.1;Password=<Ваш пароль>;Persist Security Info=True;User ID=<Ваш пользователь>;Initial Catalog=<имя вашей базы>;Data Source=<Имя сервера>;Extended Properties=\"<Имя базы=Путь к файлу БД>\";Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=<имя локальной машины>;Use Encryption for Data=False;Tag with column collation when possible=False;MARS Connection=False;DataTypeCompatibility=0;Trust Server Certificate=False";
Для MSAccess:
Код:
WideString ConnectionString ="Provider=MSDASQL.1;Persist Security Info=False;Extended Properties=\"DSN=<Имя ДСН-подключения>;DBQ=<Имя файла>;DriverId=25;FIL=MS Access;MaxBufferSize=2048;PageTimeout=5;UID=<admin>;\""
Для MSExcel:
Код:
WideString ConnectionString = "Provider=MSDASQL.1;Persist Security Info=False;Extended Properties=\"DSN=<Имя ДСН-подключения>;DBQ=<Имя файла>;DefaultDir=<Директория>;DriverId=790;FIL=excel 8.0;MaxBufferSize=2048;PageTimeout=5;\";Initial Catalog=<Имя файла>";
там где необходимо экранировать кавычки в строке - они экранированы. Соответственно вместо значений в угловых скобках вы должны подставить свои.
Пример
формирование строки подключения для MSSQLServer:
Код:
TADOConection *adoConnect = new TADOConnection(this);
WideString provider = "SQLNCLI.1";
WideString pass = "bez parola";
WideString user = "petya vasechkin";
WideString base = "base";
WideString server = "192.168.1.100,1430";
char buff[MAX_SIZE];
DWORD sizebuff = MAX_SIZE;
GetComputerName(buff,&size);
WideString ConnectionString ="Provider="+provider+";Password="+pass+";Persist Security Info=True;User ID="+user+";Initial Catalog="+base+";Data Source="+server+";Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID="+((WideString)buff)+";Use Encryption for Data=False;Tag with column collation when possible=False;MARS Connection=False;DataTypeCompatibility=0;Trust Server Certificate=False";
adoConnect->ConnectionString = ConnectionString;
adoConnect->LoginPrompt = false;
adoConnect->Connection = true;
WideString provider = "SQLNCLI.1";
WideString pass = "bez parola";
WideString user = "petya vasechkin";
WideString base = "base";
WideString server = "192.168.1.100,1430";
char buff[MAX_SIZE];
DWORD sizebuff = MAX_SIZE;
GetComputerName(buff,&size);
WideString ConnectionString ="Provider="+provider+";Password="+pass+";Persist Security Info=True;User ID="+user+";Initial Catalog="+base+";Data Source="+server+";Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID="+((WideString)buff)+";Use Encryption for Data=False;Tag with column collation when possible=False;MARS Connection=False;DataTypeCompatibility=0;Trust Server Certificate=False";
adoConnect->ConnectionString = ConnectionString;
adoConnect->LoginPrompt = false;
adoConnect->Connection = true;
Обратите внимание на то как задано имя сервера: "192.168.1.100,1430" - т.е. адрес и порт, что бы это сработало, в свойствах сервера должен быть разрешен TCP/IP. Так же к серверу можно обратиться через его инстанс - "DataBaseSrv\MSSQLSERVER" - формат соответственно ДОМЕННОЕ_ИМЯ\ИНСТАНС_СЕРВЕРА. Если сервер запущен на локальной машине - ".\MSSQLSERVER". Не забывайте экранировать слеш.
Как хранить и отображать в базе данных дерево с произвольным уровнем вложенности?
Ответ:
Для отображения структуры дерева в БД вам необходимо создать таблицу, которая имеет следующую структуру:
Код:
nodeid parentid nodename
===== ====== =======
1 0 Ветка первого уровня
2 1 Ветка второго уровня
3 0 Вторая ветка первого уровня
....
===== ====== =======
1 0 Ветка первого уровня
2 1 Ветка второго уровня
3 0 Вторая ветка первого уровня
....
Код создания таблицы для MSSQL выглядит так:
Код:
CREATE TABLE [dbo].[nodes](
[nodeid] [int] IDENTITY(1,1) NOT NULL,
[parentid] [int] NOT NULL,
[nodename] [varchar](512) COLLATE Cyrillic_General_CI_AS NOT NULL,
CONSTRAINT [PK_nodes] PRIMARY KEY CLUSTERED
(
[goodtypeid] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
[nodeid] [int] IDENTITY(1,1) NOT NULL,
[parentid] [int] NOT NULL,
[nodename] [varchar](512) COLLATE Cyrillic_General_CI_AS NOT NULL,
CONSTRAINT [PK_nodes] PRIMARY KEY CLUSTERED
(
[goodtypeid] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
Создаем хранимую процедуру для чтения значений из таблицы (в принципе можно обойтись и запросом - но работать с таблицами напрямую, я лично считаю ошибкой проектирования):
Код:
CREATE PROCEDURE [dbo].[sSelectNodes]
@parentid int
AS
BEGIN
SET NOCOUNT ON;
SELECT nodeid,nodename from nodes
WHERE parentid = @parentid
END
@parentid int
AS
BEGIN
SET NOCOUNT ON;
SELECT nodeid,nodename from nodes
WHERE parentid = @parentid
END
Естественно необходимо индексировать поля по которым идет выборка - но с этим я думаю вы сумеете разобраться сами.
Дальше в коде приложения объявляем следующюю функцию:
Код:
//Объявление
int AddChildCategory(TTreeView *Tree,TTreeNode *Node,const int &parentid);
//Реализация
int AddChildCategory(TTreeView *Tree,TTreeNode *Node,const int &parentid){
//Создаем объект для вызова процедуры
TADOStoredProc *spAddChild = new TADOStoredProc(NULL);
spAddChild->Connection = dmMain->adoConnect; //Устанавливаем соединение
//как устанавливать соединение - смотри предыдущий вопрос.
spAddChild->ProcedureName = "sSelectNodes;1";//Выбираем имя процедуры
spAddChild->Prepared = true;
//Создаем параметр для передачи в процедуру
spAddChild->Parameters->CreateParameter(WideString("@parentid"),ftInteger,pdInput,sizeof(int),0);
//Присваеваем значение ему
spAddChild->Parameters->ParamByName(WideString("@parentid"))->Value = parentid;
//Запускаем
spAddChild->Active = true;
TTreeNode *Child;
String name = ""; //Переменные для имени и идентификатора
int id = 0;
//Перебераем полученные данные
while(!spAddChild->Eof){
id = spAddChild->FieldByName("nodeid")->Value;
name = spAddChild->FieldByName("nodename")->AsString;
//Добавляем ветку
Child = Tree->Items->AddChildObject(Node,name,static_cast<void*>(new int(id)));
//Рекурсивно высываем нашу функцию
AddChildCategory(Tree,Child,id);
//Продолжаем перебор
spAddChild->Next();
}
spAddChild->Active = false;
delete spAddChild;
}
int AddChildCategory(TTreeView *Tree,TTreeNode *Node,const int &parentid);
//Реализация
int AddChildCategory(TTreeView *Tree,TTreeNode *Node,const int &parentid){
//Создаем объект для вызова процедуры
TADOStoredProc *spAddChild = new TADOStoredProc(NULL);
spAddChild->Connection = dmMain->adoConnect; //Устанавливаем соединение
//как устанавливать соединение - смотри предыдущий вопрос.
spAddChild->ProcedureName = "sSelectNodes;1";//Выбираем имя процедуры
spAddChild->Prepared = true;
//Создаем параметр для передачи в процедуру
spAddChild->Parameters->CreateParameter(WideString("@parentid"),ftInteger,pdInput,sizeof(int),0);
//Присваеваем значение ему
spAddChild->Parameters->ParamByName(WideString("@parentid"))->Value = parentid;
//Запускаем
spAddChild->Active = true;
TTreeNode *Child;
String name = ""; //Переменные для имени и идентификатора
int id = 0;
//Перебераем полученные данные
while(!spAddChild->Eof){
id = spAddChild->FieldByName("nodeid")->Value;
name = spAddChild->FieldByName("nodename")->AsString;
//Добавляем ветку
Child = Tree->Items->AddChildObject(Node,name,static_cast<void*>(new int(id)));
//Рекурсивно высываем нашу функцию
AddChildCategory(Tree,Child,id);
//Продолжаем перебор
spAddChild->Next();
}
spAddChild->Active = false;
delete spAddChild;
}
Как использовать -
Код:
void __fastcall TfmMain::actLoadTreeExecute(TObject *Sender)
{
tvMain->Items->Clear();
TTreeNode *nodeparent = tvMain->Items->AddObjectFirst(NULL,"Дерево из базы",static_cast<void*>(new int(-1)));
AddChildCategory(tvMain,nodeparent,0);
nodeparent->Expand(true);
nodeparent->Selected = true;
tvMainClick(tvMain);
}
{
tvMain->Items->Clear();
TTreeNode *nodeparent = tvMain->Items->AddObjectFirst(NULL,"Дерево из базы",static_cast<void*>(new int(-1)));
AddChildCategory(tvMain,nodeparent,0);
nodeparent->Expand(true);
nodeparent->Selected = true;
tvMainClick(tvMain);
}
tvMain - компонент TTreeView
fmMain - форма на которую он положен.
На что обратить внимание:
parentid корня дерева не должен быть равен ни одному из nodeid - т.е. если корень дерева у нас равен 0 - то nodeid со значением 0 недопустим - иначе получите бесконечный цикл.
Как хранить и отображать в базе данных дерево с произвольным уровнем вложенности?
Ответ:
Таблица представлена в виде:
Код:
nodeid parentid nodename
===== ====== =======
1 0 Ветка первого уровня
2 1 Ветка второго уровня
3 0 Вторая ветка первого уровня
===== ====== =======
1 0 Ветка первого уровня
2 1 Ветка второго уровня
3 0 Вторая ветка первого уровня
запрос из БД:
Код:
SELECT * FROM nodes
ORDER BY parentid
ORDER BY parentid
Выжно: т.к. алгоритм является линейным и однопроходным (без рекурсий) необходимо упорядочить записи таким образом что бы для каждой последующай записи (кадого последующего значения поля parentid) уже существовала запись nodeid.
Непосредственно рисуем дерево:
Код:
#include <map>
using namespace std;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
map <int, TTreeNode*>NodesMap;
ADOQuery1->Active = true;
TIntegerField *IDKey = (TIntegerField*)ADOQuery1->FieldByName("nodeid");
TIntegerField *ParentIndex = (TIntegerField*)ADOQuery1->FieldByName("parentid");
TStringField *ItemName = (TStringField*)ADOQuery1->FieldByName("nodename");
int id, level, parent_index;
String item_name;
while (!ADOQuery1->Eof)
{
id = IDKey->AsInteger;
parent_index = ParentIndex->AsInteger;
item_name = ItemName->AsString;
if (parent_index == 0) // если это root уровень
NodesMap[id] = TreeView1->Items->Add(NULL, item_name);
else
{
if (NodesMap[parent_index]) // проверка на существование значения
NodesMap[id] = TreeView1->Items->AddChild(NodesMap[parent_index], item_name);
}
NodesMap[id]->Data = (void*)(int)id; // присваиваем уникальный индекс каждому TTreeNode для последующей навигации по БД при помощи TTreeView
ADOQuery1->Next();
}
// обчно после составления большого дерева винда резервирует под приложение много памяти, сбрасываем все резервы:
SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
ADOQuery1->Active = false;
}
using namespace std;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
map <int, TTreeNode*>NodesMap;
ADOQuery1->Active = true;
TIntegerField *IDKey = (TIntegerField*)ADOQuery1->FieldByName("nodeid");
TIntegerField *ParentIndex = (TIntegerField*)ADOQuery1->FieldByName("parentid");
TStringField *ItemName = (TStringField*)ADOQuery1->FieldByName("nodename");
int id, level, parent_index;
String item_name;
while (!ADOQuery1->Eof)
{
id = IDKey->AsInteger;
parent_index = ParentIndex->AsInteger;
item_name = ItemName->AsString;
if (parent_index == 0) // если это root уровень
NodesMap[id] = TreeView1->Items->Add(NULL, item_name);
else
{
if (NodesMap[parent_index]) // проверка на существование значения
NodesMap[id] = TreeView1->Items->AddChild(NodesMap[parent_index], item_name);
}
NodesMap[id]->Data = (void*)(int)id; // присваиваем уникальный индекс каждому TTreeNode для последующей навигации по БД при помощи TTreeView
ADOQuery1->Next();
}
// обчно после составления большого дерева винда резервирует под приложение много памяти, сбрасываем все резервы:
SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
ADOQuery1->Active = false;
}