Dela via


Uppdatera datakällor med DataAdapters

Metoden Update DataAdapter för anropas för att lösa ändringar från en DataSet tillbaka till datakällan. Metoden Update , som Fill metoden, tar som argument en instans av ett DataSet, och ett valfritt DataTable objekt eller DataTable namn. Instansen DataSet är den DataSet som innehåller de ändringar som har gjorts och DataTable identifierar tabellen som ändringarna ska hämtas från. Om nej DataTable anges används den första DataTable i DataSet .

När du anropar Update metoden DataAdapter analyseras de ändringar som har gjorts och kör lämpligt kommando (INSERT, UPDATE eller DELETE). När det DataAdapter uppstår en ändring i använder DataRowden InsertCommandändringen , UpdateCommandeller DeleteCommand för att bearbeta ändringen. På så sätt kan du maximera prestandan för ditt ADO.NET-program genom att ange kommandosyntax vid designtillfället och, där det är möjligt, genom att använda lagrade procedurer. Du måste uttryckligen ange kommandona innan du anropar Update. Om Update anropas och lämpligt kommando inte finns för en viss uppdatering (till exempel nej DeleteCommand för borttagna rader) genereras ett undantag.

Kommentar

Om du använder SQL Server-lagrade procedurer för att redigera eller ta bort data med hjälp av en DataAdapterkontrollerar du att du inte använder SET NOCOUNT ON i definitionen för lagrad procedur. Detta gör att antalet rader som påverkas returneras till noll, vilket DataAdapter tolkas som en samtidighetskonflikt. I så fall utlöses en DBConcurrencyException .

Kommandoparametrar kan användas för att ange indata- och utdatavärden för en SQL-instruktion eller lagrad procedur för varje ändrad rad i en DataSet. Mer information finns i DataAdapter-parametrar.

Kommentar

Det är viktigt att förstå skillnaden mellan att ta bort en rad i en DataTable och ta bort raden. När du anropar Remove metoden eller RemoveAt tas raden bort omedelbart. Motsvarande rader i serverdelsdatakällan påverkas inte om du sedan skickar DataTable eller till en DataAdapter och anropar UpdateDataSet . När du använder Delete metoden finns raden kvar i DataTable och markeras för borttagning. Om du sedan skickar DataTable eller till ett DataAdapter och anropar Updatetas motsvarande rad i serverdelsdatakällan DataSet bort.

Om dina DataTable kartor till eller genereras från en enskild databastabell kan du dra nytta av DbCommandBuilder objektet för att automatiskt generera objekten DeleteCommand, InsertCommandoch UpdateCommand för DataAdapter. Mer information finns i Generera kommandon med CommandBuilders.

Använda UpdatedRowSource för att mappa värden till en DataSet

Du kan styra hur värdena som returneras från datakällan mappas tillbaka till DataTable följande anrop till metoden Uppdatera för ett DataAdaptermed hjälp UpdatedRowSource av egenskapen för ett DbCommand objekt. Genom att ange UpdatedRowSource egenskapen till ett av UpdateRowSource uppräkningsvärdena kan du styra om utdataparametrar som returneras av DataAdapter kommandona ignoreras eller tillämpas på den ändrade raden i DataSet. Du kan också ange om den första returnerade raden (om den finns) tillämpas på den ändrade raden i DataTable.

I följande tabell beskrivs de olika värdena för UpdateRowSource uppräkningen och hur de påverkar beteendet för ett kommando som används med en DataAdapter.

UpdatedRowSource-uppräkning beskrivning
Both Både utdataparametrarna och den första raden i en returnerad resultatuppsättning kan mappas till den ändrade raden i DataSet.
FirstReturnedRecord Endast data i den första raden i en returnerad resultatuppsättning kan mappas till den ändrade raden i DataSet.
None Utdataparametrar eller rader i en returnerad resultatuppsättning ignoreras.
OutputParameters Endast utdataparametrar kan mappas till den ändrade raden i DataSet.

Metoden Update löser dina ändringar tillbaka till datakällan, men andra klienter kan ha ändrat data i datakällan sedan den senaste gången du fyllde i DataSet. Om du vill uppdatera med DataSet aktuella data använder du DataAdapter metoden och Fill . Nya rader läggs till i tabellen och uppdaterad information införlivas i befintliga rader. Metoden Fill avgör om en ny rad ska läggas till eller om en befintlig rad ska uppdateras genom att undersöka primärnyckelvärdena för raderna i DataSet och de rader som returneras av SelectCommand. Fill Om metoden påträffar ett primärnyckelvärde för en rad i DataSet som matchar ett primärnyckelvärde från en rad i resultatet som returneras av SelectCommanduppdaterar den befintliga raden med informationen från raden som returneras av SelectCommand och anger den RowState befintliga raden till Unchanged. Om en rad som returneras av SelectCommand har ett primärnyckelvärde som inte matchar något av primärnyckelvärdena för raderna i DataSetlägger Fill metoden till en ny rad med värdet RowState Unchanged.

