powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / ADO.NET, LINQ, Entity Framework, NHibernate, DAL, ORM [игнор отключен] [закрыт для гостей] / Работа с БД со множеством связей (Access 2000 + ADO.NET)
8 сообщений из 8, страница 1 из 1
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33253954
_ArtemK_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Расскажу все сначала. Имеется БД в Access
(
24 таблицы,
все теблицы имеют ключ - поле с именем ID (Autoincrement),
все таблицы связаны друг с другом (каскадное удаление/обновление)
)

В приложении (WinForms) я хочу загузить всю БД из файла в DataSet поработать с DataSet'ом и сохранить изменения обратно в файл (структура DataSet'а полностью такая же как и у оригинальной БД - со всеми связями и ключами).

БД из файла в ДатаСет гружу следующим образом:
открываю соединение,
каждая таблица загружается отдельно своим ДатаАдаптером,
для каждого ДатаАдаптера создается КоммандБилдер,
подписываюсь на RowUpdated Адаптера, чтобы корректировать поле ID, когда таблица будет обновляться обратно в БД
ДатаАдаптер сохраняется в хеш-массиве (чтобы использовать его же затем для вызова метода Update() для данной теблицы)
закрываю соединение.

ДатаСет в БД пытаюсь сохранить следующим образом:
открываю соединение,
Для всех таблиц:
{
получаю изменения, внесенные в Датасет
отыскиваю в хеш-массиве по имени таблицы нужный ДатаАдаптер,
вызываю Update() найденного Адаптера,
}
закрываю соединение.

Проблема: например, при добавлении трех записей в еще пустую таблицу ДатаСета он генерирует для записей следующие ID:
0
1
2

Пытаюсь передать изменения в Аксесс. Передается первая строка, но Аксесс сам для нее сгенерирует ID, равное 1 (а не 0, как до этого сгенерировал ДатаСет). Пытаюсь вернуть ID, сгенерированное Аксессом в Датасет (в обработчике события RowUpdated() запрашивается @@IDENTITY):

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
private static void OnRowUpdated(object sender, OleDbRowUpdatedEventArgs args)
		{
			// Include a variable and a command to retrieve the identity value from the Access database.
			int newID =  0 ;
			OleDbCommand idCMD = new OleDbCommand("SELECT @@IDENTITY", odcConn);

			if (args.StatementType == StatementType.Insert)
			{
				// Retrieve the identity value and store it in the ID column.
				newID = (int) idCMD.ExecuteScalar();
				args.Row["ID"] = newID;
			}
		}
но когда программа доходит до строки
args.Row["ID"] = newID;
генерируется исключение, т.к. в ДатаСете уже существует поле ID со значением 1

Собственно вопрос : как с этим бороться?????

Сам подход для работы с БД верно выбран??? (то, что я загружаю в ДатаСет ВСЮ БД??? то, что БД загружается отдельно по табличкам???)

Класс для работы с Датасетом приведен ниже (здесь грузятся не все таблицы из БД, а только четыре).
Для доступа к ДатаСету нужно из программы обратится к полю CDBMan.Instance.dstbData - если ДатаСет уже заполнен, то получаем ссылку на него, если еще не заполнен, то создается новый и заполняется.
Для записи изменений обратно в БД - метод CDBMan.vUpdate()
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
using System;
using System.Data;
using System.Data.OleDb;
using System.Collections;
using System.Windows.Forms;


namespace TestBed2006
{
	/// <summary>
	/// Summary description for CDBMan.
	/// </summary>
	public class CDBMan
	{
		private static object lockFlag = new object();
		private static CDBMan instance;
		private static string sconnectionstring = 
"Jet OLEDB:Global Partial Bulk Ops=2;" +
"Jet OLEDB:Registry Path=;" +
"Jet OLEDB:Database Locking Mode=1;" +
"Data Source=\"D:\\ArtemK\\Projects\\TestBed\\Data\\TestBed2006.mdb\";" + "Mode=Share Deny None;" +
"Jet OLEDB:Engine Type=5;" +
"Provider=\"Microsoft.Jet.OLEDB. 4 . 0 \";" +
"Jet OLEDB:System database=;" +
"Jet OLEDB:SFP=False;" +
"persist security info=False;" +
"Extended Properties=;" +
"Jet OLEDB:Compact Without Replica Repair=False;" +
"Jet OLEDB:Encrypt Database=False;" +
"Jet OLEDB:Create System Database=False;" +
"Jet OLEDB:Don't Copy Locale on Compact=False;" +
"User ID=Admin;Jet OLEDB:Global Bulk Transactions=1";

		private static OleDbConnection odcConn = new OleDbConnection(sConString);
		private static Hashtable htAdapters = new Hashtable(); 

		public dsTestBed2006 dstbData = new dsTestBed2006();

		public static string sConString
		{
			get
			{
				return sconnectionstring;
			}
			set
			{
				sconnectionstring = value;
				vReload();
			}
		}
		
		public static void vReload()
		{
			instance = null;
		}

		private static void vFillTable(string sTblName)
		{
			OleDbDataAdapter oddaAdapt = new OleDbDataAdapter();		

			oddaAdapt.SelectCommand = new OleDbCommand("select * from " + sTblName, odcConn);
			OleDbCommandBuilder odcbBuilder = new OleDbCommandBuilder(oddaAdapt);
			oddaAdapt.Fill(instance.dstbData.Tables[sTblName]);

			oddaAdapt.RowUpdated += new OleDbRowUpdatedEventHandler(OnRowUpdated);

			htAdapters.Add(sTblName, oddaAdapt);
		}

