DataTable table = new DataTable("sample");
DataColumn column1 = table.Columns.Add("col1");
DataColumn column2 = table.Columns.Add("col2");
DataRow row = table.NewRow();
row[column1] = "val1";
row[column2] = "val2";
table.Rows.Add(row);
table.WriteXml("sample.xml");
table.WriteXmlSchema("sample.xsd");
これらは次のように出力されます。
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<sample>
<col1>val1</col1>
<col2>val2</col2>
</sample>
</DocumentElement>
<?xml version="1.0" standalone="yes"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="sample" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="sample">
<xs:complexType>
<xs:sequence>
<xs:element name="col1" type="xs:string" minOccurs="0" />
<xs:element name="col2" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
このときスキーマ (schema / 概要) のid属性の"NewDataSet"は、引数なしのコンストラクタでDataSetを作成したときの既定の名前であり、これは名前を設定したDataSetにDataTableを含めることで変更できます。DataSet() - DataSet Constructor (System.Data) | Microsoft Learn
DataSet dataSet = new DataSet("MyDataSet");
dataSet.Tables.Add(table);
ただし名前に記号を含めるとASCIIコードに置き換えられ、1文字目については数字も置き換えられます。
| DataSetの名前 | id属性の値 |
|---|---|
| "" | "NewDataSet" |
| "abc" | "abc" |
| "#$%" | "_x0023__x0024__x0025_" |
| "123" | "_x0031_23" |
| "あいう" | "あいう" |
| 型 | プロパティ | 内容 |
|---|---|---|
| string | TableName | テーブルの名前。これは親のDataSetからテーブルを特定するときや、WriteXml()でシリアル化するときに用いられる |
| DataColumnCollection | Columns | このテーブルに属する列 (DataColumn) のコレクション |
| DataRowCollection | Rows | このテーブルに属する行 (DataRow) のコレクション |
| int | MinimumCapacity | 初期状態での行数。データの取得前にこれを設定することで、パフォーマンスを最適化できる。既定値は50 |
| DataColumn[] | PrimaryKey | テーブルの主キーの列の配列。既定は空の配列 |
| DataView | DefaultView | テーブルのカスタマイズされたビュー |
| DataSet | DataSet | このテーブルが属するDataSet |
| DataRelationCollection | ChildRelations | 子Relationのコレクション。Relationが存在しない場合は、空のコレクション |
| DataRelationCollection | ParentRelations | 親Relationのコレクション。Relationが存在しない場合は、空のコレクション |
| ConstraintCollection | Constraints | このテーブルの制約のコレクション
個々の制約は、ChildRelationsなどを介してChildKeyConstraintへアクセスすることでも得られる |
| bool | HasErrors | |
| bool | CaseSensitive | trueならば、DataTableでの文字列比較で大文字/小文字を区別する |
| CultureInfo | Locale | CaseSensitiveと同様に、並べ替えなどに影響する。既定では、DataSetに属していないならばシステムのCultureInfoで、それに属すとそのLocaleと同じとなる |
テーブルの主キー (primary key) の列の配列を表します。
PrimaryKeyにDataColumn[]を設定すると、そのDataColumnのUniqueプロパティはtrueとなり、AllowDBNullはfalseとなります。
主キーはConstraintsプロパティを介して、ConstraintCollection.Add()からも設定できます。
内部ではIndex.InitRecords()で列挙子が用いられているため、このプロパティから取得するときに別スレッドから行を操作すると、「コレクションが修正されました。列挙操作が実行されない可能性があります。(Collection was modified; enumeration operation might not execute.)」としてInvalidOperationExceptionが投げられます。DefaultView - DataTable.cs
ドキュメントでは、DataSetのいずれかのテーブルのいずれかの行にエラーがある (there are errors in any of the rows in any of the tables of the DataSet) ことを判定するとなっています。Definition - DataTable.HasErrors Property (System.Data) | Microsoft Learn
しかし実際はテーブル内の行しか対象となりません。HasErrors - DataTable.cs
DataSet dataSet = new DataSet(); DataTable table1 = dataSet.Tables.Add(); DataTable table2 = dataSet.Tables.Add(); DataRow row1 = table1.Rows.Add(); DataRow row2 = table1.Rows.Add(); bool a1 = table1.HasErrors; // false bool a2 = table2.HasErrors; // false row1.RowError = "error"; bool b1 = table1.HasErrors; // true bool b2 = table2.HasErrors; // false
trueならば、DataTableでの文字列比較で大文字/小文字が区別されます。これは並べ替え (sorting)、検索 (searching) やフィルタ (filtering) に影響します。Remarks - DataTable.CaseSensitive Property (System.Data) | Microsoft Learn
親DataSetのCaseSensitiveを設定するとそれと同じ値になりますが、DataTableを作成する前に設定されていた値は反映されません。
| メソッド | 機能 |
|---|---|
| NewRow() | テーブルと同一スキーマのDataRowを作成できる |
| AcceptChanges() | 最後にAcceptChanges()を呼んだ後の変更をコミットできる |
| LoadDataRow(Object[], LoadOption) | 主キーが一致する行を更新する。一致する行がなければ、与えられた値で新しい行を作成する |
| BeginInit() | Formまたは別のコンポーネントで使用されているDataTableを初期化できる。Visual Studioのデザイン環境は、初期化を開始するのにこのメソッドを使用している |
| BeginLoadData() | 通知、インデックスの保守、制約を停止する。これはLoadDataRow()での更新時に利用するとあるが、ReadXml()でも有用 |
| EndLoadData() | データ読み込み後の、通知、インデックスの保守、制約を開始する |
| Select(String) | フィルタに適合するDataRowの配列を取得できる。つまり条件を指定して検索できる |
| WriteXmlSchema(String) | テーブルの現在のデータ構造を、指定ファイルにXMLスキーマとして書き込める |
| WriteXml(String) | テーブルの現在の内容を、指定ファイルにXMLとして書き込める |
| ReadXmlSchema(String) | XMLスキーマを、指定ファイルからテーブルへ読み込める |
| ReadXml(String) | XMLデータとスキーマを、指定ファイルからテーブルへ読み込める |
| Copy() | DataTableの構造とデータの両方をコピーする |
| Clone() | DataTableの構造をコピーする |
| Clear() | すべてのデータを消去できる。対象となるのは行だけであり、列は消去されない |
| GetErrors() | エラーを含むDataRowの配列を取得できる |
| AsDataView(DataTable) | LINQで使用できるDataViewを得られる |
| AsEnumerable(DataTable) | LINQで使用できるIEnumerable<DataRow>を実装したEnumerableRowCollection<DataRow>を得られる AsEnumerable - DataTableExtensions.cs |
フィルタの基準に一致する、すべてのDataRowの配列を取得できます。
public System.Data.DataRow[] Select (string filterExpression);Select(String) - DataTable.Select Method (System.Data) | Microsoft Learn
filterExpressionの式は、RowFilterに従います。
DataTable table = new DataTable();
table.Columns.Add("col1");
table.Rows.Add("1");
table.Rows.Add("2");
table.Rows.Add("3");
DataRow[] rows1 = table.Select("col1 > 1"); // rows1.Length は2
DataRow[] rows2 = table.Select("col1 < 1"); // rows2.Length は0
DataViewRowStateでもフィルタできます。
public System.Data.DataRow[] Select (
string filterExpression,
string sort,
System.Data.DataViewRowState recordStates
);
これらの引数を省いた場合は、Select("", "", DataViewRowState.CurrentRows)と指定するのと同じです。Select() - DataTable.cs
DataTable.CaseSensitiveがfalseならば、式で大文字/小文字が区別されません。これは既定でfalseです。一致するDataRowがなければ、空の配列が返されます。
テーブルのデータ構造を、XMLスキーマとして保存できます。
DataTable.WriteXmlSchema()は個々のテーブルの情報しか含まないため、Relationも含めた全体の構造を保存するにはDataSet.WriteXmlSchema()を用います。
テーブルの内容を、XMLとして保存できます。そこにシリアル化に対応しないクラスのデータが含まれていると、「'***' は IXmlSerializable インターフェイスを実装しないため、シリアル化を実行できません。」としてInvalidOperationExceptionが投げられます。
public void WriteXml (string fileName, System.Data.XmlWriteMode mode, bool writeHierarchy);WriteXml(String, XmlWriteMode, Boolean) - DataTable.WriteXml Method (System.Data) | Microsoft Learn
個々の列が出力される形式は、DataColumn.ColumnMappingで指定します。保存が不要な列は、そこでMappingType.Hiddenとします。
fileNameがすでに存在していると作成日時が維持されたまま更新日時が更新され、既存のファイルが書き替えられます。
XMLにデータ構造も含めたいならば、modeにXmlWriteMode.WriteSchemaを指定します。ただし同時に初期値やエラーも記録したいならば、WriteXmlSchema()で別ファイルに記録します。これを省略するとXmlWriteMode.IgnoreSchemaが指定されます。
table.WriteXmlSchema("sample.xsd");
table.WriteXml("sample.xml", XmlWriteMode.DiffGram);
初期値やエラーを記録するには、modeにXmlWriteMode.DiffGramを指定します。DiffGrams - ADO.NET | Microsoft Learn
たとえば次のようなデータをファイルに書き込むと、
DataTable table = new DataTable("sample");
table.Columns.Add("col1");
table.Columns.Add("col2");
DataRow row1 = table.Rows.Add("1a", "1b");
DataRow row2 = table.Rows.Add("2a", "2b");
DataRow row3 = table.Rows.Add("3a", "3b");
DataRow row4 = table.Rows.Add("4a", "4b");
row1.RowError = "E";
row2.SetColumnError(0, "e1");
row2.SetColumnError(1, "e2");
row2.AcceptChanges();
row3.AcceptChanges();
row4.AcceptChanges();
row3[0] = "**";
row4.Delete();
table.WriteXml("sample.xml", XmlWriteMode.DiffGram);
次のように出力されます。
<?xml version="1.0" standalone="yes"?>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<DocumentElement>
<sample diffgr:id="sample1" msdata:rowOrder="0" diffgr:hasChanges="inserted" diffgr:hasErrors="true">
<col1>1a</col1>
<col2>1b</col2>
</sample>
<sample diffgr:id="sample2" msdata:rowOrder="1" diffgr:hasErrors="true">
<col1>2a</col1>
<col2>2b</col2>
</sample>
<sample diffgr:id="sample3" msdata:rowOrder="2" diffgr:hasChanges="modified">
<col1>**</col1>
<col2>3b</col2>
</sample>
</DocumentElement>
<diffgr:before>
<sample diffgr:id="sample3" msdata:rowOrder="2">
<col1>3a</col1>
<col2>3b</col2>
</sample>
<sample diffgr:id="sample4" msdata:rowOrder="3">
<col1>4a</col1>
<col2>4b</col2>
</sample>
</diffgr:before>
<diffgr:errors>
<sample diffgr:id="sample1" diffgr:Error="E" />
<sample diffgr:id="sample2">
<col1 diffgr:Error="e1" />
<col2 diffgr:Error="e2" />
</sample>
</diffgr:errors>
</diffgr:diffgram>
このデータを読み込むとき<diffgr:errors>要素にあるdiffgr:idに一致する行が<DocumentElement>要素に存在しないと、XMLDiffLoader.ProcessErrors()で「オブジェクト参照がオブジェクト インスタンスに設定されていません。」としてNullReferenceExceptionが投げられます。
なおXmlWriteMode.IgnoreSchemaとしてスキーマを書き込まないと、次のように出力されます。
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<sample>
<col1>1a</col1>
<col2>1b</col2>
</sample>
<sample>
<col1>2a</col1>
<col2>2b</col2>
</sample>
<sample>
<col1>**</col1>
<col2>3b</col2>
</sample>
</DocumentElement>
BinaryFormatterを用いることで、テーブルの内容をバイナリで保存できます。C# DataTable Binary Serialization - Stack Overflow
こうすることでファイルサイズは小さくできますが、処理時間はさほど変わりません。
table.WriteXmlSchema("sample.xsd");
object[][] items = new object[table.Rows.Count][];
for (int i = 0; i < table.Rows.Count; i++)
{
items[i] = table.Rows[i].ItemArray;
}
using (FileStream fileStream = new FileStream("sample.bin", FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fileStream, items);
}
DataTable table = new DataTable();
table.ReadXmlSchema("sample.xsd");
object[][] items;
using (FileStream fileStream = new FileStream("sample.bin", FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
items = (object[][])formatter.Deserialize(fileStream);
}
foreach (object[] item in items)
{
table.Rows.Add(item);
}
RowStateがDetachedまたはDeletedの行は書き込まれません。
DataTable table = new DataTable("sample");
table.Columns.Add();
DataRow row1 = table.Rows.Add("1");
DataRow row2 = table.Rows.Add("2");
DataRow row3 = table.Rows.Add("3");
DataRow row4 = table.Rows.Add("4");
DataRow row5 = table.Rows.Add("5");
row1.Delete();
row2.AcceptChanges();
row4.AcceptChanges();
row4.Delete();
row5.AcceptChanges();
row5[0] = "5a";
DataRowState state1 = row1.RowState; // Detached
DataRowState state2 = row2.RowState; // Unchanged
DataRowState state3 = row3.RowState; // Added
DataRowState state4 = row4.RowState; // Deleted
DataRowState state5 = row5.RowState; // Modified
table.WriteXml("sample.xml");
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<sample>
<Column1>2</Column1>
</sample>
<sample>
<Column1>3</Column1>
</sample>
<sample>
<Column1>5a</Column1>
</sample>
</DocumentElement>
XMLスキーマをテーブルへ読み込めます。
public void ReadXmlSchema (string fileName);ReadXmlSchema(String) - DataTable.ReadXmlSchema Method (System.Data) | Microsoft Learn
読み込み前にColumnsが設定されていると、その情報は読み込まれません。
DataTable tableA = new DataTable("A");
tableA.Columns.Add("col1");
tableA.Columns.Add("col2");
tableA.WriteXmlSchema("sample.xsd");
DataTable tableB = new DataTable();
tableB.ReadXmlSchema("sample.xsd");
// tableB.Columns: {col1}, {col2} ... Columnsが読み込まれている
DataTable tableC = new DataTable();
tableC.Columns.Add("col3");
tableC.ReadXmlSchema("sample.xsd");
// tableC.Columns: {col3} ... Columnsが読み込まれていない
XMLスキーマとデータをテーブルへ読み込めます。
public System.Data.XmlReadMode ReadXml (string fileName);ReadXml(String) - DataTable.ReadXml Method (System.Data) | Microsoft Learn
読み込み元をstringで指定すると、内部ではXmlTextReaderで読み込まれます。ReadXml - DataTable.cs
DiffGram形式で保存されていないとき、読み込まれた個々のDataRowのRowStateは、Addedになります。
読み込み時に投げられる例外には、それぞれ次のように対処します。
現在のテーブルへ、指定のテーブルをマージできます。
public void Merge (System.Data.DataTable table);Merge(DataTable) - DataTable.Merge Method (System.Data) | Microsoft Learn
DataTable tableA = new DataTable();
tableA.Columns.Add("col1");
tableA.Columns.Add("col2");
DataTable tableB = new DataTable();
tableB.Columns.Add("col2");
tableB.Columns.Add("col3");
tableA.Rows.Add("1", "2");
tableB.Rows.Add("2", "3");
tableA.Merge(tableB);
マージ後、tableAは次のようになります。
| col1 | col2 | col3 | |
|---|---|---|---|
| tableA.Rows[0] | "1" | "2" | DBNull |
| tableA.Rows[1] | DBNull | "2" | "3" |
このとき、
tableA.PrimaryKey = new[] { tableA.Columns["col2"] };
のように主キーを設定しておくと、
| col1 | col2 | col3 | |
|---|---|---|---|
| tableA.Rows[0] | "1" | "2" | "3" |
のようにマージされます。
DataTableのデータを取得するための、DataTableReaderを取得できます。
public System.Data.DataTableReader CreateDataReader ();DataTable.CreateDataReader Method (System.Data) | Microsoft Learn
DataTableReader reader = dataTable.CreateDataReader();
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
Console.Write(reader[i]);
}
}
public System.Data.DataTable GetChanges ();GetChanges() - DataTable.GetChanges Method (System.Data) | Microsoft Learn
変更されたデータのコピーが返されます。変更がないならば、nullが返されます。このとき返されるのは新しいインスタンスであり、参照のコピーではありません。
public System.Data.DataTable GetChanges (System.Data.DataRowState rowStates);GetChanges(DataRowState) - DataTable.GetChanges Method (System.Data) | Microsoft Learn
引数にDataRowStateをとる形式では、それによってフィルタされた変更されたデータが返されるとドキュメントにあります。しかし実際には変更の有無は考慮されておらず、指定のrowStatesの状態のデータのコピーが返されます。GetChanges - DataTable.cs
DataTable table = new DataTable(); table.Columns.Add(); table.Rows.Add(1); table.Rows.Add(2); table.Rows.Add(3); DataTable table1a = table.GetChanges(); DataTable table1b = table.GetChanges(DataRowState.Unchanged); int count1a = table1a.Rows.Count; // 3 int count1b = table1b.Rows.Count; // NullReferenceException table.AcceptChanges(); DataTable table2a = table.GetChanges(); DataTable table2b = table.GetChanges(DataRowState.Unchanged); int count2a = table2a.Rows.Count; // NullReferenceException int count2b = table2b.Rows.Count; // 3 table.Rows[0][0] = 10; // 1つの行を変更する DataTable table3a = table.GetChanges(); DataTable table3b = table.GetChanges(DataRowState.Unchanged); int count3a = table3a.Rows.Count; // 1 … 変更された1つの行が返される int count3b = table3b.Rows.Count; // 2 … Unchangedの行、2つが返される
構造をコピーできます。ただしスキーマと制約はコピーされますがDataRelationはコピーされず、ForeignKeyConstraintの制約は失われます。
DataSet dataSet = new DataSet();
DataTable tableA = dataSet.Tables.Add();
DataTable tableB = dataSet.Tables.Add();
DataRelation relation = dataSet.Relations.Add(
tableA.Columns.Add(),
tableB.Columns.Add());
ConstraintCollection cA = tableA.Constraints; // UniqueConstraint
ConstraintCollection cB = tableB.Constraints; // ForeignKeyConstraint
DataRelationCollection rB = tableB.ParentRelations; // DataRelation
DataTable tableAc = tableA.Clone();
DataTable tableBc = tableB.Clone();
ConstraintCollection cAc = tableAc.Constraints; // UniqueConstraint
ConstraintCollection cBc = tableBc.Constraints; // Empty
DataRelationCollection rBc = tableBc.ParentRelations; // Empty
DataRelationもコピーしたいならば、DataSet.Clone()を用います。Clone() - DataSet.cs
DataSet dataSetc = dataSet.Clone();
DataRelationCollection c = dataSetc.Tables[tableB.TableName].ParentRelations; // DataRelation
構造とデータの両方をコピーできます。
public System.Data.DataTable Copy ();DataTable.Copy Method (System.Data) | Microsoft Learn
返されるDataTableは、同じ構造 (スキーマと制約) とデータです。ただし構造はClone()によってコピーされるため内容もそれと同一で、ForeignKeyConstraintの制約はコピーされません。Copy - DataTable.cs
public void Clear ();DataTable.Clear Method (System.Data) | Microsoft Learn
このテーブルを消去することで子テーブルの行が孤立する場合には、InvalidConstraintExceptionが投げられ失敗します。このような場合には子テーブルのデータを先に消去するか、これらのテーブルが属するDataSetのClear()で消去します。
public System.Data.DataRow[] GetErrors ();DataTable.GetErrors Method (System.Data) | Microsoft Learn
このメソッドはHasErrorsがtrueであるRowsの要素を確認しているだけのため、戻り値にはRowStateがDeletedの行なども含まれます。GetErrors - DataTable.cs
| 区分 | イベント | 発生タイミング |
|---|---|---|
| Table | Initialized | Occurs after the DataTable is initialized. |
| TableNewRow | 新しいDataRowが挿入されたとき | |
| TableClearing | Occurs when a DataTable is cleared. | |
| TableCleared | Occurs after a DataTable is cleared. | |
| Column | ColumnChanging | DataRowの指定のDataColumnの値が変更されるとき |
| ColumnChanged | DataRowの指定のDataColumnの値が変更されたとき。ただしDataColumnを指定してイベントを登録することはできないため、ハンドラでイベントを発生させたDataColumnを判定する | |
| Row | RowChanging | DataRow内の値またはRowStateが、変更されるとき (同じ値を設定しても発生するため、変更されていない場合もある) |
| RowChanged | DataRow内の値またはRowStateが、正しく変更されたとき (同じ値を設定しても発生するため、変更されていない場合もある) | |
| RowDeleting | テーブル内の行が削除され、RowStateがDeletedに変更される前 (Delete()やDataRowCollection.Remove()で、Detachedになるときにも発生する) | |
| RowDeleted | テーブル内の行が削除され、RowStateがDeletedに変更された後 (同上) | |
| Disposed | Adds an event handler to listen to the Disposed event on the component. (Inherited from MarshalByValueComponent) |
行のカスタムエラー (DataRow.RowError) や列のエラーが変更されてもRowStateは変更されず、どのイベントも発生しません。
実行された動作 (Action) は、DataRowChangeEventArgs.Actionで得られます。このDataRowAction列挙型は行に対して実行された動作を表すものであり、行の状態を表すDataRowState列挙型とは異なります。
| 列挙子 | 数値 | 内容 |
|---|---|---|
| Nothing | 0 | The row has not changed. |
| Delete | 1 | 行はテーブルから削除された (DataRowStateがDeletedまたはDetachedとなった後)
テーブルから除去されDetachedとなることも、この動作に区分されている |
| Change | 2 | The row has changed. |
| Rollback | 4 | The most recent change to the row has been rolled back. |
| Commit | 8 | 行への変更がコミットされた ※1 |
| Add | 16 | The row has been added to the table. |
| ChangeOriginal | 32 | The original version of the row has been changed. |
| ChangeCurrentAndOriginal | 64 | The original and the current versions of the row have been changed. |
※1 RowStateがAddedかDetachedではない行に対してDataRowCollection.Remove()を呼んだときも、内部でDataRow.AcceptChanges()が呼ばれるためこの状態になる。
次のようにハンドラを登録し、どのような状況でイベントが発生するかを検証します。
DataTable table = new DataTable();
table.Columns.Add();
table.RowChanging += (object sender, DataRowChangeEventArgs e) => { Console.WriteLine($"RowChanging {e.Action} {e.Row.RowState}"); };
table.RowChanged += (object sender, DataRowChangeEventArgs e) => { Console.WriteLine($"RowChanged {e.Action} {e.Row.RowState}"); };
table.RowDeleting += (object sender, DataRowChangeEventArgs e) => { Console.WriteLine($"RowDeleting {e.Action} {e.Row.RowState}"); };
table.RowDeleted += (object sender, DataRowChangeEventArgs e) => { Console.WriteLine($"RowDeleted {e.Action} {e.Row.RowState}"); };
DataRow row = table.Rows.Add(); // イベント名 動作 行の状態 // RowChanging Add Detached // RowChanged Add Added row[0] = 1; // RowChanging Change Added // RowChanged Change Added row.AcceptChanges(); // RowChanging Commit Added // RowChanged Commit Unchanged row.AcceptChanges(); // 何も変更せずコミット // RowChanging Commit Unchanged // RowChanged Commit Unchanged row[0] = 2; // RowChanging Change Unchanged // RowChanged Change Modified row[0] = 2; // 同じ値を設定 // RowChanging Change Modified // RowChanged Change Modified row.RejectChanges(); // RowChanging Rollback Modified // RowChanged Rollback Unchanged row.RejectChanges(); // 何も変更せずロールバック // イベントは発生しない
DataRow row1 = table.Rows.Add(); table.Rows.Remove(row1); // DataRowState.Added の状態で実行 // RowDeleting Delete Added // RowDeleted Delete Detached DataRow row2 = table.Rows.Add(); row2.AcceptChanges(); table.Rows.Remove(row2); // DataRowState.Unchanged の状態で実行 // RowDeleting Delete Unchanged // RowDeleted Delete Deleted // RowChanging Commit Deleted // RowChanged Commit Detached row2.AcceptChanges(); // RowNotInTableException
DataRow row1 = table.Rows.Add(); row1.Delete(); // DataRowState.Added の状態で実行 // RowDeleting Delete Added // RowDeleted Delete Detached DataRow row2 = table.Rows.Add(); row2.AcceptChanges(); row2.Delete(); // DataRowState.Unchanged の状態で実行 // RowDeleting Delete Unchanged // RowDeleted Delete Deleted row2.AcceptChanges(); // RowChanging Commit Deleted // RowChanged Commit Detached
DataRowVersionごとの値は、RowChangingやRowChangedの発生時点で異なります。たとえば次のように操作した場合を検証します。
DataTable table = new DataTable(); table.Columns.Add(); DataRow row = table.Rows.Add(); // Action: Add row[0] = 1; // Action: Change (1st) row.AcceptChanges(); // Action: Commit (1st) row[0] = 2; // Action: Change (2nd) row.AcceptChanges(); // Action: Commit (2nd) row.Delete(); // Action: Delete
table.RowChanging += (object sender, DataRowChangeEventArgs e) =>
{
object current, original, proposed;
switch (e.Action)
{
case DataRowAction.Add:
current = e.Row[0, DataRowVersion.Current]; // VersionNotFoundException
original = e.Row[0, DataRowVersion.Original]; // VersionNotFoundException
proposed = e.Row[0, DataRowVersion.Proposed]; // DBNull
break;
case DataRowAction.Change:
current = e.Row[0, DataRowVersion.Current]; // 1st:DBNul, 2nd:"1"
original = e.Row[0, DataRowVersion.Original]; // 1st:VersionNotFoundException, 2nd:"1"
proposed = e.Row[0, DataRowVersion.Proposed]; // 1st:"1", 2nd:"2"
break;
case DataRowAction.Commit:
current = e.Row[0, DataRowVersion.Current]; // 1st:"1", 2nd:"2"
original = e.Row[0, DataRowVersion.Original]; // 1st:VersionNotFoundException, 2nd:"1"
proposed = e.Row[0, DataRowVersion.Proposed]; // 1st, 2nd:VersionNotFoundException
break;
}
};
table.RowChanged += (object sender, DataRowChangeEventArgs e) =>
{
object current, original, proposed;
switch (e.Action)
{
case DataRowAction.Add:
current = e.Row[0, DataRowVersion.Current]; // DBNull
original = e.Row[0, DataRowVersion.Original]; // VersionNotFoundException
proposed = e.Row[0, DataRowVersion.Proposed]; // VersionNotFoundException
break;
case DataRowAction.Change:
current = e.Row[0, DataRowVersion.Current]; // 1st:"1", 2nd:"2"
original = e.Row[0, DataRowVersion.Original]; // 1st:VersionNotFoundException, 2nd:"1"
proposed = e.Row[0, DataRowVersion.Proposed]; // 1st, 2nd:VersionNotFoundException
break;
case DataRowAction.Commit:
current = e.Row[0, DataRowVersion.Current]; // 1st:"1", 2nd:"2"
original = e.Row[0, DataRowVersion.Original]; // 1st:"1", 2nd:"2"
proposed = e.Row[0, DataRowVersion.Proposed]; // 1st, 2nd:VersionNotFoundException
break;
}
};
table.RowDeleting += (object sender, DataRowChangeEventArgs e) =>
{
object current, original, proposed;
current = e.Row[0, DataRowVersion.Current]; // "2"
original = e.Row[0, DataRowVersion.Original]; // "2"
proposed = e.Row[0, DataRowVersion.Proposed]; // VersionNotFoundException
};
table.RowDeleted += (object sender, DataRowChangeEventArgs e) =>
{
object current, original, proposed;
current = e.Row[0, DataRowVersion.Current]; // VersionNotFoundException
original = e.Row[0, DataRowVersion.Original]; // "2"
proposed = e.Row[0, DataRowVersion.Proposed]; // VersionNotFoundException
};
TableNewRowは、DataTable.NewRow()で作成されたときのみ発生します。DataTable.Rows.Add()ではRowChangedが発生し、そのActionがAddであることで新しい行が追加されたことを検知できます。
DataTable table = new DataTable();
table.TableNewRow += (object sender, DataTableNewRowEventArgs e) => { };
table.RowChanged += (object sender, DataRowChangeEventArgs e) => { };
DataRow row1 = table.NewRow(); // TableNewRow
table.Rows.Add(row1); // RowChanged: Add
DataRow row2 = table.Rows.Add(); // RowChanged: Add
DataRowの値が変更されたときに、DataColumnごとに発生します。DataTable.ColumnChanged Event (System.Data) | Microsoft Learn
行の追加や削除では、このイベントは発生しません。Handling DataTable Events - ADO.NET | Microsoft Learn
列が追加や削除されたことは、DataColumnCollection.CollectionChangedで検知できます。
DataTable table = new DataTable();
table.ColumnChanged += (object sender, DataColumnChangeEventArgs e) =>
{
Debug.WriteLine(e.Column);
};
DataColumn col1 = table.Columns.Add(); // イベントは発生しない
DataColumn col2 = table.Columns.Add(); // イベントは発生しない
DataRow row = table.Rows.Add(1); // イベントは発生しない
row[col1] = "a"; // e.Column が Column1 でイベント発生
row[col2] = "b"; // e.Column が Column2 でイベント発生
row[col2] = "b"; // e.Column が Column2 でイベント発生 (値が変更されていなくても発生)
row.ItemArray = new object[] { "c", "d" }; // e.Column が Column1 と Column2 でイベント発生
row.Delete(); // イベントは発生しない
行内の値の変更が、成功したときに発生します。またRowStateが変更されたときにも発生しますが、Deletedに変更されたときは発生せず、その状態はRowDeletedイベントで処理できます。
同じ値を設定しても発生するため、実際には変更されていない場合もあります。
制約のAcceptRejectRuleがCascadeだとコミットが関連する行にも波及するため、そのテーブルの行の値やRowStateが変更されていなくても、このイベントが発生することがあります。そして親行の変更によってこのイベントが発生するときは、その呼び出し元 (source) は子行自体となります。
public event System.Data.DataRowChangeEventHandler RowChanged;DataTable.RowChanged Event (System.Data) | Microsoft Learn