MS_ASPNETMVCDataTable - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

ASP.NET MVCでDataTableを䜿甚する。

抂芁

ASP.NET MVC で Entity Framework をキャンセルした堎合の遞択肢ずしお
ASP.NET MVC + DataTable が必芁なため、本項にコレを纏めた。

補足この遞択の背景: 「Entity Framework を
キャンセルした堎合」ずいう前提が本ペヌゞの出発点である。
その刀断理由は Entity Framework の懞念 や
ADO.NET vs ORM で論じられおいる。

【ORM を採らない堎合のデヌタ衚珟の遞択肢】

 ① DataTable / DataSet     
 型なし。列名は文字列 ★ 本ペヌゞ
 ② POCO自前のクラス     
 型あり。マッピングを自分で曞く
 ③ dynamic / ExpandoObject  
 型なし。動的
 ④ Dapper 等の軜量マッパヌ   
 型あり。SQL は自分で曞く ★ 珟圚の䞻流

珟圚なら ④Dapper 等を第䞀に怜蚎する。

// DapperSQL は自分で曞き、結果は POCO で受ける
var products = conn.Query<Product>(
    "SELECT Id, Name, Price FROM Products WHERE CategoryId = @cid",
    new { cid = 3 }).ToList();

「EF は重いが、DataTable は型がなくお蟛い」ずいう
圓時のゞレンマは、軜量マッパヌが埋めた
——ずいうのが
その埌の流れである。

ただし、既存の ADO.NET 資産DataTable を返す共通基盀が
ある堎合は、本ペヌゞの内容が今も必芁
になる。

通垞の凊理

通垞通りViewに持っお行っお凊理可胜。

  • c# - Displaying standard DataTables in MVC - Stack Overflow
    https://stackoverflow.com/questions/2243898/displaying-standard-datatables-in-mvc

    • ASP.NET MVC で DataTable の䜵甚は問題ない。

      There is nothing about MVC that prevents you from using ADO.NET.
      
    • 以䞋は、

      • 「ASP.NET MVC で DataTable を䜿甚するのがベストプラクティスでは無い。」
        ず蚀っおいるのでは無くお、
      • 「Controller でデヌタを䜜成するのがベストプラクティスでは無い。」ず蚀っおいるので、

      お間違いなきよう。

      Now, I'm violating a whole lot of principles and "best-practices"
      of ASP.NET MVC here, so please understand this is just a simple demonstration.
      

補足原文の読解は正しい: 匕甚元の英文を
「DataTable が悪い」ではなく「Controller にデヌタ生成を曞くのが悪い」
ず読み取っおいる点は、正確である。

MVC の責務分担:

【Controller】 リク゚ストを受け、【サヌビス局を呌び】、View を遞ぶ
                → デヌタ取埗ロゞックそのものは曞かない
【Model】      デヌタずビゞネス ロゞック
                → ここで DataTable を返しおも、MVC の原則には反しない
【View】       衚瀺だけ

DataTable を䜿うこずの実際の欠点は、MVC の原則ずは別の話である。

欠点 内容
コンパむル時に怜蚌されない row["Nmae"] の打ち間違いが実行時たで分からない ★
リファクタリングが効かない 列名の倉曎を IDE が远跡できない
メモリを食う 倉曎远跡・スキヌマ情報を持぀ため、POCO の数倍
単䜓テストが曞きにくい テスト デヌタの構築が冗長
View が読みにくい @item["Name"] が䞊ぶ

䞀方、DataTable が有利な堎面もある。

・【列が動的に決たる】垳祚、CSV ゚クスポヌト、汎甚怜玢画面
・スキヌマが実行時たで分からない
・既存の共通基盀が DataTable を前提にしおいる
・Excel 出力ClosedXML 等は DataTable を盎接受けられる

ただし、双方向バむンディング等は䞍可胜

単䞀レコヌドの凊理を行う堎合等は POCO の ViewModel を䜜成する。

補足この方針は珟圚も正しい: 「衚瀺は DataTable、
入力は POCO の ViewModel
」ずいう䜿い分けは、
実務的な折衷案ずしお劥圓である。

【なぜ DataTable では双方向バむンディングできないのか】

   ModelBinder は【プロパティ名】でフォヌムの倀を察応付ける
     <input name="Name" value="
">  →  model.Name

   DataTable は列を【むンデクサ】で持぀
     row["Name"]  ← プロパティではない
     → ModelBinder が察応付けられない

   たた、DataRow の public プロパティは
     RowError / HasErrors / Table / ItemArray / RowState
     ← 【業務デヌタずは無関係なものばかり】★
     埌述の WebGrid の問題ず同じ根
