このチュートリアルでは、オプティミスティック コンカレンシー制御の要点を確認し、SqlDataSource コントロールを使用して実装する方法について説明します。
イントロダクション
前のチュートリアルでは、SqlDataSource コントロールに挿入、更新、削除の機能を追加する方法について説明しました。 要するに、これらの機能を提供するには、コントロールのINSERT
、UPDATE
、またはDELETE
プロパティで、対応するInsertCommand
、UpdateCommand
、またはDeleteCommand
SQL ステートメントを、InsertParameters
、UpdateParameters
、およびDeleteParameters
コレクションの適切なパラメーターと共に指定する必要があります。 これらのプロパティとコレクションは手動で指定できますが、データ ソースの構成ウィザードの [詳細設定] ボタンには、INSERT
ステートメントに基づいてこれらのステートメントを自動的に作成する [UPDATE
、DELETE
、およびSELECT
ステートメントの生成] チェック ボックスが用意されています。
[ INSERT
、 UPDATE
、および DELETE
ステートメントの生成] チェックボックスに加えて、[SQL 生成オプションの詳細設定] ダイアログ ボックスには、[オプティミスティック コンカレンシーを使用する] オプションが表示されます (図 1 を参照)。 オンにすると、自動生成されたWHERE
およびUPDATE
ステートメントのDELETE
句は、ユーザーが最後にデータをグリッドに読み込んだ後に基になるデータベース データが変更されていない場合にのみ、更新または削除を実行するように変更されます。
図 1: [高度な SQL 生成オプション] ダイアログ ボックスからオプティミスティック コンカレンシーのサポートを追加できる
オプティミスティック コンカレンシーの実装に関するチュートリアルに戻り、オプティミスティック コンカレンシー制御の基礎と、それを ObjectDataSource に追加する方法について説明しました。 このチュートリアルでは、オプティミスティック コンカレンシー制御の基本について説明し、SqlDataSource を使用して実装する方法について説明します。
オプティミスティック コンカレンシーの要約
複数の同時ユーザーが同じデータを編集または削除できる Web アプリケーションでは、あるユーザーが誤って別の変更を上書きする可能性があります。 オプティミスティック コンカレンシーの実装に関するチュートリアルでは、次の例を示しました。
Jisun と Sam の 2 人のユーザーが、ユーザーが GridView コントロールを使用して製品を更新および削除できるアプリケーションのページにアクセスしたとします。 ほぼ同じタイミングで、両方がチャイの編集ボタンをクリックします。 Jisun は製品名をチャイ ティーに変更し、[更新] ボタンをクリックします。 結果はデータベースに送信される UPDATE
ステートメントであり、製品 のすべての 更新可能なフィールドが設定されます (Jisun が更新したフィールドは 1 つだけですが、 ProductName
)。 この時点で、このデータベースには、この特定の製品の登録情報としてChai Tea、カテゴリ、飲料、サプライヤーエキゾチック・リキッドなどが含まれています。 ただし、Sam の画面の GridView には、編集可能な GridView 行に Chai として製品名が表示されます。 Jisun の変更がコミットされてから数秒後、Sam はカテゴリを Condiments に更新し、[更新] をクリックします。 これにより、 UPDATE
ステートメントがデータベースに送信され、製品名が Chai に設定され、 CategoryID
が対応する Condiments カテゴリ ID に設定されます。 製品名に対する Jisun の変更が上書きされました。
図 2 は、この相互作用を示しています。
図2: 2人のユーザーがレコードを同時に更新する場合、1人のユーザーの変更が他のユーザーの変更を上書きする可能性がある (フルサイズの画像を表示するにはクリックします)
このシナリオが展開されないようにするには、 コンカレンシー制御 の形式を実装する必要があります。 オプティミスティック コンカレンシー では、このチュートリアルの焦点は、コンカレンシーの競合が随時発生する可能性がある一方で、そのような競合の大部分が発生しないという前提に基づいて機能します。 したがって、競合が発生した場合、オプティミスティック コンカレンシー制御は、別のユーザーが同じデータを変更したため、変更を保存できないことをユーザーに通知するだけです。
注
コンカレンシーの競合が多数あると見なされるアプリケーションや、そのような競合が許容できない場合は、代わりにペシミスティック コンカレンシー制御を使用できます。 ペシミスティック コンカレンシー制御の詳細については、 オプティミスティック コンカレンシーの実装 に関するチュートリアルを参照してください。
オプティミスティック コンカレンシー制御は、更新または削除されるレコードの値が、更新または削除プロセスの開始時と同じであることを確認することによって機能します。 たとえば、編集可能な GridView で [編集] ボタンをクリックすると、レコードの値がデータベースから読み取られ、TextBoxes やその他の Web コントロールに表示されます。 これらの元の値は GridView によって保存されます。 後で、ユーザーが変更を加えて [更新] ボタンをクリックした後、使用する UPDATE
ステートメントでは、元の値と新しい値を考慮し、ユーザーが編集を開始した元の値がデータベース内の値と同じ場合にのみ、基になるデータベース レコードを更新する必要があります。 図 3 は、この一連のイベントを示しています。
図 3: 更新または削除が成功するには、元の値が現在のデータベース値と等しい必要があります (フルサイズの画像を表示する をクリックします)。
オプティミスティック コンカレンシーを実装するには、さまざまな方法があります (さまざまなオプションを簡単に見るための Peter A. Bromberg の オプティミスティック コンカレンシー更新ロジック を参照してください)。 SqlDataSource (およびデータ アクセス層で使用される ADO.NET 型指定されたデータセット) で使用される手法により、 WHERE
句が拡張され、元のすべての値の比較が含まれます。 たとえば、次の UPDATE
ステートメントは、現在のデータベース値が GridView のレコードを更新するときに最初に取得された値と等しい場合にのみ、製品の名前と価格を更新します。
@ProductName
パラメーターと @UnitPrice
パラメーターにはユーザーが入力した新しい値が含まれますが、@original_ProductName
と@original_UnitPrice
には、[編集] ボタンがクリックされたときに GridView に最初に読み込まれた値が含まれます。
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
このチュートリアルで説明するように、SqlDataSource でオプティミスティック コンカレンシー制御を有効にすることは、チェック ボックスをオンにするのと同じくらい簡単です。
手順 1: オプティミスティック コンカレンシーをサポートする SqlDataSource の作成
まず、OptimisticConcurrency.aspx
フォルダーからSqlDataSource
ページを開きます。 ツールボックスからデザイナーに SqlDataSource コントロールをドラッグし、その ID
プロパティを ProductsDataSourceWithOptimisticConcurrency
に設定します。 次に、コントロールのスマート タグから [データ ソースの構成] リンクをクリックします。 ウィザードの最初の画面で、 NORTHWINDConnectionString
の操作を選択し、[次へ] をクリックします。
図 4: NORTHWINDConnectionString
の操作を選択する (フルサイズの画像を表示する] をクリックします)
この例では、ユーザーが Products
テーブルを編集できるようにする GridView を追加します。 そのため、図 5 に示すように、[ステートメントの選択] 画面から Products
テーブルをドロップダウン リストから選択し、 ProductID
、 ProductName
、 UnitPrice
、および Discontinued
列を選択します。
図 5: Products
テーブルから、 ProductID
、 ProductName
、 UnitPrice
、および Discontinued
列を返します (フルサイズの画像を表示する をクリックします)。
列を選択した後、[詳細設定] ボタンをクリックして、[SQL 生成の詳細オプション] ダイアログ ボックスを表示します。
INSERT
、UPDATE
、およびDELETE
ステートメントを生成し、「オプティミスティック コンカレンシーを使用する」チェックボックスをオンにして、[OK] をクリックします(スクリーンショットについては、図1を参照してください)。 [次へ]、[完了] の順にクリックして、ウィザードを完了します。
データ ソースの構成ウィザードが完了したら、少し時間を取って、結果の DeleteCommand
プロパティと UpdateCommand
プロパティ、および DeleteParameters
コレクションと UpdateParameters
コレクションを確認します。 これを行う最も簡単な方法は、左下隅にある [ソース] タブをクリックして、ページの宣言構文を表示することです。 次の UpdateCommand
値があります。
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
UpdateParameters
コレクションに 7 つのパラメーターがあります。
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ...>
<DeleteParameters>
...
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</UpdateParameters>
...
</asp:SqlDataSource>
同様に、 DeleteCommand
プロパティと DeleteParameters
コレクションは次のようになります。
DELETE FROM [Products]
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ...>
<DeleteParameters>
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
...
</UpdateParameters>
...
</asp:SqlDataSource>
WHERE
プロパティとUpdateCommand
プロパティのDeleteCommand
句を拡張する (および各パラメーター コレクションにパラメーターを追加する) だけでなく、[オプティミスティック コンカレンシーを使用する] オプションを選択すると、他の 2 つのプロパティが調整されます。
-
ConflictDetection
プロパティをOverwriteChanges
(既定値) からCompareAllValues
に変更します。 -
OldValuesParameterFormatString
プロパティを {0} (既定値) から original_{0} に変更します。
データ Web コントロールが SqlDataSource の Update()
または Delete()
メソッドを呼び出すと、元の値が渡されます。 SqlDataSource の ConflictDetection
プロパティが CompareAllValues
に設定されている場合、これらの元の値がコマンドに追加されます。
OldValuesParameterFormatString
プロパティは、これらの元の値パラメーターに使用される名前付けパターンを提供します。 データ ソースの構成ウィザードでは、original_{0} を使用し、 UpdateCommand
および DeleteCommand
プロパティの元の各パラメーターに名前を付け、それに応じて UpdateParameters
および DeleteParameters
コレクションに名前を付けます。
注
SqlDataSource コントロールの挿入機能を使用していないため、 InsertCommand
プロパティとその InsertParameters
コレクションを自由に削除してください。
正しく値を処理NULL
残念ながら、オプティミスティック コンカレンシーを使用する場合、データ ソースの構成ウィザードによって自動生成された拡張UPDATE
およびDELETE
ステートメントは、値を含むレコードでは機能NULL
。 その理由を確認するには、SqlDataSource の UpdateCommand
を検討してください。
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
UnitPrice
テーブルのProducts
列には、NULL
値を指定できます。 特定のレコードにNULL
のUnitPrice
値がある場合、WHERE
[UnitPrice] = @original_UnitPrice
句の部分は常に False に評価されます。NULL = NULL
は常に False を返します。 したがって、 NULL
値を含むレコードは編集または削除できません。 UPDATE
ステートメントと DELETE
ステートメント WHERE
句は更新または削除する行を返さないためです。
注
このバグは、2004 年 6 月に SqlDataSource で最初に Microsoft に報告されました。 正しくない SQL ステートメントが生成 され、次のバージョンの ASP.NET で修正される予定であると報告されています。
これを修正するには、WHERE
値を持つUpdateCommand
列のDeleteCommand
プロパティと プロパティの両方で、NULL
句を手動で更新する必要があります。 一般に、 [ColumnName] = @original_ColumnName
を次に変更します。
(
([ColumnName] IS NULL AND @original_ColumnName IS NULL)
OR
([ColumnName] = @original_ColumnName)
)
この変更は、宣言型マークアップ、プロパティ ウィンドウの UpdateQuery または DeleteQuery オプション、またはデータ ソースの構成ウィザードの [カスタム SQL ステートメントまたはストアド プロシージャの指定] オプションの [UPDATE] タブと [DELETE] タブを使用して直接行うことができます。 この変更は、UpdateCommand
内のDeleteCommand
句でWHERE
値を含むことができるNULL
列に対して行う必要があります。
これをこの例に適用すると、次の変更された UpdateCommand
と DeleteCommand
の値になります。
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
DELETE FROM [Products]
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
手順 2: 編集オプションと削除オプションを使用して GridView を追加する
オプティミスティック コンカレンシーをサポートするように SqlDataSource を構成した場合、残っているのは、このコンカレンシー コントロールを利用するページにデータ Web コントロールを追加することです。 このチュートリアルでは、編集と削除の両方の機能を提供する GridView を追加します。 これを行うには、ツールボックスからデザイナーに GridView をドラッグし、その ID
を Products
に設定します。 GridView のスマート タグから、手順 1 で追加した ProductsDataSourceWithOptimisticConcurrency
SqlDataSource コントロールにバインドします。 最後に、[編集を有効にする] と [スマート タグからの削除を有効にする] オプションをオンにします。
図 6: GridView を SqlDataSource にバインドし、編集と削除を有効にする (フルサイズの画像を表示する をクリックします)
GridView を追加した後、ProductID
BoundField を削除し、ProductName
BoundField の HeaderText
プロパティを Product に変更し、UnitPrice
プロパティが単に Price になるように BoundField HeaderText
を更新して、外観を構成します。 理想的には、編集インターフェイスを拡張して、 ProductName
値の RequiredFieldValidator と、 UnitPrice
値の CompareValidator を含めます (適切に書式設定された数値であることを確認するため)。 GridView の編集インターフェイスのカスタマイズの詳細については、「 データ変更インターフェイス のカスタマイズ」チュートリアルを参照してください。
注
GridView から SqlDataSource に渡された元の値はビュー ステートに格納されるため、GridView のビュー ステートを有効にする必要があります。
GridView に対してこれらの変更を行った後、GridView および SqlDataSource 宣言型マークアップは次のようになります。
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ConflictDetection="CompareAllValues"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
DeleteCommand=
"DELETE FROM [Products]
WHERE [ProductID] = @original_ProductID
AND [ProductName] = @original_ProductName
AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice))
AND [Discontinued] = @original_Discontinued"
OldValuesParameterFormatString=
"original_{0}"
SelectCommand=
"SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
FROM [Products]"
UpdateCommand=
"UPDATE [Products]
SET [ProductName] = @ProductName, [UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE [ProductID] = @original_ProductID
AND [ProductName] = @original_ProductName
AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice))
AND [Discontinued] = @original_Discontinued">
<DeleteParameters>
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</UpdateParameters>
</asp:SqlDataSource>
<asp:GridView ID="Products" runat="server"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ProductsDataSourceWithOptimisticConcurrency">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Price"
SortExpression="UnitPrice" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
オプティミスティック コンカレンシー 制御の動作を確認するには、2 つのブラウザー ウィンドウを開き、両方で OptimisticConcurrency.aspx
ページを読み込みます。 両方のブラウザーで最初の製品の [編集] ボタンをクリックします。 1 つのブラウザーで製品名を変更し、[更新] をクリックします。 ブラウザーはポストバックされ、GridView は編集前モードに戻り、編集したレコードの新しい製品名が表示されます。
2 番目のブラウザー ウィンドウで価格を変更し (ただし、製品名は元の値のままにします)、[更新] をクリックします。 ポストバックでは、グリッドは編集前モードに戻りますが、価格の変更は記録されません。 2つ目のブラウザーには、新製品名と古い価格が、最初のブラウザーと同じ値として表示されます。 2 番目のブラウザー ウィンドウで行われた変更は失われました。 さらに、コンカレンシー違反が発生したことを示す例外やメッセージがないため、変更はかなり静かに失われました。
図 7: 2 番目のブラウザー ウィンドウの変更がサイレント モードで失われました (フルサイズの画像を表示する をクリックします)。
2 番目のブラウザーの変更がコミットされなかった理由は、 UPDATE
ステートメントの WHERE
句によってすべてのレコードがフィルターで除外されたため、行に影響を与えなかったためです。
UPDATE
ステートメントをもう一度見てみましょう。
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL) OR
([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
2 番目のブラウザー ウィンドウでレコードが更新されると、 WHERE
句で指定された元の製品名が既存の製品名と一致しません (最初のブラウザーによって変更されたため)。 したがって、ステートメント [ProductName] = @original_ProductName
は False を返し、 UPDATE
はレコードに影響しません。
注
削除も同様に機能します。 2 つのブラウザー ウィンドウが開いた状態で、まず特定の製品を 1 つで編集し、その変更を保存します。 一方のブラウザーで変更を保存した後、もう一方のブラウザーで同じ製品の [削除] ボタンをクリックします。
DELETE
ステートメントの WHERE
句では元の値が一致しないため、削除は警告なしに失敗します。
2 番目のブラウザー ウィンドウのエンド ユーザーの観点から、[更新] ボタンをクリックすると、グリッドは編集前モードに戻りますが、変更は失われました。 ただし、変更が反映されていないという視覚的なフィードバックはありません。 理想的には、ユーザーの変更がコンカレンシー違反に失われた場合は、ユーザーに通知し、おそらく、グリッドを編集モードに保ちます。 これを実現する方法を見てみましょう。
手順 3: コンカレンシー違反がいつ発生したかを判断する
コンカレンシー違反は行った変更を拒否するので、コンカレンシー違反が発生したときにユーザーに警告を出すのが良いでしょう。 ユーザーに警告するには、 ConcurrencyViolationMessage
という名前のページの上部にラベル Web コントロールを追加します。 Text
プロパティに次のメッセージが表示されます。別のユーザーによって同時に更新されたレコードを更新または削除しようとしました。 他のユーザーの変更を確認してから、更新または削除をやり直してください。 Label コントロールの CssClass
プロパティを Warning に設定します。これは、赤、斜体、太字、および大きなフォントでテキストを表示する Styles.css
で定義された CSS クラスです。 最後に、ラベルの Visible
プロパティと EnableViewState
プロパティを false
に設定します。 これにより、 Visible
プロパティを明示的に true
に設定したポストバックのみを除き、ラベルは非表示になります。
図 8: 警告を表示するページにラベル コントロールを追加します (フルサイズの画像を表示する をクリックします)。
更新または削除を実行すると、データ ソース コントロールが要求された更新または削除を行った後に、GridView の RowUpdated
イベント ハンドラーと RowDeleted
イベント ハンドラーが発生します。 これらのイベント ハンドラーから、操作の影響を受けた行の数を確認できます。 0 行が影響を受けた場合は、 ConcurrencyViolationMessage
ラベルを表示します。
RowUpdated
イベントとRowDeleted
イベントの両方のイベント ハンドラーを作成し、次のコードを追加します。
protected void Products_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.AffectedRows == 0)
{
ConcurrencyViolationMessage.Visible = true;
e.KeepInEditMode = true;
// Rebind the data to the GridView to show the latest changes
Products.DataBind();
}
}
protected void Products_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
if (e.AffectedRows == 0)
ConcurrencyViolationMessage.Visible = true;
}
どちらのイベント ハンドラーでも、 e.AffectedRows
プロパティを確認し、0 の場合は、 ConcurrencyViolationMessage
Label の Visible
プロパティを true
に設定します。
RowUpdated
イベント ハンドラーでは、KeepInEditMode
プロパティを true に設定して、編集モードを維持するように GridView に指示します。 その際、他のユーザーのデータが編集インターフェイスに読み込まれるように、データをグリッドに再バインドする必要があります。 これを行うには、GridView の DataBind()
メソッドを呼び出します。
図 9 に示すように、これら 2 つのイベント ハンドラーでは、コンカレンシー違反が発生するたびに非常に顕著なメッセージが表示されます。
図 9: コンカレンシー違反の顔にメッセージが表示されます (フルサイズの画像を表示する をクリックします)。
概要
複数の同時ユーザーが同じデータを編集している可能性がある Web アプリケーションを作成する場合は、コンカレンシー制御オプションを検討することが重要です。 既定では、ASP.NET データ Web コントロールとデータ ソース コントロールはコンカレンシー制御を使用しません。 このチュートリアルで説明したように、SqlDataSource を使用したオプティミスティック コンカレンシー制御の実装は比較的迅速かつ簡単です。 SqlDataSource は、自動生成されたWHERE
およびUPDATE
ステートメントに拡張DELETE
句を追加するためのほとんどの作業を処理しますが、「NULL
値の正しい処理」セクションで説明されているように、NULL
値列の処理にはいくつかの微妙な点があります。
このチュートリアルでは、SqlDataSource の調査を終了します。 残りのチュートリアルでは、ObjectDataSource と階層化アーキテクチャを使用したデータの操作に戻ります。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。