Poznáváme C# a Microsoft .NET 64. díl – ADO .NET – Datový adaptér

Během našeho několikadílného poznávání komponenty ADO .NET jsme poměrně dobře poznali objekty související s implementací aplikací pracující s daty v takzvaném odpojeném scénáři. Do postupně skládané mozaiky našeho vědění nám již chybí jen se podrobněji seznámit s datovými adaptéry.

Role datového adaptéru v ADO .NET

Z úvodního dílu o odpojených aplikacích víme, že jednou z cest k naplnění objektu DataSet tabulkami, které jsou reprezentovány objekty DataTable, je použití takzvaného datového adaptéru. Pomocí datového adaptéru nejsme schopni pouze objekt DataSet naplnit, ale provádět obousměrnou synchronizaci mezi daty v objektu DataSet a příslušným datovým zdrojem.

Datový adaptér je v řeči objektového programování implementací rozhraní IDataAdapter. Všechny základní datové adaptéry v ADO .NET, kterými jsou SqlDataAdapter, OracleDataAdapter, OleDbDataAdapter a OdbcDataAdapter kromě zmíněného základního rozhraní implementují také rozhraní IDbDataAdapter, které rozhraní IDataAdapter rozšiřuje.

Datový adaptér zařizuje obousměrnou synchronizaci dat na základě svých čtyř příkazů, které jsou představovány vlastnostmi SelectCommand, InsertCommand, UpdateCommand a DeleteCommand. První zmíněný příkaz slouží k definici dotazu, který zajistí naplnění DataSetu daty obsaženými ve vrácené výsledkové sadě. Zbylé tři slouží k synchronizaci druhým směrem, tedy pro přidávání, úpravy a mazání záznamů z datového zdroje na základě dat v DataSetu.

Implementace odpojené aplikace

Pro naplnění DataSetu daty je na datovém adaptéru metoda Fill, jíž předáme objekt DataSet, který hodláme naplnit. Tato metoda pro naplnění použije výsledkovou sadu získanou použitím příkazu SelectCommand. Pro synchronizaci směrem k datovému zdroji (přidání, úpravy, mazání záznamů) využijeme metodu Update, která zařídí použití příslušných příkazů (InsertCommand, UpdateCommand, DeleteCommand) pro úpravu dat v datovém zdroji.

Určitě vás zajímá, jakým způsobem datový adaptér ví, které řádky má do datového zdroje přidat, které má upravit a které má smazat. Je to tak, že datový adaptér při vykonáváni své metody Update kontroluje vlastnost RowState jednotlivých řádků (DataRow) v objektu DataTable. Podle hodnoty této vlastnosti provede příslušný příkaz. Takže v případě hodnoty Added provede adaptér pro daný řádek příkaz InsertCommand, pro hodnotu Modified provede příkaz UpdateCommand a pro hodnotu Deleted provede příkaz DeleteCommand.

Nyní je podle mě vhodné přejít k poměrně obsáhlému zdrojovému kódu příkladu, který ukazuje použití datového adaptéru pro synchronizaci objektu DataSet s datovým zdrojem, kterým je v našem příkladu opět databáze MS SQL server 2000.