// 入力画面甚の ViewModelPOCO
public class ProductEditViewModel
{
    public int Id { get; set; }

    [Required(ErrorMessage = "名前は必須です")]
    [StringLength(50)]
    public string Name { get; set; }

    [Range(0, 999999)]
    public decimal Price { get; set; }
}

POCO にするず、以䞋が同時に手に入る。

・双方向バむンディング[HttpPost] のメ゜ッド匕数で受け取れる
・デヌタ泚釈による【入力怜蚌】サヌバ・クラむアント䞡方
・IntelliSense ず コンパむル時チェック
・単䜓テストの容易さ

䞀芧は DataTable、明现は POCO ずいう混圚は、
移行途䞭の構成ずしお珟実的である。

Grid系の凊理

foreach

foreach で凊理可胜。

補足珟圚はこれが第䞀遞択: WebGrid が廃れた珟圚、
foreach で曞くのが最も確実である
ASP.NET MVCのWebGrid。

@model System.Data.DataTable

<table class="table">
  <thead>
    <tr>
      @foreach (System.Data.DataColumn col in Model.Columns)
      {
        <th>@col.ColumnName</th>
      }
    </tr>
  </thead>
  <tbody>
  @foreach (System.Data.DataRow row in Model.Rows)
  {
    <tr>
      @foreach (System.Data.DataColumn col in Model.Columns)
      {
        <td>@row[col]</td>
      }
    </tr>
  }
  </tbody>
</table>

列が動的な画面汎甚怜玢などでは、
むしろ DataTable の方が玠盎に曞ける
——ずいう利点が
ここで効いおくる。

Razor の @ は自動的に HTML ゚スケヌプされるため、
XSS の心配は芁らない゚ンコヌディング。
@Html.Raw() を䜿わない限り安党である。

WebGrid のコンストラクタの第䞀匕数の
source は、IEnumerable をサポヌトする必芁があるので、
DataTable 䜿甚時は、型の倉換が必芁がになるもよう。

IEnumerable<DataRow> ではダメなケヌスがある。

以䞋で IEnumerable<DataRow> に倉換可胜。

var result = dt.AsEnumerable();

しかし、この result は、

@{
  ・・・
  var grid = new WebGrid(erc);
  ・・・
}
・・・
@grid.GetHtml()
・・・

ずしお䜿甚できない。

理由は、

DataRow の public property が、

  • RowError property
  • HasError property

であるためのもよう。

詳しくは、䞋蚘を参照のこず。

この堎合、

  • 埌述のように、DataTable を、List<dynamic> に倉換するか、

  • grid.Column() メ゜ッドで以䞋のように、format を明瀺する、

    grid.Column("Name", format: (item) => item.Value["Name"].ToString()),

ず、むむ感じに凊理できるもよう。

補足原因の説明を正確にしおおく: 原文の「DataRow の
public property が RowError / HasError であるため
」ずいう蚺断は
正しい方向だが、少し補足するず分かりやすい。

【WebGrid が列を決める仕組み】
   columns を指定しない堎合、
   source の芁玠の型を【リフレクションで調べ】、
   その【public プロパティ】を列にする

【DataRow の public プロパティ】
   ・ItemArray
   ・HasErrors      ← 原文の「HasError」は正しくは HasErrors
   ・RowError
   ・RowState
   ・Table

   → 【業務デヌタの列は 1 ぀も出おこない】★
     row["Name"] はプロパティではなくむンデクサのため、
     リフレクションからは「列」ずしお芋えない

移行メモ: 原文の「HasError property」は、
正しくは HasErrors耇数圢である。

原文が挙げる 2 ぀の回避策は、どちらも有効である。

方法 性質
List<dynamic> に倉換 列が動的でも動く。ただし遅い埌述
format: を明瀺 倉換が䞍芁で速い。ただし列を先に知っおいる必芁がある

列が固定なら format: 明瀺の方が良い。
倉換のコストもメモリも増えないためである。

List<dynamic> に倉換する。

List<dynamic> に、(IDictionary<string, object>) ExpandoObject を远加しおいく。

var result = new List<dynamic>();
foreach (DataRow row in table.Rows)
{
  var obj = (IDictionary<string, object>)new ExpandoObject();
  foreach (DataColumn col in table.Columns)
  {
    obj.Add(col.ColumnName, row[col.ColumnName]);
  }
  result.Add(obj);
}

補足この方法の性胜䞊の泚意: 原文の参考リンクにも
「exceedingly slow」ずいう Stack Overflow の投皿が挙がっおいる通り、
この倉換は遅い。理由を明確にしおおく。

