Aktualizace zdrojů dat pomocí objektů DataAdapter
Volá Update
se DataAdapter metoda, která řeší změny ze DataSet zdroje dat zpět. Metoda Update
, podobně jako Fill
metoda, přebírá jako argumenty instanci objektu DataSet
a volitelný DataTable objekt nebo DataTable
název. Instance DataSet
je DataSet
ta, která obsahuje změny, které byly provedeny, a DataTable
identifikuje tabulku, ze které se mají změny načíst. Pokud není zadána žádná DataTable
, použije se první DataTable
v seznamu DataSet
.
Při volání Update
metody analyzuje změny, DataAdapter
které byly provedeny, a spustí příslušný příkaz (INSERT, UPDATE nebo DELETE). DataAdapter
Když narazí na DataRowzměnu , použije , použije InsertCommand, UpdateCommandnebo DeleteCommand ke zpracování změny. To vám umožní maximalizovat výkon ADO.NET aplikace zadáním syntaxe příkazů v době návrhu a tam, kde je to možné, prostřednictvím uložených procedur. Před voláním Update
je nutné explicitně nastavit příkazy . Pokud Update
je volána a příslušný příkaz neexistuje pro konkrétní aktualizaci (například ne DeleteCommand
pro odstraněné řádky), vyvolá se výjimka.
Poznámka:
Pokud k úpravě nebo odstranění dat používáte uložené procedury SQL Serveru, DataAdapter
ujistěte se, že v definici uložené procedury nepoužíváte funkci SET NOCOUNT ON. To způsobí, že počet ovlivněných řádků bude nulový, což DataAdapter
interpretuje jako konflikt souběžnosti. V tomto případě dojde k DBConcurrencyException vyvolání.
Parametry příkazu lze použít k zadání vstupních a výstupních hodnot pro příkaz SQL nebo uloženou proceduru pro každý upravený řádek v objektu DataSet
. Další informace naleznete v tématu DataAdapter Parameters.
Poznámka:
Je důležité pochopit rozdíl mezi odstraněním řádku v DataTable řádku a odebráním řádku. Když zavoláte metodu nebo RemoveAt
metoduRemove
, řádek se okamžitě odebere. Všechny odpovídající řádky v back-endovém zdroji dat nebudou ovlivněny, pokud pak předáte nebo zavoláte DataAdapter
a zavoláte Update
.DataSet
DataTable
Když použijete metodu Delete
, zůstane řádek v DataTable
řádku a je označen k odstranění. Pokud pak předáte DataTable
nebo DataSet
zavoláte DataAdapter
a zavoláte Update
, odstraní se odpovídající řádek v back-endovém zdroji dat.
Pokud se DataTable
mapy mapují nebo generují z jedné databázové tabulky, můžete využít výhod DbCommandBuilder objektu k automatickému vygenerování objektu DeleteCommand
, InsertCommand
a UpdateCommand
objektů pro objekty DataAdapter
. Další informace naleznete v tématu Generování příkazů pomocí CommandBuilders.
Mapování hodnot na datovou sadu pomocí UpdatedRowSource
Pomocí vlastnosti objektu můžete určit, jak se hodnoty vrácené ze zdroje dat mapují zpět na DataTable
následující volání metody DataAdapter
UpdatedRowSource Update objektuDbCommand. UpdatedRowSource
Nastavením vlastnosti na jednu z hodnot výčtu UpdateRowSource můžete řídit, zda jsou výstupní parametry vrácené DataAdapter
příkazy ignorovány nebo použity na změněný řádek v objektu DataSet
. Můžete také určit, zda je první vrácený řádek (pokud existuje) použit na změněný řádek v sadě DataTable
.
Následující tabulka popisuje různé hodnoty výčtu UpdateRowSource
a jejich vliv na chování příkazu použitého s parametrem DataAdapter
.
UpdatedRowSource – výčet | Popis |
---|---|
Both | Výstupní parametry i první řádek vrácené sady výsledků mohou být namapovány na změněný řádek v sadě DataSet výsledků . |
FirstReturnedRecord | Na změněný řádek v DataSet sadě výsledků můžou být namapována pouze data v prvním řádku vrácené sady výsledků . |
None | Všechny výstupní parametry nebo řádky vrácené sady výsledků se ignorují. |
OutputParameters | Na změněný řádek v nástroji DataSet . lze mapovat pouze výstupní parametry. |
Metoda Update
vyřeší vaše změny zpět ke zdroji dat; ostatní klienti však možná změnili data ve zdroji dat od posledního vyplnění DataSet
. Pokud chcete aktualizovat DataSet
aktuální data, použijte metodu a Fill
metoduDataAdapter
. Do tabulky se přidají nové řádky a aktualizované informace se začlení do existujících řádků. Metoda Fill
určuje, zda bude přidán nový řádek nebo existující řádek bude aktualizován prozkoumáním hodnot primárního klíče řádků v DataSet
řádcích a řádků vrácených SelectCommand
. Fill
Pokud metoda narazí na hodnotu primárního klíče pro řádek, DataSet
který odpovídá hodnotě primárního klíče z řádku ve výsledcích vrácených objektem SelectCommand
, aktualizuje existující řádek informacemi z řádku vráceného řádkem SelectCommand
a nastaví RowState existující řádek na Unchanged
. Pokud řádek vrácený SelectCommand
primárním klíčem má hodnotu primárního klíče, která neodpovídá žádné hodnotě primárního klíče řádků v DataSet
sadě , Fill
metoda přidá nový řádek s RowState
hodnotou Unchanged
.
Poznámka:
SelectCommand
Pokud vrátí výsledky vnějšího spojení, DataAdapter
nenastaví PrimaryKey
hodnotu výsledného DataTable
spojení . Musíte definovat PrimaryKey
sami sebe, aby se zajistilo, že se správně přeloží duplicitní řádky. Další informace naleznete v tématu Definování primárních klíčů.
Pokud chcete zpracovat výjimky, ke kterým může dojít při volání Update
metody, můžete pomocí RowUpdated
události reagovat na chyby aktualizace řádků při jejich výskytu (viz Zpracování událostí dataAdapter), nebo můžete nastavit DataAdapter.ContinueUpdateOnError
před true
voláním Update
a odpovědět na informace o chybě uložené ve RowError
vlastnosti konkrétního řádku po dokončení aktualizace (viz Informace o chybě řádku).
Poznámka:
Volání AcceptChanges
na DataSet
, DataTable
nebo DataRow
způsobí, že všechny Original
hodnoty budou DataRow
přepsány Current
hodnotami pro hodnotu .DataRow
Pokud byly hodnoty polí, které identifikují řádek jako jedinečné, změněny, po zavolání AcceptChanges
Original
hodnot již nebudou odpovídat hodnotám ve zdroji dat. AcceptChanges
je volána automaticky pro každý řádek během volání metody Update metody .DataAdapter
Při volání metody Update můžete zachovat původní hodnoty tak, že nejprve nastavíte AcceptChangesDuringUpdate
vlastnost DataAdapter
na hodnotu false, nebo vytvořením obslužné rutiny události pro RowUpdated
událost a nastavením Status na SkipCurrentRowhodnotu . Další informace naleznete v tématu Sloučení obsahu datové sady a zpracování událostí dataAdapter.
Příklad
Následující příklady ukazují, jak provádět aktualizace upravených řádků tím, že explicitně nastavíte UpdateCommand
DataAdapter
a zavoláte jeho Update
metodu. Všimněte si, že parametr zadaný v klauzuli WHERE příkazu UPDATE je nastaven tak, aby používal Original
hodnotu SourceColumn
. To je důležité, protože Current
hodnota mohla být změněna a nemusí odpovídat hodnotě ve zdroji dat. Hodnota Original
je hodnota, která byla použita k naplnění DataTable
ze zdroje dat.
static void AdapterUpdate(string connectionString)
{
using (SqlConnection connection =
new(connectionString))
{
SqlDataAdapter dataAdapter = new(
"SELECT CategoryID, CategoryName FROM Categories",
connection)
{
UpdateCommand = new SqlCommand(
"UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID", connection)
};
dataAdapter.UpdateCommand.Parameters.Add(
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter parameter = dataAdapter.UpdateCommand.Parameters.Add(
"@CategoryID", SqlDbType.Int);
parameter.SourceColumn = "CategoryID";
parameter.SourceVersion = DataRowVersion.Original;
DataTable categoryTable = new();
dataAdapter.Fill(categoryTable);
DataRow categoryRow = categoryTable.Rows[0];
categoryRow["CategoryName"] = "New Beverages";
dataAdapter.Update(categoryTable);
Console.WriteLine("Rows after update.");
foreach (DataRow row in categoryTable.Rows)
{
{
Console.WriteLine("{0}: {1}", row[0], row[1]);
}
}
}
}
Private Sub AdapterUpdate(ByVal connectionString As String)
Using connection As SqlConnection = New SqlConnection( _
connectionString)
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
"SELECT CategoryID, CategoryName FROM dbo.Categories", _
connection)
adapter.UpdateCommand = New SqlCommand( _
"UPDATE Categories SET CategoryName = @CategoryName " & _
"WHERE CategoryID = @CategoryID", connection)
adapter.UpdateCommand.Parameters.Add( _
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName")
Dim parameter As SqlParameter = _
adapter.UpdateCommand.Parameters.Add( _
"@CategoryID", SqlDbType.Int)
parameter.SourceColumn = "CategoryID"
parameter.SourceVersion = DataRowVersion.Original
Dim categoryTable As New DataTable
adapter.Fill(categoryTable)
Dim categoryRow As DataRow = categoryTable.Rows(0)
categoryRow("CategoryName") = "New Beverages"
adapter.Update(categoryTable)
Console.WriteLine("Rows after update.")
Dim row As DataRow
For Each row In categoryTable.Rows
Console.WriteLine("{0}: {1}", row(0), row(1))
Next
End Using
End Sub
Sloupce automatického přírůstku
Pokud tabulky z vašeho zdroje dat mají automaticky inkrementované sloupce, můžete sloupce vyplnit tak, že vrátíte DataSet
hodnotu automatického přírůstku jako výstupní parametr uložené procedury a namapujete ji na sloupec v tabulce tak, že vrátíte hodnotu automatického přírůstku v prvním řádku sady výsledků vrácenou uloženou procedurou nebo příkazem SQL. nebo pomocí RowUpdated
události spuštění dalšího DataAdapter
příkazu SELECT. Další informace a příklad najdete v tématu Načítání hodnot identity nebo automatického číslování.
Řazení vložení, aktualizací a odstranění
V mnoha případech je důležité pořadí, v jakém se změny provedené prostřednictvím DataSet
zdroje dat odesílají. Pokud se například aktualizuje hodnota primárního klíče pro existující řádek a přidá se nový řádek s hodnotou nového primárního klíče jako cizí klíč, je důležité aktualizaci zpracovat před vložením.
Můžete použít metodu Select
DataTable
vrácení DataRow
pole, které odkazuje pouze na řádky s konkrétním RowState
. Vrácenou matici Update
pak můžete předat DataRow
metodě zpracování upravených DataAdapter
řádků. Zadáním podmnožiny řádků, které se mají aktualizovat, můžete řídit pořadí, ve kterém se vložení, aktualizace a odstranění zpracovávají.
Například následující kód zajistí, že se odstraněné řádky tabulky zpracovávají jako první, pak aktualizované řádky a potom vložené řádky.
Dim table As DataTable = dataSet.Tables("Customers")
' First process deletes.
dataSet.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Deleted))
' Next process updates.
adapter.Update(table.Select(Nothing, Nothing, _
DataViewRowState.ModifiedCurrent))
' Finally, process inserts.
adapter.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Added))
DataTable table = dataSet.Tables["Customers"];
// First process deletes.
adapter.Update(table.Select(null, null, DataViewRowState.Deleted));
// Next process updates.
adapter.Update(table.Select(null, null,
DataViewRowState.ModifiedCurrent));
// Finally, process inserts.
adapter.Update(table.Select(null, null, DataViewRowState.Added));
Použití datovéhoadapteru k načtení a aktualizaci dat
DataAdapter můžete použít k načtení a aktualizaci dat.
Ukázka používá DataAdapter.AcceptChangesDuringFill ke klonování dat v databázi. Pokud je vlastnost nastavena jako false, AcceptChanges není volána při vyplňování tabulky a nově přidané řádky jsou považovány za vložené řádky. Ukázka proto tyto řádky používá k vložení nových řádků do databáze.
Ukázky používají DataAdapter.TableMappings k definování mapování mezi zdrojovou tabulkou a tabulkou DataTable.
Ukázka používá DataAdapter.FillLoadOption k určení, jak adaptér vyplní DataTable z DbDataReader. Při vytváření tabulky DataTable můžete zapisovat pouze data z databáze do aktuální verze nebo původní verze nastavením vlastnosti LoadOption.Upsert nebo LoadOption.PreserveChanges.
Ukázka také aktualizuje tabulku pomocí DbDataAdapter.UpdateBatchSize k provádění dávkových operací.
Před kompilací a spuštěním ukázky je potřeba vytvořit ukázkovou databázi:
USE [master]
GO
CREATE DATABASE [MySchool]
GO
USE [MySchool]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Course]([CourseID] [nvarchar](10) NOT NULL,
[Year] [smallint] NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[Credits] [int] NOT NULL,
[DepartmentID] [int] NOT NULL,
CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED
(
[CourseID] ASC,
[Year] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Department]([DepartmentID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Budget] [money] NOT NULL,
[StartDate] [datetime] NOT NULL,
[Administrator] [int] NULL,
CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED
(
[DepartmentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
GO
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1045', 2012, N'Calculus', 4, 7)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1061', 2012, N'Physics', 4, 1)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2021', 2012, N'Composition', 3, 2)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2042', 2012, N'Literature', 4, 2)
SET IDENTITY_INSERT [dbo].[Department] ON
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (1, N'Engineering', 350000.0000, CAST(0x0000999C00000000 AS DateTime), 2)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (2, N'English', 120000.0000, CAST(0x0000999C00000000 AS DateTime), 6)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (4, N'Economics', 200000.0000, CAST(0x0000999C00000000 AS DateTime), 4)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (7, N'Mathematics', 250024.0000, CAST(0x0000999C00000000 AS DateTime), 3)
SET IDENTITY_INSERT [dbo].[Department] OFF
ALTER TABLE [dbo].[Course] WITH CHECK ADD CONSTRAINT [FK_Course_Department] FOREIGN KEY([DepartmentID])
REFERENCES [dbo].[Department] ([DepartmentID])
GO
ALTER TABLE [dbo].[Course] CHECK CONSTRAINT [FK_Course_Department]
GO
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;
namespace CSDataAdapterOperations.Properties {
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
public string MySchoolConnectionString {
get {
return ((string)(this["MySchoolConnectionString"]));
}
}
}
}
class Program {
static void Main(string[] args) {
Settings settings = new Settings();
// Copy the data from the database. Get the table Department and Course from the database.
String selectString = @"SELECT [DepartmentID],[Name],[Budget],[StartDate],[Administrator]
FROM [MySchool].[dbo].[Department];
SELECT [CourseID],@Year as [Year],Max([Title]) as [Title],
Max([Credits]) as [Credits],Max([DepartmentID]) as [DepartmentID]
FROM [MySchool].[dbo].[Course]
Group by [CourseID]";
DataSet mySchool = new DataSet();
SqlCommand selectCommand = new SqlCommand(selectString);
SqlParameter parameter = selectCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2);
parameter.Value = new Random(DateTime.Now.Millisecond).Next(9999);
// Use DataTableMapping to map the source tables and the destination tables.
DataTableMapping[] tableMappings = {new DataTableMapping("Table", "Department"), new DataTableMapping("Table1", "Course")};
CopyData(mySchool, settings.MySchoolConnectionString, selectCommand, tableMappings);
Console.WriteLine("The following tables are from the database.");
foreach (DataTable table in mySchool.Tables) {
Console.WriteLine(table.TableName);
ShowDataTable(table);
}
// Roll back the changes
DataTable department = mySchool.Tables["Department"];
DataTable course = mySchool.Tables["Course"];
department.Rows[0]["Name"] = "New" + department.Rows[0][1];
course.Rows[0]["Title"] = "New" + course.Rows[0]["Title"];
course.Rows[0]["Credits"] = 10;
Console.WriteLine("After we changed the tables:");
foreach (DataTable table in mySchool.Tables) {
Console.WriteLine(table.TableName);
ShowDataTable(table);
}
department.RejectChanges();
Console.WriteLine("After use the RejectChanges method in Department table to roll back the changes:");
ShowDataTable(department);
DataColumn[] primaryColumns = { course.Columns["CourseID"] };
DataColumn[] resetColumns = { course.Columns["Title"] };
ResetCourse(course, settings.MySchoolConnectionString, primaryColumns, resetColumns);
Console.WriteLine("After use the ResetCourse method in Course table to roll back the changes:");
ShowDataTable(course);
// Batch update the table.
String insertString = @"Insert into [MySchool].[dbo].[Course]([CourseID],[Year],[Title],
[Credits],[DepartmentID])
values (@CourseID,@Year,@Title,@Credits,@DepartmentID)";
SqlCommand insertCommand = new SqlCommand(insertString);
insertCommand.Parameters.Add("@CourseID", SqlDbType.NVarChar, 10, "CourseID");
insertCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2, "Year");
insertCommand.Parameters.Add("@Title", SqlDbType.NVarChar, 100, "Title");
insertCommand.Parameters.Add("@Credits", SqlDbType.Int, 4, "Credits");
insertCommand.Parameters.Add("@DepartmentID", SqlDbType.Int, 4, "DepartmentID");
const Int32 batchSize = 10;
BatchInsertUpdate(course, settings.MySchoolConnectionString, insertCommand, batchSize);
}
private static void CopyData(DataSet dataSet, String connectionString, SqlCommand selectCommand, DataTableMapping[] tableMappings) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
selectCommand.Connection = connection;
connection.Open();
using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand)) {adapter.TableMappings.AddRange(tableMappings);
// If set the AcceptChangesDuringFill as the false, AcceptChanges will not be called on a
// DataRow after it is added to the DataTable during any of the Fill operations.
adapter.AcceptChangesDuringFill = false;
adapter.Fill(dataSet);
}
}
}
// Roll back only one column or several columns data of the Course table by call ResetDataTable method.
private static void ResetCourse(DataTable table, String connectionString,
DataColumn[] primaryColumns, DataColumn[] resetColumns) {
table.PrimaryKey = primaryColumns;
// Build the query string
String primaryCols = String.Join(",", primaryColumns.Select(col => col.ColumnName));
String resetCols = String.Join(",", resetColumns.Select(col => $"Max({col.ColumnName}) as {col.ColumnName}"));
String selectString = $"Select {primaryCols},{resetCols} from Course Group by {primaryCols}");
SqlCommand selectCommand = new SqlCommand(selectString);
ResetDataTable(table, connectionString, selectCommand);
}
// RejectChanges will roll back all changes made to the table since it was loaded, or the last time AcceptChanges
// was called. When you copy from the database, you can lose all the data after calling RejectChanges
// The ResetDataTable method rolls back one or more columns of data.
private static void ResetDataTable(DataTable table, String connectionString,
SqlCommand selectCommand) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
selectCommand.Connection = connection;
connection.Open();
using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand)) {
// The incoming values for this row will be written to the current version of each
// column. The original version of each column's data will not be changed.
adapter.FillLoadOption = LoadOption.Upsert;
adapter.Fill(table);
}
}
}
private static void BatchInsertUpdate(DataTable table, String connectionString,
SqlCommand insertCommand, Int32 batchSize) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
insertCommand.Connection = connection;
// When setting UpdateBatchSize to a value other than 1, all the commands
// associated with the SqlDataAdapter have to have their UpdatedRowSource
// property set to None or OutputParameters. An exception is thrown otherwise.
insertCommand.UpdatedRowSource = UpdateRowSource.None;
connection.Open();
using (SqlDataAdapter adapter = new SqlDataAdapter()) {
adapter.InsertCommand = insertCommand;
// Gets or sets the number of rows that are processed in each round-trip to the server.
// Setting it to 1 disables batch updates, as rows are sent one at a time.
adapter.UpdateBatchSize = batchSize;
adapter.Update(table);
Console.WriteLine("Successfully to update the table.");
}
}
}
private static void ShowDataTable(DataTable table) {
foreach (DataColumn col in table.Columns) {
Console.Write("{0,-14}", col.ColumnName);
}
Console.WriteLine("{0,-14}", "RowState");
foreach (DataRow row in table.Rows) {
foreach (DataColumn col in table.Columns) {
if (col.DataType.Equals(typeof(DateTime)))
Console.Write("{0,-14:d}", row[col]);
else if (col.DataType.Equals(typeof(Decimal)))
Console.Write("{0,-14:C}", row[col]);
else
Console.Write("{0,-14}", row[col]);
}
Console.WriteLine("{0,-14}", row.RowState);
}
}
}