元々SqlDataAdapterオブジェクトを何度も作成していた短いコードがあります。
呼び出しを少し合理化するために、SqlDataAdapterをSqlCommandに置き換え、SqlConnectionをループ外に移動しました。
これで、DataTableに返されたデータの行を編集しようとするたびに、以前にスローされなかったReadOnlyExceptionがスローされます。
注:IDに基づいて従業員の氏名を取得するカスタム関数があります。ここでは簡単にするために、以下のサンプルコードで「John Doe」を使用して、ポイントを示しています。
ExampleQueryOldはSqlDataAdapter;で動作します。 ExampleQueryNewは、DataRowの要素に書き込もうとするたびにReadOnlyExceptionで失敗します。
これは機能し、問題はありません。
public static DataTable ExampleQueryOld(string targetItem, string[] sqlQueryStrings) {
DataTable bigTable = new DataTable();
for (int i = 0; i < sqlQueryStrings.Length; i++) {
string sqlText = sqlQueryStrings[i];
DataTable data = new DataTable(targetItem);
using (SqlDataAdapter da = new SqlDataAdapter(sqlText, Global.Data.Connection)) {
try {
da.Fill(data);
} catch (Exception err) {
Global.LogError(_CODEFILE, err);
}
}
int rowCount = data.Rows.Count;
if (0 < rowCount) {
int index = data.Columns.IndexOf(GSTR.Employee);
for (int j = 0; j < rowCount; j++) {
DataRow row = data.Rows[j];
row[index] = "John Doe"; // This Version Works
}
bigTable.Merge(data);
}
}
return bigTable;
}
この例は、ReadOnlyExceptionをスローします。
public static DataTable ExampleQueryNew(string targetItem, string[] sqlQueryStrings) {
DataTable bigTable = new DataTable();
using (SqlConnection conn = Global.Data.Connection) {
for (int i = 0; i < sqlQueryStrings.Length; i++) {
string sqlText = sqlQueryStrings[i];
using (SqlCommand cmd = new SqlCommand(sqlText, conn)) {
DataTable data = new DataTable(targetItem);
try {
if (cmd.Connection.State == ConnectionState.Closed) {
cmd.Connection.Open();
}
using (SqlDataReader reader = cmd.ExecuteReader()) {
data.Load(reader);
}
} catch (Exception err) {
Global.LogError(_CODEFILE, err);
} finally {
if ((cmd.Connection.State & ConnectionState.Open) != 0) {
cmd.Connection.Close();
}
}
int rowCount = data.Rows.Count;
if (0 < rowCount) {
int index = data.Columns.IndexOf(GSTR.Employee);
for (int j = 0; j < rowCount; j++) {
DataRow row = data.Rows[j];
try {
// ReadOnlyException thrown below: "Column 'index' is read only."
row[index] = "John Doe";
} catch (ReadOnlyException roErr) {
Console.WriteLine(roErr.Message);
}
}
bigTable.Merge(data);
}
}
}
}
return bigTable;
}
なぜDataRow要素に書き込むことができますが、他の場合にはできないのですか?
SqlConnectionがまだ開いているか、またはSqlDataAdapterが舞台裏で何かをしているからでしょうか?
DataAdapter.Fill
を使用しても、データベーススキーマはロードされません。これには、列が主キーであるかどうか、列が読み取り専用であるかどうかが含まれます。データベーススキーマをロードするには、DataAdapter.FillSchema
を使用しますが、それはあなたの質問ではありません。
DataReader
を使用してテーブルを埋めると、スキーマがロードされます。そのため、index
列は読み取り専用であり(おそらく主キーであるため)、その情報はDataTable
にロードされます。これにより、テーブル内のデータを変更できなくなります。
@ k3bが正しかったと思う。 ReadOnly = false
を設定すると、データテーブルに書き込むことができるはずです。
foreach (System.Data.DataColumn col in tab.Columns) col.ReadOnly = false;
異なるアプローチを試している間、私は同じ例外を受け取り続けました。最終的に私のために働いたのは、列のReadOnlyプロパティをfalseに設定し、row [index] = "new value"の代わりにExpression列の値を変更することでした
これに遭遇する可能性は低いですが、私はいくつかのVB.NETコードに取り組んでいて、ReadOnlyException
を取得しました。
コードがDataRowアイテムをSub ByRefに渡していたため、この問題に遭遇しました。参照渡しの動作だけが例外をトリガーします。
_Sub Main()
Dim dt As New DataTable()
dt.Columns.Add(New DataColumn With {
.ReadOnly = True,
.ColumnName = "Name",
.DataType = GetType(Integer)
})
dt.Rows.Add(4)
Try
DoNothing(dt.Rows(0).Item("Name"))
Console.WriteLine("All good")
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Sub DoNothing(ByRef item As Object)
End Sub
_
_Column 'Name' is read only
_
C#でこのようなコードを書くこともできません。 DoNothing(ref dt.Rows[0].Item["Name"])
は、コンパイル時エラーを返します。