【遅い理由】
 ① ExpandoObject は内郚で蟞曞を持぀
      → 行数 × 列数 の分だけ蟞曞゚ントリを䜜る
 ② dynamic のアクセスは【実行時バむンディング】
      → 呌び出しごずに DLR がメンバを解決するキャッシュはあるが重い
 ③ WebGrid が列を決める際、【1 行目をリフレクションで調べる】
 ④ ゜ヌト・ペヌゞングも dynamic 経由で行われる

 → 1,000 行皋床でも䜓感できる遅さになる

性胜を優先するなら:

// ① 衚瀺する列だけを POCO に詰め替える最も速い
var rows = table.AsEnumerable()
    .Select(r => new ProductRow {
        Id = r.Field<int>("Id"),
        Name = r.Field<string>("Name") })
    .ToList();

// ② ペヌゞングを DB 偎で行うそもそも党件持っおこない
//    SELECT ... ORDER BY Id OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY

② が本質的な察策である。
1 䞇件を取っおきお画面で 20 件衚瀺するずいう蚭蚈自䜓が問題で、
DB 偎でペヌゞングするのが正しい
SQL Server。

row.Field<T>() を䜿う利点:

(int)row["Id"]           // ✗ DBNull で InvalidCastException
row.Field<int?>("Id")    // ○ DBNull を null ずしお扱える ★

ポストバック的な動䜜がある堎合

Sessionに栌玍しおおく。

削陀しないずメモリを食う。

Hiddenに栌玍しおおく。

Hidden にバむナリ・シリアラむズした DataTable の Base64 ゚ンコヌディングを保存。

郜床、DBから取埗する。

オヌバヌヘッドがある。

移行メモ原文の芋出しの誀字: 原文は「Hidennに栌玍しおおく」ず
なっおいたため、「Hidden」に修正した。

補足3 ぀の遞択肢の評䟡ず、珟圚の掚奚: 3 ぀ずも実圚する手法だが、
珟圚は評䟡が倧きく倉わっおいる。

方法 圓時の評䟡 珟圚の評䟡
Session に栌玍 メモリを食う スケヌルアりトで問題。分散キャッシュなら可
Hidden にシリアラむズ サむズが増える 䜿っおはならない ★埌述
郜床 DB から取埗 オヌバヌヘッド これが正解。DB 偎ペヌゞングず䜵甚

② Hidden ぞの栌玍は、珟圚は明確に非掚奚である。

【問題点】
 ① 【セキュリティ】 利甚者に改竄される
      → Base64 を戻せば䞭身が読める[゚ンコヌディング] 参照
      → 改竄されたデヌタをそのたた信じるず重倧な脆匱性
 ② 【BinaryFormatter が .NET 5 以降で無効化された】★
      → DataTable のバむナリ シリアラむズは既定で䜿えない
      → 既知の脆匱性任意コヌド実行のため
 ③ ペヌゞ サむズが肥倧する[ASP.NET ViewState] ず同じ問題

BinaryFormatter は .NET 9 で完党に削陀された。
DataTable.ReadXml / WriteXml は䜿えるが、
そもそも Hidden に業務デヌタを茉せないのが正しい。

① Session の珟圚の扱い:

・むンプロセス Session は【スケヌルアりトできない】
   → 耇数サヌバに分散するず、別サヌバに飛んだ時に倱われる
・分散キャッシュRedis / SQL Serverに眮けば共有できるが、
   倧きな DataTable を眮くず【シリアラむズのコストが支配的】になる
     → [ASP.NET CoreのSession利甚方法] / [分散キャッシュ] 参照

③ 郜床 DB から取埗が正解である理由:

・状態を持たないステヌトレス【氎平にスケヌルする】
・デヌタが垞に最新
・「オヌバヌヘッド」は、【DB 偎でペヌゞングすれば小さい】
    → 20 件だけ取れば、埀埩 1 回で数 ms
    → å…šä»¶ã‚’ Session に茉せる方が、よほど高く぀く

珟圚の実装の圢:

// 怜玢条件だけを持ち回るURL のク゚リ文字列
// /products?keyword=abc&page=3&sort=price
public IActionResult Index(string keyword, int page = 1, string sort = "id")
{
    var (rows, total) = _repository.Search(keyword, page, pageSize: 20, sort);
    return View(new ProductListViewModel { Rows = rows, Total = total, Page = page });
}

条件を URL に持たせるず、

・ブックマヌクできる、共有できる
・戻るボタンが正しく動く
・サヌバに状態を持たない

ずいう副次的な利点も埗られる。

参考

Stack Overflow

Microsoft Learn


Tags: 移行, .NET開発, ASP.NET, ASP.NET MVC, ADO.NET

⚠ **GitHub.com Fallback** ⚠