/// <summary>
/// Priklad na pouzi datoveho adapteru
/// </summary>
public class AdapterUsing
{
  SqlConnection connection;
SqlDataAdapter adapter;
  DataSet DataSet;

public AdapterUsing()
  {
    //inicializace spojeni
    connection = new SqlConnection(@"...");
    DataSet = new DataSet();
    adapter = new SqlDataAdapter();
    //inicializace prikazu pro vyber, pridani a mazani zaznamu
´    adapter.SelectCommand = new SqlCommand("select ID, FirstName, SurName, BirthDate from employee", connection);
    adapter.InsertCommand = new SqlCommand("insert into employee(FirstName, SurName, BirthDate) values(@FirstName, @SurName, @BirthDate)", connection);
    adapter.DeleteCommand = new SqlCommand("delete from employee where
ID=@ID", connection);
    //nastaveni mapovani parametru
    adapter.InsertCommand.Parameters.Add("@FirstName", SqlDbType.NVarChar, 30, "FirstName");
    adapter.InsertCommand.Parameters.Add("@SurName", SqlDbType.NVarChar, 30, "SurName");
    adapter.InsertCommand.Parameters.Add("@BirthDate", SqlDbType.DateTime, 30, "BirthDate");

    adapter.DeleteCommand.Parameters.Add("@ID", SqlDbType.Int, 4, "ID");
  }

  /// <summary>
  /// Vypise vsechny zamestnance
  /// </summary>
  public void ShowEmployees()
  {
    //pokud DataSet jeste neobsahuje zadnou tabulku
    //je tabulka vytvorena
    if (DataSet.Tables.Count == 0)
    {
      adapter.Fill(DataSet, "employees");
    }
    //projit kazdy radek v tabulce
    foreach(DataRow lRow in DataSet.Tables["employees"].Rows)
    {
      //projit kazdy sloupec v tabulce
      foreach(DataColumn lCol in DataSet.Tables["employees"].Columns)
      {
        Console.Write(lRow[lCol] + " ");
      }
      Console.WriteLine();
    }
  }

  /// <summary>
  /// Prida zaznam o zamestnanci
  /// </summary>
  public void AddEmployee(string firstName, string surName, DateTime birthDate)
  {
    DataRow record = DataSet.Tables["employees"].NewRow();
    record["FirstName"] = firstName;
    record["SurName"] = surName;
    record["BirthDate"] = birthDate;
    DataSet.Tables["employees"].Rows.Add(record);
  }

  /// <summary>
  /// Odstrani zamestnance se specifickym jmenem a prijmenim
  /// </summary>
  public void DeleteEmployee(string firstName, string surName)
  {
    string selectExpr = String.Format("FirstName=`{0}` and SurName=`{1}`", firstName, surName);
    DataRow[] result = DataSet.Tables["employees"].Select(selectExpr);
    if (result.Length != 0)
    {
      result[0].Delete();
    }
  }

  /// <summary>
  /// Synchronizuje DataSet s databazi
  /// </summary>
  public void Synchronize()
  {
    if (DataSet.Tables.Count != 0)
    {
      adapter.Update(DataSet, "employees");
    }
  }

  public static void Run()
  {
    AdapterUsing instance = new AdapterUsing();
    instance.ShowEmployees();
    instance.AddEmployee("Novy", "Zamestnanec", DateTime.Now);
    instance.Synchronize();
    instance.DeleteEmployee("Stary", "Zamestnanec");
    instance.Synchronize();
    instance.ShowEmployees();
  }
}

Pojďme se na klíčové části tohoto příkladu podívat. V konstruktoru naší třídy je prováděna inicializace datového adaptéru, jejíž nejdůležitější částí je definice jednotlivých příkazů. V příkladu jsou adaptéru definovány příkazy SelectCommand, InsertCommand a DeleteCommand. Definice příkazu UpdateCommand v příkladu chybí, protože v něm nejsou prováděny žádné úpravy záznamů. Nicméně jeho definice by byla analogická k definici příkazu InsertCommand.

Definice příkazu SelectCommand je jednoduchá a jednoduše vrátí výsledkovou sadu s kompletním obsahem databázové tabulky. O něco složitější je však definice příkazů ostatních, kde je zapotřebí použít příkazů parametrických. Při definici jednotlivých parametrů je potřebné zadat jméno parametru, jakého typu je mapovaný sloupec v datovém zdroji, velikost tohoto sloupce a název sloupce v objektu DataTable z něhož bude převzata hodnota, která bude použita při synchronizaci.

Při použití metody Fill je v případě, že naplňovaný objekt DataSet neobsahuje objekt DataTable specifikovaného jména (druhý parametr metody Fill), je pod tímto jménem vytvořen objekt DataTable, jehož schéma je vytvořeno podle výsledkové sady vrácené příkazem SelectCommand. V našem příkladu tedy bude po naplnění objektu DataSet vytvořen objekt DataTable se sloupci ID, FirstName, SurName a BirhDate. A právě pod těmito názvy se odkazujeme na zdrojové sloupce (čtvrtý parametr metody Add) při definici parametrů zmíněných parametrických příkazů, což jsou v našem příkladu příkazu InsertCommand a DeleteCommand. Všechny ostatní operace prováděné v tomto příkladu by nám již měli být po shlédnutí předchozích dílů známé.

Nepochybuji o tom, že se po spuštění tohoto příkladu mnozí z Vás podiví, že se při druhém výpisu obsahu objektu DataTable objeví námi přidaný záznam bez hodnoty ve sloupci ID. Je tomu tak, protože není při vytváření schématu objektu DataTable při vykonávání metody Fill sloupec nastaven na automatickou inkrementaci. A i když je po přidání záznamu do objektu DataTable následně zavolána metoda Update na adaptéru, data s novým ID, které je vygenerováno na úrovní databáze, nejsou do objektu DataTable promítnuta.

Bylo by poměrně rozumné v metodě Synchronize našeho příkladu opět pomocí metody Fill načíst data z datového zdroje. A to nejen za účelem získání hodnoty sloupce ID u nově přidaných záznamů, ale i pro případ, že nám nějaké záznamy v databázi během našich úprav přibyli výsledkem činnosti jiných uživatelů/aplikací. Takže upravíme metodu Synchronize našeho příkladu do následující podoby.

public void Synchronize()
{
  if (DataSet.Tables.Count != 0)
  {
    adapter.Update(DataSet, "employees");
    //vymazani vsech zaznamu
    DataSet.Clear();
    //opetovne naplneni
    adapter.Fill(DataSet, "employees");
  }
}

Kromě zavolání metody Fill je v nové verzi metody volána ještě metoda Clear, která zapříčiní vymazání všech řádků ve všech tabulkách, které jsou obsaženy v DataSetu. Kdybychom tuto metodu před zavoláním metody Fill nepoužili, byla by všechna data z databázové tabulky přidána do tabulky v DataSetu, takže by tam některé záznamy byly dvakrát.

Zdrojové kódy k příkladům naleznete zde.

Diskuze (5) Další článek: Pohled pod pokličku Intel iMac Core Duo

Témata článku: , , , , , , , , , , , , , , ,