Kommentar

SelectCommand Om returnerar resultatet av en YTTRE KOPPLING DataAdapter anger inte ett PrimaryKey värde för den resulterande DataTable. Du måste definiera PrimaryKey dig själv för att dubbletter av rader ska matchas korrekt. Mer information finns i Definiera primära nycklar.

Om du vill hantera undantag som kan inträffa när du anropar Update metoden kan du använda RowUpdated händelsen för att svara på raduppdateringsfel när de inträffar (se Hantera DataAdapter-händelser), eller så kan du ange DataAdapter.ContinueUpdateOnError till true innan du anropar Updateoch svara på felinformationen RowError som lagras i egenskapen för en viss rad när uppdateringen är klar (se Radfelinformation).

Kommentar

Anropar AcceptChangesDataSet, DataTable, eller DataRow gör att alla Original värden för en DataRow skrivs över med Current värdena för DataRow. Om fältvärdena som identifierar raden som unik har ändrats efter att värdena har anropats AcceptChanges Original matchar inte längre värdena i datakällan. AcceptChanges anropas automatiskt för varje rad under ett anrop till uppdateringsmetoden för en DataAdapter. Du kan bevara de ursprungliga värdena under ett anrop till metoden Uppdatera genom att först ange AcceptChangesDuringUpdate egenskapen för till DataAdapter false, eller genom att skapa en händelsehanterare för RowUpdated händelsen och ange Status till SkipCurrentRow. Mer information finns i Slå samman datauppsättningsinnehåll och hantera DataAdapter-händelser.

Exempel

Följande exempel visar hur du utför uppdateringar av ändrade rader genom att uttryckligen UpdateCommand ange metoden och DataAdapter anropa dess Update metod. Observera att parametern som anges i WHERE-satsen i UPDATE-instruktionen är inställd på Original att använda värdet för SourceColumn. Detta är viktigt eftersom Current värdet kan ha ändrats och kanske inte matchar värdet i datakällan. Värdet Original är det värde som användes för att fylla i DataTable från datakällan.

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

Autoincrement-kolumner

Om tabellerna från datakällan har automatiskt inkrementella kolumner kan du fylla kolumnerna i antingen DataSet genom att returnera värdet för automatisk inkrement som en utdataparameter för en lagrad procedur och mappa det till en kolumn i en tabell genom att returnera värdet för automatisk inkrement på den första raden i en resultatuppsättning som returneras av en lagrad procedur eller SQL-instruktion. eller genom att använda RowUpdated händelsen för DataAdapter att köra ytterligare en SELECT-instruktion. Mer information och ett exempel finns i Hämta identitets- eller räknarevärden.

Ordningsföljd för infogningar, uppdateringar och borttagningar

I många fall är den ordning i vilken ändringar som görs via DataSet skickas till datakällan viktig. Om till exempel ett primärnyckelvärde för en befintlig rad uppdateras och en ny rad har lagts till med det nya primärnyckelvärdet som sekundärnyckel, är det viktigt att bearbeta uppdateringen före infogningen.

Du kan använda Select metoden DataTable för för att returnera en DataRow matris som endast refererar till rader med en viss RowState. Du kan sedan skicka den returnerade DataRow matrisen Update till metoden DataAdapter för att bearbeta de ändrade raderna. Genom att ange en delmängd rader som ska uppdateras kan du styra i vilken ordning infogningar, uppdateringar och borttagningar bearbetas.

Följande kod säkerställer till exempel att de borttagna raderna i tabellen bearbetas först, sedan de uppdaterade raderna och sedan de infogade raderna.

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

Använda en DataAdapter för att hämta och uppdatera data

Du kan använda en DataAdapter för att hämta och uppdatera data.

  • Exemplet använder DataAdapter.AcceptChangesDuringFill för att klona data i databasen. Om egenskapen anges som false anropas inte AcceptChanges när tabellen fylls i, och de nyligen tillagda raderna behandlas som infogade rader. Exemplet använder därför dessa rader för att infoga de nya raderna i databasen.

  • Exemplen använder DataAdapter.TableMappings för att definiera mappningen mellan källtabellen och DataTable.

  • Exemplet använder DataAdapter.FillLoadOption för att avgöra hur adaptern fyller DataTable från DbDataReader. När du skapar en DataTable kan du bara skriva data från databasen till den aktuella versionen eller den ursprungliga versionen genom att ange egenskapen som LoadOption.Upsert eller LoadOption.PreserveChanges.

  • Exemplet uppdaterar också tabellen med hjälp av DbDataAdapter.UpdateBatchSize för att utföra batchåtgärder.

Innan du kompilerar och kör exemplet måste du skapa exempeldatabasen:

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

Se även