web-dev-qa-db-ja.com

ReadOnlyException DataTable DataRow "列Xは読み取り専用です。"

元々SqlDataAdapterオブジェクトを何度も作成していた短いコードがあります。

呼び出しを少し合理化するために、SqlDataAdapterSqlCommandに置き換え、SqlConnectionをループ外に移動しました。

これで、DataTableに返されたデータの行を編集しようとするたびに、以前にスローされなかったReadOnlyExceptionがスローされます。

注:IDに基づいて従業員の氏名を取得するカスタム関数があります。ここでは簡単にするために、以下のサンプルコードで「John Doe」を使用して、ポイントを示しています。

ExampleQueryOldSqlDataAdapter;で動作します。 ExampleQueryNewは、DataRowの要素に書き込もうとするたびにReadOnlyExceptionで失敗します。

  • ExampleQueryOld

これは機能し、問題はありません。

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;
}
  • ExampleQueryNew

この例は、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が舞台裏で何かをしているからでしょうか?

43
jp2code

DataAdapter.Fillを使用しても、データベーススキーマはロードされません。これには、列が主キーであるかどうか、列が読み取り専用であるかどうかが含まれます。データベーススキーマをロードするには、DataAdapter.FillSchemaを使用しますが、それはあなたの質問ではありません。

DataReaderを使用してテーブルを埋めると、スキーマがロードされます。そのため、index列は読み取り専用であり(おそらく主キーであるため)、その情報はDataTableにロードされます。これにより、テーブル内のデータを変更できなくなります。

@ k3bが正しかったと思う。 ReadOnly = falseを設定すると、データテーブルに書き込むことができるはずです。

foreach (System.Data.DataColumn col in tab.Columns) col.ReadOnly = false; 
86
Fun Mun Pieng

異なるアプローチを試している間、私は同じ例外を受け取り続けました。最終的に私のために働いたのは、列のReadOnlyプロパティをfalseに設定し、row [index] = "new value"の代わりにExpression列の値を変更することでした

1

VBでは、参照のみで読み取り専用のDataRowアイテムを渡さないでください。

これに遭遇する可能性は低いですが、私はいくつかの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シャープ

C#でこのようなコードを書くこともできません。 DoNothing(ref dt.Rows[0].Item["Name"])は、コンパイル時エラーを返します。

0
Walter Stabosz