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 DataAdapter
kontrollerar 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 Update
DataSet
. 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 Update
tas 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
, InsertCommand
och 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 DataAdapter
med 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 SelectCommand
uppdaterar 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 DataSet
lä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 Update
och svara på felinformationen RowError
som lagras i egenskapen för en viss rad när uppdateringen är klar (se Radfelinformation).
Kommentar
Anropar AcceptChanges
på DataSet
, 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);
}
}
}