Sdílet prostřednictvím


Aktualizace zdrojů dat pomocí objektů DataAdapter

Platí pro: .NET Framework .NET Standard

Stáhnout ADO.NET

Metoda Update objektu DataAdapter je volána k vyřešení změn z DataSet zpět do zdrojových dat. Metoda Update , podobně jako Fill metoda, přebírá jako argumenty instanci objektu DataSeta 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 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 změnu DataRow, použije InsertCommand, UpdateCommand nebo DeleteCommand ke zpracování změny.

Tyto vlastnosti umožňují maximalizovat výkon vaší aplikace ADO.NET zadáním syntaxe příkazů v době návrhu a případně prostřednictvím uložených procedur. Před voláním Updateje 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.

Důležité

Pokud používáte uložené procedury SQL Serveru k úpravě nebo odstranění dat pomocí DataAdapter, ujistěte se, že v definici uložené procedury nepoužíváte 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ě se dojde k vyvolání DBConcurrencyException.

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 Parametry DataAdapteru.

Poznámka:

Je důležité pochopit rozdíl mezi odstraněním řádku v DataTable a odebráním řádku. Když zavoláte metodu `Remove` nebo `RemoveAt`, řá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 DataTable nebo DataSet do DataAdapter a/nebo zavoláte Update. Když použijete metodu Delete , zůstane řádek v DataTable řádku a je označen k odstranění. Pokud předáte DataTable nebo DataSet a zavoláte DataAdapter a Update, odpovídající řádek ve zdroji dat v back-endu se odstraní.

Pokud DataTable mapuje na jednu databázovou tabulku nebo je z ní generován, můžete využít výhody objektu DbCommandBuilder k automatickému vygenerování objektů DeleteCommand, InsertCommand a UpdateCommand pro DataAdapter. Další informace naleznete v tématu Generování příkazů pomocí CommandBuilders.

Mapování hodnot na datovou sadu pomocí UpdatedRowSource

Pomocí vlastnosti objektu SqlCommand můžete ovládat, jak se hodnoty vrácené ze zdroje dat mapují zpět na DataTable po volání metody Update objektu DataAdapter. Nastavením vlastnosti UpdatedRowSource na jednu z hodnot výčtu UpdateRowSource můžete určit, zda výstupní parametry, které vrátí příkazy DataAdapter, budou ignorovány nebo aplikovány 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 Description
Both Výstupní parametry i první řádek vrácené sady výsledků mohou být namapovány na změněný řádek v sadě DataSetvýsledků .
FirstReturnedRecord Na změněný řádek v DataSetsadě 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 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 svůj DataSet pomocí aktuálních dat, použijte metody DataAdapter a Fill. 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 má hodnotu primárního klíče, která neodpovídá žádné z hodnot primárního klíče řádků v sadě DataSet, metoda Fill přidá nový řádek, jehož RowState má hodnotu Unchanged.

Poznámka:

Pokud SelectCommand vrátí výsledky vnějšího spojení, DataAdapter nenastaví hodnotu PrimaryKey pro výsledné DataTable. Musíte definovat PrimaryKey vlastními silami, aby bylo zajištěno, že se duplicitní řádky správně vyřeší.

Chcete-li 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 ContinueUpdateOnError před true voláním Updatea odpovědět na informace o chybě uložené ve RowError vlastnosti konkrétního řádku po dokončení aktualizace.

Poznámka:

Volání AcceptChanges na DataSet, DataTable nebo DataRow způsobí, že všechny Original hodnoty pro DataRow budou přepsány Current hodnotami pro DataRow. Pokud byly hodnoty polí, které identifikují řádek jako jedinečné, změněny, po zavolání AcceptChangesOriginal hodnot již nebudou odpovídat hodnotám ve zdroji dat. AcceptChanges je volána automaticky pro každý řádek během volání metody UpdateDataAdapter. 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 Zpracování událostí datovéhoadapteru.

Následující příklady ukazují, jak provádět aktualizace upravených řádků tím, že explicitně nastavíte UpdateCommandDataAdapter a zavoláte jeho Update metodu.

Poznámka:

Parametr určený v WHERE clause prvku UPDATE statement je nastaven na použití Original hodnoty prvku 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.

private static void AdapterUpdate(string connectionString)
{
    using (SqlConnection connection =
               new SqlConnection(connectionString))
    {
        SqlDataAdapter dataAdpater = new SqlDataAdapter(
          "SELECT CategoryID, CategoryName FROM Categories",
          connection);

        dataAdpater.UpdateCommand = new SqlCommand(
           "UPDATE Categories SET CategoryName = @CategoryName " +
           "WHERE CategoryID = @CategoryID", connection);

        dataAdpater.UpdateCommand.Parameters.Add(
           "@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");

        SqlParameter parameter = dataAdpater.UpdateCommand.Parameters.Add(
          "@CategoryID", SqlDbType.Int);
        parameter.SourceColumn = "CategoryID";
        parameter.SourceVersion = DataRowVersion.Original;

        DataTable categoryTable = new DataTable();
        dataAdpater.Fill(categoryTable);

        DataRow categoryRow = categoryTable.Rows[0];
        categoryRow["CategoryName"] = "New Beverages";

        dataAdpater.Update(categoryTable);

        Console.WriteLine("Rows after update.");
        foreach (DataRow row in categoryTable.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}

Sloupce automatického navýšení

Pokud tabulky z vašeho zdroje dat mají automaticky inkrementované sloupce, můžete sloupce vyplnit následujícími způsoby: vrátíte hodnotu automatického přírůstku jako výstupní parametr uložené procedury a namapujete ji na sloupec v tabulce; vrátíte hodnotu automatického přírůstku v prvním řádku sady výsledků vrácené uloženou procedurou nebo příkazem SQL; nebo použijete událost RowUpdated v DataAdapter pro spuštění dalšího příkazu SELECT. Další informace a příklad najdete v tématu Načtení 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 z DataTable k vrácení pole DataRow, které odkazuje pouze na řádky s konkrétním RowState. Vrácenou matici DataRow pak můžete předat Update 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í.

Example

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.

// Assumes that dataSet and adapter are valid objects.
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 k 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 zdrojovými tabulkami 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 Microsoft.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;

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);
        }
    }
}

namespace CSDataAdapterOperations.Properties
{
    internal sealed partial class Settings : System.Configuration.ApplicationSettingsBase
    {
        private static readonly Settings defaultInstance =
            ((Settings)(System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

        public static Settings Default => defaultInstance;

        [System.Configuration.ApplicationScopedSetting()]
        [System.Configuration.DefaultSettingValue("Data Source=(local);Initial Catalog=MySchool;Integrated Security=True")]
        public string MySchoolConnectionString => ((string)(this["MySchoolConnectionString"]));
    }
}

Viz také