		private static void vUpdateTable(string sTblName)
		{
			DataSet dsTmp = instance.dstbData.GetChanges();

			if (dsTmp != null)
			{
				OleDbDataAdapter oddaAdapt = (OleDbDataAdapter) htAdapters[sTblName];
				oddaAdapt.Update(dsTmp.Tables[sTblName]);
			}
		}

		private static void OnRowUpdated(object sender, OleDbRowUpdatedEventArgs args)
		{
			// Include a variable and a command to retrieve the identity value from the Access database.
			int newID =  0 ;
			OleDbCommand idCMD = new OleDbCommand("SELECT @@IDENTITY", odcConn);

			if (args.StatementType == StatementType.Insert)
			{
				// Retrieve the identity value and store it in the ID column.
				newID = (int) idCMD.ExecuteScalar();
				args.Row["ID"] = newID;
			}
		}


		public static void vUpdate()
		{
			lock(lockFlag) 
			{
				if(instance != null)
				{
					odcConn.Open();

					vUpdateTable("TesterCategory");
					vUpdateTable("TesterExemplar");
					vUpdateTable("SysPower");
					vUpdateTable("SysAnlComm");

					odcConn.Close();
				}
			}
		}

		public static CDBMan Instance
		{
			get
			{
				lock(lockFlag) 
				{
					if(instance == null)
					{
						instance = new CDBMan();
						odcConn = new OleDbConnection(sConString);

						odcConn.Open();

						vFillTable("TesterCategory");
						vFillTable("TesterExemplar");
						vFillTable("SysPower");
						vFillTable("SysAnlComm");

						odcConn.Close();
					}
				}
				return instance;
			}
		}
	}
}

...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33254066
Артем1
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Весь пост не осилил. :)
Возможно вам поможет такое решение:
В таблицах датасета настраиваете автоинкремент с 0-я и с пририщением в -1. На связях между таблицами датасета настраиваете каскадные обновления.
После сохранения родительской таблицы в БД оттуда считывается сохраненная строка целиком (включая новый, сгенерированный акцессом, первичный ключ). По каскаду новый валидный первичный ключ обновляет подчиненные записи в других таблицах. Потом можно и их сохранять в бд. Вот как то так.

зы: прием не мой, вычитал в книжке сеппы. наверняка можно найти в инете более подробное описание.

ззы: если не правильно понял вопрос, то сорри :)
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33254128
Alex_2003
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А почему используете CommandBuilder ? Не хочется самому писать адаптеры?
Лучше бы написать свой адаптер для вставки и обновления. Если у вас поле ID в Access типа(Identity), то не нужно будет передавать значение этого поля в БД из DataSet при добавлении новой записи. Тогда проблемы не будет как таковой.
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33254179
_ArtemK_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Артем1
Возможно вам поможет такое решение:
В таблицах датасета настраиваете автоинкремент с 0-я и с пририщением в -1. На связях между таблицами датасета настраиваете каскадные обновления.
После сохранения родительской таблицы в БД оттуда считывается сохраненная строка целиком (включая новый, сгенерированный акцессом, первичный ключ). По каскаду новый валидный первичный ключ обновляет подчиненные записи в других таблицах. Потом можно и их сохранять в бд. Вот как то так.


Действительно так дожно работать. Красиво придумано. Спасибо за ответ.
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33254208
_ArtemK_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Alex_2003А почему используете CommandBuilder ? Не хочется самому писать адаптеры?
Лучше бы написать свой адаптер для вставки и обновления. Если у вас поле ID в Access типа(Identity), то не нужно будет передавать значение этого поля в БД из DataSet при добавлении новой записи. Тогда проблемы не будет как таковой.

К сожалению в моем случае так проблему решить не получиться. Если я оставляю назначение ID на усмотрение Аксесса и не буду возвращать его обратно в ДатаСет, то при обновлении вначале родительской таблицы, а затем дочерней, у дочерней foreign key'и все поедут (ведь primary key'и в родительской таблице Аксесс поизменял, а в дочерних остались старые значения). Если бы связей не было, то таких проблем и не возникло бы. Все же спасибо за ответ.
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33254502
Alex_2003
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да,с Access я давно не работал. У меня БД SQL Server, там решение проще получается, т.к. есть замечательная функция SCOPE_IDENTITY(), которая возвращает новый ID в ХП, функции или пакете.
Но в Access тоже можно сделать след. образом:
Для адаптера ( InsertCommand ) пишете что-то вроде:
"INSRET INTO tablename(...) values(...);
SELECT * FROM tablename WHERE ID = (SELECT max(ID) FROM tablename)"
Тогда после добавления ACCESS Вам вернет новый ID. И в Датасет Вы получите то что нужно.
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33258144
Mik Prokoshin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А Вы Relation'ы настройте и будет Вам счастье с автоматическим обновлением foreign ключей. ADO.NET в отличие от старого АДО с идентити полями отлично умеет работать !
...
Рейтинг: 0 / 0
Работа с БД со множеством связей (Access 2000 + ADO.NET)
    #33258275
_ArtemK_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Проблему решил следующим образом: настроил в DataSet'е для полей ID отрицательное автоприращение. Dataset генерирует для новых строк отрицательные ID. Теперь при передаче добавленных строк обратно в Access, он сам генерирует ID для новых строк (положительное значение). Спрашиваю у Access'а новый ID и отдаю его обратно Dataset'у, тот уже обновляет у себя все внешние ключи в дочерних таблицах (по Relations).
...
Рейтинг: 0 / 0
8 сообщений из 8, страница 1 из 1
Форумы / ADO.NET, LINQ, Entity Framework, NHibernate, DAL, ORM [игнор отключен] [закрыт для гостей] / Работа с БД со множеством связей (Access 2000 + ADO.NET)
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]