Sdílet prostřednictvím


LINQ to SQL: Dotaz Language-Integrated .NET pro relační data

 

Dinesh Kulkarni, Luca Bolognese, Matt Warren, Anders Hejlsberg, Kit George

březen 2007

Platí pro:
   Visual Studio Code Name "Orcas"
   .NET Framework 3.5

Shrnutí: LINQ to SQL poskytuje infrastrukturu modulu runtime pro správu relačních dat jako objektů bez ztráty možnosti dotazování. Aplikace může volně manipulovat s objekty, zatímco LINQ to SQL zůstane na pozadí a automaticky sleduje vaše změny. (119 tištěných stránek)

Obsah

Úvod
Rychlá prohlídka
   Vytváření tříd entit
   The DataContext
   Definování relací
   Dotazování napříč relacemi
   Úprava a ukládání entit
In-Depth dotazů
   Provádění dotazů
   Identita objektu
   Relace
   Spojení
   Projekce
   Kompilované dotazy
   Překlad SQL
Životní cyklus entity
   Sledování změn
   Odesílání změn
   Souběžné změny
   Transakce
   Uložené procedury
In-Depth tříd entit
   Používání atributů
   Konzistence grafů
   Oznámení o změnách
   Dědičnost
Pokročilá témata
   Vytváření databází
   Spolupráce s ADO.NET
   Řešení konfliktů změn
   Vyvolání uložených procedur
   Nástroj generátoru tříd entit
   Referenční dokumentace k nástroji generátoru DBML
   Vícevrstvé entity
   Externí mapování
   Podpora funkcí a poznámky k rozhraní NET Framework
   Podpora ladění

Úvod

Většina dnešních programů manipuluje s daty tak či onak a často jsou tato data uložena v relační databázi. Mezi moderními programovacími jazyky a databázemi je ale obrovský rozdíl v tom, jak představují informace a jak s nimi manipulují. Tato neshoda impedance je viditelná několika způsoby. Nejdůležitější je, že programovací jazyky přistupují k informacím v databázích prostřednictvím rozhraní API, která vyžadují zadání dotazů jako textových řetězců. Tyto dotazy představují významnou část logiky programu. Přesto jsou pro jazyk neprůsadné a nemůžou využívat výhody z ověřování v době kompilace a funkcí návrhu, jako je IntelliSense.

Rozdíly jsou samozřejmě mnohem hlubší. Způsob reprezentace informací – datový model – se mezi těmito dvěma typy zcela liší. Moderní programovací jazyky definují informace ve formě objektů. Relační databáze používají řádky. Objekty mají jedinečnou identitu, protože každá instance se fyzicky liší od jiné. Řádky jsou identifikovány hodnotami primárního klíče. Objekty obsahují odkazy, které identifikují a propojují instance. Řádky jsou ponechány záměrně odlišné, což vyžaduje, aby související řádky byly volně svázány pomocí cizích klíčů. Objekty stojí samostatně, existují, dokud na nich stále odkazuje jiný objekt. Řádky existují jako prvky tabulek a po odebrání zmizí.

Není divu, že aplikace, u kterých se očekává, že tento rozdíl překlenou, se obtížně sestavují a udržují. Určitě by to zjednodušilo rovnici, jak se zbavit jedné nebo druhé strany. Relační databáze ale poskytují kritickou infrastrukturu pro dlouhodobé ukládání a zpracování dotazů a moderní programovací jazyky jsou nepostradatelné pro agilní vývoj a bohaté výpočty.

Až dosud bylo úkolem vývojáře aplikace vyřešit tuto neshodu v každé aplikaci zvlášť. Dosud nejlepšími řešeními byly propracované vrstvy abstrakce databáze, které převážejí informace mezi objektovými modely specifických pro danou doménu aplikací a tabulkovou reprezentací databáze, přičemž data se mění a přeformátují. Zatemněním skutečného zdroje dat však tato řešení nakonec zahodí nejpůsobivější funkci relačních databází; možnost dotazování na data.

LINQ to SQL, součást editoru Visual Studio Code Name "Orcas", poskytuje infrastrukturu za běhu pro správu relačních dat jako objektů, aniž by došlo ke ztrátě možnosti dotazování. Dělá to tak, že přeloží dotazy integrované s jazykem do SQL pro spuštění databází a pak přeloží tabulkové výsledky zpět do objektů, které definujete. Aplikace pak může volně manipulovat s objekty, zatímco LINQ to SQL zůstane na pozadí a automaticky sleduje vaše změny.

  • LINQ to SQL je navržený tak, aby nepřerušil vaši aplikaci.
    • Aktuální řešení ADO.NET je možné migrovat do LINQ to SQL postupně (sdílení stejných připojení a transakcí), protože LINQ to SQL je jednoduše další komponentou v rodině ADO.NET. LINQ to SQL má také rozsáhlou podporu uložených procedur, což umožňuje opakované použití stávajících podnikových prostředků.
  • LINQ to SQL aplikací je snadné začít.
    • Objekty propojené s relačními daty mohou být definovány stejně jako normální objekty, pouze zdobené atributy, které identifikují, jak vlastnosti odpovídají sloupcům. Samozřejmě, že to není ani nutné dělat ručně. K dispozici je nástroj pro návrh, který automatizuje převod existujících schémat relačních databází do definic objektů.

Společně LINQ to SQL infrastrukturou za běhu a nástroji návrhu výrazně snižují zatížení pro vývojáře databázových aplikací. Následující kapitoly poskytují přehled o tom, jak lze LINQ to SQL použít k provádění běžných úloh souvisejících s databází. Předpokládá se, že čtenář zná Language-Integrated Query a standardní operátory dotazů.

LINQ to SQL je jazykově nezávislý. Jakýkoli jazyk vytvořený tak, aby poskytoval Language-Integrated Query, ho může použít k povolení přístupu k informacím uloženým v relačních databázích. Ukázky v tomto dokumentu jsou zobrazeny v jazyce C# a Visual Basic; LINQ to SQL lze použít také s verzí kompilátoru jazyka Visual Basic s podporou LINQ.

Rychlá prohlídka

Prvním krokem při vytváření aplikace LINQ to SQL je deklarování tříd objektů, které budete používat k reprezentaci dat aplikace. Pojďme si projít příklad.

Vytváření tříd entit

Začneme jednoduchou třídou Customer a přidružíme ji k tabulce customers v ukázkové databázi Northwind. K tomu stačí použít pouze vlastní atribut na začátek deklarace třídy. LINQ to SQL definuje atribut Table pro tento účel.

C#

[Table(Name="Customers")]
public class Customer
{
   public string CustomerID;
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   Public CustomerID As String
   Public City As String
End Class

Atribut Table má vlastnost Name , kterou můžete použít k určení přesného názvu databázové tabulky. Pokud není zadána žádná vlastnost Name, LINQ to SQL předpokládá, že tabulka databáze má stejný název jako třída. V databázi budou uloženy pouze instance tříd deklarovaných jako tabulky. Instance těchto typů tříd se označují jako entity. Samotné třídy se označují jako třídy entit.

Kromě přidružení tříd k tabulkám budete muset označit každé pole nebo vlastnost, které chcete přidružit ke sloupci databáze. Za tímto způsobem LINQ to SQL definuje atribut Column.

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(IsPrimaryKey=true)]
   public string CustomerID;
   [Column]
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   <Column(IsPrimaryKey:=true)> _
   Public CustomerID As String

   <Column> _
   Public City As String

End Class

Atribut Column má řadu vlastností, které můžete použít k přizpůsobení přesného mapování mezi poli a sloupci databáze. Jednou z vlastností, které je třeba si všimnout, je vlastnost Id . Informuje LINQ to SQL, že sloupec databáze je součástí primárního klíče v tabulce.

Stejně jako u atributu Table stačí zadat informace v atributu Column pouze v případě, že se liší od toho, co lze odvodit z vašeho pole nebo deklarace vlastnosti. V tomto příkladu potřebujete LINQ to SQL sdělit, že pole CustomerID je součástí primárního klíče v tabulce, ale nemusíte zadávat přesný název nebo typ.

Pouze pole a vlastnosti deklarované jako sloupce budou zachovány do databáze nebo načteny z databáze. Jiné budou považovány za přechodné části logiky aplikace.

The DataContext

DataContext je hlavní konduit, pomocí kterého načítáte objekty z databáze a znovu odešlete změny. Použijete ho stejným způsobem jako připojení ADO.NET. Ve skutečnosti se dataContext inicializuje pomocí připojení nebo připojovacího řetězce, který zadáte. Účelem objektu DataContext je přeložit požadavky na objekty do dotazů SQL provedených vůči databázi a následně sestavit objekty z výsledků. DataContext umožňuje dotaz integrovaný do jazyka implementací stejného vzoru operátoru jako standardní operátory dotazů, jako jsou Where a Select.

DataContext můžete například použít k načtení objektů zákazníka, jejichž město je Londýn, následujícím způsobem:

C#

// DataContext takes a connection string 
DataContext db = new   DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q =
   from c in Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);

Visual Basic

' DataContext takes a connection string 
Dim db As DataContext  = New DataContext("c:\northwind\northwnd.mdf")
' Get a typed table to run queries
Dim Customers As Customers(Of Customer) = db.GetTable(Of Customer)()
' Query for customers from London
Dim londonCustomers = From customer in Customers _
                      Where customer.City = "London" _
                      Select customer
For Each cust in londonCustomers
   Console.WriteLine("id = " & cust.CustomerID & ", City = " & cust.City)
Next

Každá tabulka databáze je reprezentovaná jako kolekce Table , která je přístupná prostřednictvím metody GetTable() pomocí třídy entity, která ji identifikuje. Místo spoléhání na základní třídu DataContext a metodu GetTable() doporučujeme deklarovat dataContext se silnými typy. DataContext silného typu deklaruje všechny kolekce tabulky jako členy kontextu.

C#

public partial class Northwind : DataContext
{
   public Table<Customer> Customers;
   public Table<Order> Orders;
   public Northwind(string connection): base(connection) {}
}

Visual Basic

Partial Public Class Northwind 
              Inherits DataContext

   Public Customers As Table(Of Customers)
   Public Orders As Table(Of Orders)
         Public Sub New(ByVal connection As String)
            MyBase.New(connection)
   End Sub
End Class

Dotaz na zákazníky z Londýna se pak dá vyjádřit jednodušeji takto:

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}",cust.CustomerID, cust.City);

Visual Basic

Dim db = New Northwind("c:\northwind\northwnd.mdf")
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City) 

Next

Ve zbývající části dokumentu přehledu budeme dál používat třídu Northwind se silnými typy.

Definování relací

Relace v relačních databázích se obvykle modelují jako hodnoty cizího klíče odkazující na primární klíče v jiných tabulkách. Pokud mezi nimi chcete přecházet, musíte tyto dvě tabulky explicitně spojit dohromady pomocí operace relačního spojení. Objekty naopak odkazují na sebe navzájem pomocí odkazů na vlastnosti nebo kolekcí odkazů, na které se naviguje pomocí zápisu "tečka". Je zřejmé, že tečkování je jednodušší než připojování, protože při každé navigaci si nemusíte vzpomenout na explicitní podmínku spojení.

U datových relací, jako jsou tyto, které budou vždy stejné, je docela vhodné je zakódovat jako odkazy na vlastnosti ve třídě entity. LINQ to SQL definuje atribut přidružení, který můžete použít na člena, který se používá k reprezentaci relace. Přidružovací relace je relace cizího klíče k primárnímu klíči, která se provádí porovnáváním hodnot sloupců mezi tabulkami.

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(Id=true)]
   public string CustomerID;
   ...
   private EntitySet<Order> _Orders;
   [Association(Storage="_Orders", OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer

   <Column(Id:=true)> _
   Public CustomerID As String
   ...
   Private _Orders As EntitySet(Of Order)
                  <Association(Storage:="_Orders", OtherKey:="CustomerID")> _
         Public Property Orders() As EntitySet(Of Order)
            Get
               Return Me._Orders
            End Get
            Set(ByVal value As EntitySet(Of Order))
            End Set
   End Property

End Class

Třída Customer teď má vlastnost, která deklaruje vztah mezi zákazníky a jejich objednávkami. Vlastnost Orders je typu EntitySet , protože relace je 1:N. K popisu způsobu, jakým se toto přidružení provádí, používáme vlastnost OtherKey v atributu Association . Určuje názvy vlastností v související třídě, která se má porovnat s touto třídou. Byla zde také vlastnost ThisKey , která nebyla zadána. Normálně bychom ho použili k výpisu členů na této straně relace. Jeho vynecháním ale LINQ to SQL umožníme, aby je odvodili od členů, kteří tvoří primární klíč.

Všimněte si, jak je tato situace obrácena v definici třídy Order .

C#

[Table(Name="Orders")]
public class Order
{
   [Column(Id=true)]
   public int OrderID;
   [Column]
   public string CustomerID;
   private EntityRef<Customer> _Customer;    
   [Association(Storage="_Customer", ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value; }
   }
}

Visual Basic

<Table(Name:="Orders")> _
Public Class Order

   <Column(Id:=true)> _
   Public OrderID As String
   <Column> _
   Public CustomerID As String
   Private _Customer As EntityRef(Of Customer)
         <Association(Storage:="_Customer", ThisKey:="CustomerID")> _
         Public Property Customer() As Customer
            Get
               Return Me._Customer.Entity
            End Get
            Set(ByVal value As Customer)
               Me._Customers.Entity = value
            End Set
   End Property
End Class

Třída Order používá typ EntityRef k popisu vztahu zpět zákazníkovi. Použití třídy EntityRef je vyžadováno pro podporu odloženého načítání (probíráme dále). Atribut Association pro vlastnost Customer určuje vlastnost ThisKey , protože členové, které se nedají odvodit, jsou nyní na této straně relace.

Podívejte se také na vlastnost Storage . Informuje LINQ to SQL, který soukromý člen se používá k uchování hodnoty vlastnosti. To LINQ to SQL umožní obejít přístupové objekty veřejných vlastností při ukládání a načítání jejich hodnot. To je nezbytné, pokud chcete LINQ to SQL, aby se zabránilo zapisování vlastní obchodní logiky do přístupových objektů. Pokud není vlastnost úložiště zadaná, použijí se místo toho veřejné přístupové objekty. Vlastnost Storage můžete použít také s atributy Column .

Jakmile do tříd entit zavednete relace, množství kódu, který potřebujete napsat, roste s tím, jak zavádíte podporu oznámení a konzistenci grafů. Naštěstí existuje nástroj (popsaný dále), který lze použít ke generování všech potřebných definic jako částečných tříd, což vám umožní použít kombinaci vygenerovaného kódu a vlastní obchodní logiky.

Ve zbývající části tohoto dokumentu předpokládáme, že se nástroj použil k vygenerování kompletního kontextu dat Northwind a všech tříd entit.

Dotazování napříč relacemi

Když teď máte relace, můžete je použít při psaní dotazů jednoduše odkazem na vlastnosti relace definované ve vaší třídě.

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonCustOrders = From cust In db.Customers, ord In cust.Orders _
                       Where cust.City = "London" _
                       Select Customer = cust, Order = ord

Výše uvedený dotaz používá vlastnost Orders k vytvoření křížového produktu mezi zákazníky a objednávkami a vytvoří novou posloupnost dvojic Zákazník a Objednávka .

Je také možné postupovat opačně.

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select new { c = o.Customer, o };

Visual Basic

Dim londonCustOrders = From ord In db.Orders _
                       Where ord.Customer.City = "London" _
                       Select Customer = ord.Customer, Order = ord

V tomto příkladu se dotazují objednávky a pro přístup k informacím o přidruženém objektu Zákazník se používá vztah se zákazníkem.

Úpravy a ukládání entit

Málo aplikací je sestaveno pouze s ohledem na dotazy. Je také nutné vytvořit a upravit data. LINQ to SQL je navržená tak, aby poskytovala maximální flexibilitu při manipulaci a zachování změn provedených v objektech. Jakmile jsou objekty entit k dispozici – buď jejich načtením prostřednictvím dotazu nebo novým sestavením – můžete s nimi manipulovat jako s normálními objekty ve vaší aplikaci, měnit jejich hodnoty nebo je přidávat a odebírat z kolekcí podle potřeby. LINQ to SQL sleduje všechny vaše změny a je připravena je přenést zpět do databáze, jakmile budete hotovi.

Následující příklad používá třídy Customer a Order vygenerované nástrojem z metadat celé ukázkové databáze Northwind. Definice tříd nebyly zobrazeny kvůli stručnosti.

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// Query for a specific customer
string id = "ALFKI";
var cust = db.Customers.Single(c => c.CustomerID == id);
// Change the name of the contact
cust.ContactName = "New Contact";
// Create and add a new Order to Orders collection
Order ord = new Order { OrderDate = DateTime.Now };
cust.Orders.Add(ord);
// Ask the DataContext to save all the changes
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                     Where cust.CustomerID = id).First
' Change the name of the contact
targetCustomer.ContactName = "New Contact"
' Create and add a new Order to Orders collection
Dim id = New Order With { .OrderDate = DateTime.Now }
targetCustomer.Orders.Add(ord)
' Ask the DataContext to save all the changes
db.SubmitChanges()

Při volání Metody SubmitChanges() LINQ to SQL automaticky vygeneruje a spustí příkazy SQL, aby se změny přenesly zpět do databáze. Toto chování je také možné přepsat vlastní logikou. Vlastní logika může volat uloženou proceduru databáze.

In-Depth dotazů

LINQ to SQL poskytuje implementaci standardních operátorů dotazu pro objekty přidružené k tabulkám v relační databázi. Tato kapitola popisuje LINQ to SQL specifické aspekty dotazů.

Provádění dotazů

Ať už dotaz napíšete jako výraz dotazu vysoké úrovně nebo sestavíte z jednotlivých operátorů, dotaz, který napíšete, není imperativním příkazem, který se spustí okamžitě. Je to popis. Například v deklaraci níže odkazuje místní proměnná q na popis dotazu, nikoli na výsledek jeho spuštění.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
For Each cust  In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next

Skutečný typ q v této instanci je IQueryable<Customer>. Dokud se aplikace nepokusí vytvořit výčet obsahu dotazu, znamená to, že se dotaz skutečně provede. V tomto příkladu příkaz foreach způsobí spuštění.

Objekt IQueryable je podobný objektu ADO.NET příkaz. Když ho máte po ruce, neznamená to, že byl dotaz proveden. Objekt příkazu uchovává řetězec, který popisuje dotaz. Podobně objekt IQueryable uchovává popis dotazu kódovaného jako datovou strukturu označovanou jako expression. Objekt příkazu má metodu ExecuteReader(), která způsobuje spuštění a vrací výsledky jako DataReader. Objekt IQueryablemetodu GetEnumerator(), která způsobuje spuštění a vrací výsledky jako zákazník> IEnumerator<.

Proto z toho vyplývá, že pokud je dotaz výčtu dvakrát, provede se dvakrát.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute first time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);
// Execute second time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute first time
For Each cust In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next
' Execute second time
For Each cust In londonCustomers
   Console.WriteLine(cust.CustomerID) 
Next

Toto chování se označuje jako odložené spuštění. Stejně jako u objektu příkazu ADO.NET je možné dotaz podržet a znovu ho spustit.

Autoři aplikací samozřejmě často musí být velmi explicitní v tom, kde a kdy se dotaz spustí. Bylo by neočekávané, kdyby aplikace spustila dotaz vícekrát, jednoduše proto, že musela prozkoumat výsledky více než jednou. Můžete například chtít svázat výsledky dotazu s něčím, jako je DataGrid. Ovládací prvek může při každém malování na obrazovce zobrazit výčet výsledků.

Pokud se chcete vyhnout vícenásobnému spuštění, převeďte výsledky na libovolný počet standardních tříd kolekce. Výsledky lze snadno převést na seznam nebo pole pomocí standardních operátorů dotazu ToList() nebo ToArray().

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute once using ToList() or ToArray()
Dim londonCustList = londonCustomers.ToList()
' Neither of these iterations re-executes the query
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

Jednou z výhod odloženého spuštění je to, že dotazy mohou být po částech vytvořeny tak, že k provedení dochází pouze v době, kdy je konstrukce dokončena. Můžete začít vytvářet část dotazu, přiřazovat ho k místní proměnné a později pro něj používat další operátory.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
if (orderByLocation) {
   q =
      from c in q
      orderby c.Country, c.City
      select c;
}
else if (orderByName) {
   q =
      from c in q
      orderby c.ContactName
      select c;
}
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
if orderByLocation Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.Country, cust.City

Else If orderByName Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.ContactName
End If
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

V tomto příkladu q začíná jako dotaz pro všechny zákazníky v Londýně. Později se změní na seřazený dotaz v závislosti na stavu aplikace. Odložením provádění je možné vytvořit dotaz tak, aby vyhovoval přesným potřebám aplikace, aniž by to vyžadovalo rizikovou manipulaci s řetězci.

Identita objektu

Objekty v modulu runtime mají jedinečnou identitu. Pokud dvě proměnné odkazují na stejný objekt, ve skutečnosti odkazují na stejnou instanci objektu. Z tohoto důvodu jsou změny provedené prostřednictvím cesty přes jednu proměnnou okamžitě viditelné prostřednictvím druhé proměnné. Řádky v tabulce relační databáze nemají jedinečnou identitu. Mají ale primární klíč a tento primární klíč může být jedinečný, což znamená, že stejný klíč nesmí sdílet dva řádky. Tím se ale omezuje jenom obsah databázové tabulky. Pokud tedy s daty pracujeme jenom pomocí vzdálených příkazů, je to přibližně totéž.

To se ale stává jen zřídka. Data se nejčastěji přenesou z databáze do jiné vrstvy, kde s nimi aplikace manipuluje. Je zřejmé, že to je model, který LINQ to SQL podporuje. Když jsou data přenesena z databáze jako řádky, nelze očekávat, že dva řádky představující stejná data ve skutečnosti odpovídají stejným instancím řádků. Pokud se na konkrétního zákazníka dotazujete dvakrát, získáte dva řádky dat, z nichž každý obsahuje stejné informace.

U objektů ale očekáváte něco úplně jiného. Očekáváte, že když znovu požádáte DataContext o stejné informace, ve skutečnosti vám vrátí stejnou instanci objektu. To očekáváte, protože objekty mají pro vaši aplikaci zvláštní význam a očekáváte, že se budou chovat jako normální objekty. Navrhli jste je jako hierarchie nebo grafy a určitě očekáváte, že je jako takové načtete bez hord replikovaných instancí jenom proto, že jste o stejnou věc žádali dvakrát.

Z tohoto důvodu DataContext spravuje identitu objektu. Pokaždé, když se z databáze načte nový řádek, zaprotokoluje se do tabulky identit pomocí primárního klíče a vytvoří se nový objekt. Pokaždé, když se stejný řádek znovu načte, je původní instance objektu předána zpět aplikaci. Tímto způsobem překládá DataContext koncept identity databází (klíče) do konceptu jazyků (instance). Aplikace vidí objekt pouze ve stavu, kdy byl poprvé načten. Nová data, pokud se liší, se zahodí.

Možná vás to zaráží, protože proč by nějaká aplikace vyhazovala data? Ukázalo se, že tímto způsobem LINQ to SQL spravuje integritu místních objektů a dokáže podporovat optimistické aktualizace. Vzhledem k tomu, že jediné změny, ke kterým dojde po počátečním vytvoření objektu, jsou změny provedené aplikací, je záměr aplikace jasný. Pokud dojde ke změnám provedené externí stranou v mezidobí, budou identifikovány při zavolání metody SubmitChanges(). Další informace najdete v části Souběžné změny.

Všimněte si, že v případě, že databáze obsahuje tabulku bez primárního klíče, LINQ to SQL umožňuje odesílat dotazy přes tabulku, ale neumožňuje aktualizace. Je to proto, že vzhledem k chybějícímu jedinečnému klíči rozhraní nedokáže určit, který řádek má aktualizovat.

Pokud je objekt požadovaný dotazem snadno identifikovatelný podle primárního klíče, protože už je načtený, žádný dotaz se nespustí vůbec. Tabulka identit funguje jako mezipaměť, do které se ukládají všechny dříve načtené objekty.

Relace

Jak jsme viděli v rychlé prohlídce, odkazy na jiné objekty nebo kolekce jiných objektů v definicích tříd přímo odpovídají relacím cizích klíčů v databázi. Tyto relace můžete použít při dotazování jednoduchým použitím zápisu s tečkou pro přístup k vlastnostem relace a přechodu z jednoho objektu na jiný. Tyto operace přístupu se překládají na složitější spojení nebo korelované dílčí dotazy v ekvivalentním SQL, což vám umožní procházet graf objektů během dotazu. Následující dotaz například přejde z objednávek na zákazníky jako způsob, jak omezit výsledky pouze na ty objednávky pro zákazníky se sídlem v Londýně.

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select o;

Visual Basic

Dim londonOrders = From ord In db.Orders _
                       where ord.Customer.City = "London"

Pokud by vlastnosti relace neexistovaly, museli byste je ručně zapsat jako spojení stejně jako v dotazu SQL.

C#

var q =
   from c in db.Customers
   join o in db.Orders on c.CustomerID equals o.CustomerID
   where c.City == "London"
   select o;

Visual Basic

Dim londonOrders = From cust In db.Customers _
                            Join ord In db.Orders _
                            On cust.CustomerID Equals ord.CustomerID _
                   Where ord.Customer.City = "London" _
                   Select ord

Vlastnost relace umožňuje definovat tuto konkrétní relaci, jakmile povolíte použití praktičtější syntaxe tečky. To ale není důvod, proč existují vlastnosti relace. Existují, protože máme tendenci definovat objektové modely specifické pro doménu jako hierarchie nebo grafy. Objekty, které se rozhodnete programovat, mají odkazy na jiné objekty. Je to jen šťastná náhoda, že vzhledem k tomu, že relace mezi objekty odpovídají relacím cizích klíčů v databázích, přístup k vlastnostem vede k pohodlnému způsobu zápisu spojení.

Proto je existence vlastností relace důležitější na straně výsledků dotazu než v rámci samotného dotazu. Jakmile budete mít ruce na konkrétního zákazníka, jeho definice třídy vám řekne, že zákazníci mají objednávky. Když se tedy podíváte na vlastnost Orders konkrétního zákazníka, očekáváte, že se kolekce zobrazí naplněná všemi objednávkami zákazníka, protože to je ve skutečnosti smlouva, kterou jste deklarovali definováním tříd tímto způsobem. Očekáváte, že tam uvidíte objednávky i v případě, že jste o objednávky předem nepožádali. Očekáváte, že si váš objektový model zachová iluzi, že se jedná o rozšíření databáze v paměti a související objekty jsou okamžitě dostupné.

LINQ to SQL implementuje metodu označovanou jako odložené načítání, aby tuto iluzi udržela. Při dotazování na objekt ve skutečnosti načítáte pouze objekty, které jste požadovali. Související objekty se nenačítají automaticky ve stejnou dobu. Skutečnost, že související objekty ještě nejsou načteny, však není pozorovatelná, protože jakmile se pokusíte o přístup k nim, odešle se žádost o jejich načtení.

C#

var q =
   from o in db.Orders
   where o.ShipVia == 3
   select o;
foreach (Order o in q) {
   if (o.Freight > 200)
      SendCustomerNotification(o.Customer);
   ProcessOrder(o);
}

Visual Basic

Dim shippedOrders = From ord In db.Orders _
                    where ord.ShipVia = 3
For Each ord In shippedOrders
   If ord.Freight > 200 Then
      SendCustomerNotification(ord.Customer) 
      ProcessOrder(ord)
   End If
Next

Můžete se například chtít dotazovat na konkrétní sadu objednávek a pak jenom občas poslat e-mailové oznámení konkrétním zákazníkům. Při každé objednávce byste nemuseli načítat všechna zákaznická data předem. Odložené načítání vám umožní odložit náklady na načtení dodatečných informací, dokud to nebude nutné.

Může to být samozřejmě i naopak. Můžete mít aplikaci, která potřebuje současně prohlížet data o zákaznících a objednávkách. Víte, že potřebujete obě sady dat. Víte, že vaše aplikace přejde k podrobnostem objednávek jednotlivých zákazníků, jakmile je získáte. Bylo by nešťastné vyhazovat jednotlivé dotazy na objednávky pro každého zákazníka. Ve skutečnosti chcete, aby se data objednávek načetla společně se zákazníky.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q) {
   foreach (Order o in c.Orders) {
      ProcessCustomerOrder(o);
   }
}

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                   Where cust.City = "London"
For Each cust In londonCustomers
   For Each ord In cust.Orders
      ProcessCustomerOrder(ord) 
   End If
Next

Samozřejmě můžete vždy najít způsob, jak spojit zákazníky a objednávky dohromady v dotazu tak, že vytvoříte křížový produkt a načte všechny relativní části dat jako jednu velkou projekci. Výsledky by ale nebyly entitami. Entity jsou objekty s identitou, které můžete upravit, zatímco výsledky by byly projekce, které nelze změnit a trvale zachovat. V horším případě byste načítali velké množství redundantních dat, protože každý zákazník opakuje pro každou objednávku ve výstupu zploštěného spojení.

Co opravdu potřebujete, je způsob, jak současně načíst sadu souvisejících objektů – vytyčených částí grafu, abyste nikdy nenačítali více nebo míň, než bylo nutné pro zamýšlené použití.

LINQ to SQL umožňuje požádat o okamžité načtení oblasti objektového modelu právě z tohoto důvodu. Dělá to tak, že povoluje specifikaci DataShape pro DataContext. DataShape Třída slouží k instrukci rozhraní o tom, které objekty se mají načíst při načtení konkrétního typu. Toho se dosahuje pomocí metody LoadWith , jak je znázorněno v následujícím příkladu:

C#

DataShape ds = new DataShape();
ds.LoadWith<Customer>(c => c.Orders);
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim ds As DataShape = New DataShape()
ds.LoadWith(Of Customer)(Function(c As Customer) c.Orders)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust

V předchozím dotazu se při spuštění dotazu načtou všechny objednávky pro všechny zákazníky , kteří žijí v Londýně, takže následný přístup k vlastnosti Orders u objektu Customer neaktivuje databázový dotaz.

Třídu DataShape lze také použít k určení dílčích dotazů, které jsou použity pro navigaci v relaci. Pokud například chcete načíst pouze objednávky , které byly odeslány dnes, můžete použít metodu AssociateWith v datovém obrazci jako v následujícím příkladu:

C#

DataShape ds = new DataShape();
ds.AssociateWith<Customer>(
   c => c.Orders.Where(p => p.ShippedDate != DateTime.Today));
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;
foreach(Customer c in q) {
   foreach(Order o in c.Orders) {}
}

Visual Basic

Dim ds As DataShape = New DataShape()
ds.AssociateWith(Of Customer)( _
         Function(cust As Customer) From cust In db.Customers _
                                 Where order.ShippedDate <> Today _
                                 Select cust)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   For Each ord In cust.Orders …
   Next
   Next

V předchozím kódu vnitřní příkaz foreach iteruje těsně nad objednávkami , které byly odeslány dnes, protože právě takové objednávky byly načteny z databáze.

Je důležité si všimnout dvou faktů o třídě DataShape :

  1. Po přiřazení objektu DataShape k objektu DataContext nelze obrazec DataShape upravit. Jakékoli volání metody LoadWith nebo AssociateWith na takové DataShape vrátí chybu za běhu.

  2. Není možné vytvořit cykly pomocí LoadWith nebo AssociateWith. Například následující kód vygeneruje chybu za běhu:

    C#

    DataShape ds = new DataShape();
    ds.AssociateWith<Customer>(
             c=>c.Orders.Where(o=> o.Customer.Orders.Count() < 35);
    

    Visual Basic

    Dim ds As DataShape = New DataShape()
    ds.AssociateWith(Of Customer)( _
             Function(cust As Customer) From ord In cust.Orders _
                          Where ord.Customer.Orders.Count() < 35)
    

Spojení

Většina dotazů na objektové modely se ve velké míře spoléhá na navigaci v odkazech na objekty v objektovém modelu. Existují však zajímavé "relace" mezi entitami, které nemusí být zachyceny v objektovém modelu jako odkazy. Například Customer.Orders je užitečná relace založená na relacích cizích klíčů v databázi Northwind. Dodavatelé a zákazníci ve stejném městě nebo zemi je ale ad hoc vztah, který není založený na vztahu cizího klíče a nemusí být zachycen v objektovém modelu. Spojení poskytují další mechanismus pro zpracování takových relací. LINQ to SQL podporuje nové operátory spojení zavedené v LINQ.

Zvažte následující problém – vyhledejte dodavatele a zákazníky ve stejném městě. Následující dotaz vrátí názvy společností dodavatelů a zákazníků a společné město jako zploštěný výsledek. Jedná se o ekvivalent vnitřního ekvi-spojení v relačních databázích:

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City
   select new {
      Supplier = s.CompanyName,
      Customer = c.CompanyName,
      City = c.City
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Join cust In db.Customers _
                              On sup.City Equals cust.City _
                        Select Supplier = sup.CompanyName, _
                        CustomerName = cust.CompanyName, _
                        City = cust.City

Výše uvedený dotaz eliminuje dodavatele, kteří nejsou ve stejném městě jako určitý zákazník. Existují však chvíle, kdy nechceme odstranit jednu z entit v ad hoc vztahu. Následující dotaz zobrazí seznam všech dodavatelů se skupinami zákazníků pro každého z dodavatelů. Pokud konkrétní dodavatel nemá žádného zákazníka ve stejném městě, výsledkem je prázdná kolekce zákazníků odpovídajících danému dodavateli. Všimněte si, že výsledky nejsou ploché – každý dodavatel má přidruženou kolekci. Tím se vlastně zajistí spojení skupin – spojí dvě sekvence a seskupí prvky druhé sekvence podle prvků první sekvence.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   select new { s, scusts };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        Customers = supCusts

Připojení ke skupině se dá rozšířit i na více kolekcí. Následující dotaz rozšiřuje výše uvedený dotaz o seznam zaměstnanců, kteří jsou ve stejném městě jako dodavatel. Výsledek tady ukazuje dodavatele s (možná prázdnými) kolekcemi zákazníků a zaměstnanců.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   join e in db.Employees on s.City equals e.City into semps
   select new { s, scusts, semps };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Group Join emp In db.Employees _
                              On sup.City Equals emp.City _
                              Into supEmps _
                        Select Supplier = sup, _
                        Customers = supCusts, Employees = supEmps

Výsledky spojení se skupinou je také možné zploštět. Výsledkem zploštělování spojení skupin mezi dodavateli a zákazníky je několik položek pro dodavatele s více zákazníky v jejich městě – jeden pro každého zákazníka. Prázdné kolekce se nahradí hodnotou null. Jedná se o ekvivalent levého vnějšího ekvi-spojení v relačních databázích.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into sc
   from x in sc.DefaultIfEmpty()
   select new {
      Supplier = s.CompanyName, 
      Customer = x.CompanyName, 
      City = x.City 
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        CustomerName = supCusts.CompanyName, sup.City

Podpisy pro podkladové operátory spojení jsou definovány v dokumentu standardních operátorů dotazů. Podporují se pouze ekvi-spojení a dva operandy rovná se musí mít stejný typ.

Projekce

Zatím jsme se dívali pouze na dotazy na načítání entit – objektů přímo přidružených k databázovým tabulkám. Nemusíme se omezovat jen na to. Výhodou dotazovacího jazyka je, že informace můžete načíst v libovolné formě. Když to uděláte, nebudete moct využít výhod automatického sledování změn ani správy identit. Můžete ale získat jenom požadovaná data.

Můžete například jednoduše potřebovat znát názvy společností všech zákazníků v Londýně. V takovém případě neexistuje žádný konkrétní důvod, proč načítat celé objekty zákazníka pouze pro výběr názvů. Názvy můžete promítnout jako součást dotazu.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c.CompanyName;

Visual Basic

Dim londonCustomerNames = From cust In db.Customer _
                          Where cust.City = "London" _
                          Select cust.CompanyName

V tomto případě se q stane dotazem, který načte posloupnost řetězců.

Pokud chcete získat zpět více než jen jeden název, ale ne tolik, aby bylo možné načíst celý objekt zákazníka, můžete zadat libovolnou podmnožinu, kterou chcete, vytvořením výsledků v rámci dotazu.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone

Tento příklad používá anonymní inicializátor objektů k vytvoření struktury, která obsahuje název společnosti i telefonní číslo. Možná nevíte, co volat typ, ale s implicitně napsanou deklaraci místní proměnné v jazyce, kterou nutně nepotřebujete.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };
foreach(var c in q)
   Console.WriteLine("{0}, {1}", c.CompanyName, c.Phone);

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone
For Each cust In londonCustomerInfo 
   Console.WriteLine(cust.CompanyName & ", " & cust.Phone) 
Next

Pokud data využíváte okamžitě, představují anonymní typy dobrou alternativu k explicitní definici tříd pro uložení výsledků dotazu.

Můžete také vytvořit křížové produkty celých objektů, i když zřídkakdy k tomu budete mít důvod.

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonOrders = From cust In db.Customer, _
                   ord In db.Orders _
                   Where cust.City = "London" _
                   Select Customer = cust, Order = ord

Tento dotaz vytvoří sekvenci dvojic objektů zákazníka a objednávky.

Projekce je také možné vytvořit v jakékoli fázi dotazu. Data můžete promítnout do nově vytvořených objektů a potom odkazovat na členy těchto objektů v následných operacích dotazu.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new {Name = c.ContactName, c.Phone} into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select Name = cust.ContactName, cust.Phone _
                  Order By Name

Dávejte si však pozor na používání parametrizovaných konstruktorů v této fázi. Je to technicky platné, ale není možné, aby LINQ to SQL sledovat, jak použití konstruktoru ovlivňuje členský stát bez pochopení skutečného kódu uvnitř konstruktoru.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType(c.ContactName, c.Phone) into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select MyType = New MyType(cust.ContactName, cust.Phone) _
                  Order By MyType.Name

Vzhledem k tomu, že LINQ to SQL pokusy o překlad dotazu na čistě relační místně definované typy objektů SQL nejsou na serveru k dispozici ke skutečnému vytvoření. Veškerá konstrukce objektů je ve skutečnosti odložena až po načtení dat zpět z databáze. Místo skutečných konstruktorů používá vygenerovaný jazyk SQL normální projekci sloupců SQL. Vzhledem k tomu, že překladač dotazů není možné pochopit, co se děje během volání konstruktoru, není schopen stanovit význam pro pole NázevmyType.

Místo toho je osvědčeným postupem vždy používat inicializátory objektů ke kódování projekcí.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType { Name = c.ContactName, HomePhone = c.Phone } into x
   orderby x.Name
   select x;

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select Contact = New With {.Name = cust.ContactName, _
                      .Phone = cust.Phone} _
                      Order By Contact.Name

Jediným bezpečným místem pro použití parametrizovaného konstruktoru je konečná projekce dotazu.

C#

var e =
   new XElement("results",
      from c in db.Customers
      where c.City == "London"
      select new XElement("customer",
         new XElement("name", c.ContactName),
         new XElement("phone", c.Phone)
      )
   );

Visual Basic

      Dim x = <results>
                  <%= From cust In db.Customers _
                      Where cust.City = "London" _
                      Select <customer>
                         <name><%= cust.ContactName %></name>
                         <phone><%= cust.Phone %></phone>
                      </customer> 
                 %>
        </results>

Pokud chcete, můžete dokonce použít propracované vnoření konstruktorů objektů, jako je tento příklad, který vytváří XML přímo z výsledku dotazu. Funguje tak dlouho, dokud se jedná o poslední projekci dotazu.

Přesto, i když jsou volání konstruktoru srozumitelná, volání místních metod nemusí být. Pokud vaše konečná projekce vyžaduje vyvolání místních metod, je nepravděpodobné, že by to LINQ to SQL mohli vyžadovat. Volání metody, která nemají známý překlad do SQL, nelze použít jako součást dotazu. Jednou z výjimek tohoto pravidla jsou volání metod, která nemají žádné argumenty závislé na proměnných dotazu. Ty se nepovažují za součást přeloženého dotazu a místo toho se považují za parametry.

Stále propracované projekce (transformace) můžou vyžadovat implementaci místní procedurální logiky. Abyste mohli v konečné projekci použít vlastní místní metody, budete muset promítnout dvakrát. První projekce extrahuje všechny datové hodnoty, na které budete muset odkazovat, a druhá projekce provede transformaci. Mezi těmito dvěma projekcemi je volání operátoru AsEnumerable(), který přesune zpracování v daném okamžiku z LINQ to SQL dotazu na místně spuštěný dotaz.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.ContactName, c.Phone };
var q2 =
   from c in q.AsEnumerable()
   select new MyType {
      Name = DoNameProcessing(c.ContactName),
      Phone = DoPhoneProcessing(c.Phone)
   };

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select cust.ContactName, cust.Phone

Dim processedCustomers = From cust In londonCustomers.AsEnumerable() _
                         Select Contact = New With { _
                         .Name = DoNameProcessing(cust.ContactName), _
                         .Phone = DoPhoneProcessing(cust.Phone)}

Poznámka Operátor AsEnumerable() na rozdíl od ToList() a ToArray() nezpůsobí spuštění dotazu. Je to stále odloženo. Operátor AsEnumerable() pouze změní statické psaní dotazu a převede IQueryable<T> (IQueryable (ofT) v jazyce Visual Basic na IEnumerable<T> (IEnumerable (ofT) v jazyce Visual Basic a vyláká kompilátor tak, aby se zbytek dotazu zacházel jako s místně spuštěným.

Kompilované dotazy

V mnoha aplikacích je běžné provádět strukturálně podobné dotazy mnohokrát. V takových případech je možné zvýšit výkon tím, že dotaz jednou kompiluje a několikrát ho v aplikaci spustí s různými parametry. Tento výsledek je získán v LINQ to SQL pomocí CompiledQuery třídy. Následující kód ukazuje, jak definovat zkompilovaný dotaz:

C#

static class Queries
{
   public static Func<Northwind, string, IQueryable<Customer>>
      CustomersByCity = CompiledQuery.Compile((Northwind db, string city) =>
         from c in db.Customers where c.City == city select c);
}

Visual Basic

Class Queries
   public Shared Function(Of Northwind, String, IQueryable(Of Customer)) _      CustomersByCity = CompiledQuery.Compile( _
                Function(db As Northwind, city As String) _
                From cust In db.Customers Where cust.City = city)
End Class

Metoda Compile vrátí delegáta, který je možné uložit do mezipaměti a spustit poté několikrát jen změnou vstupních parametrů. Následující kód ukazuje příklad tohoto:

C#

public IEnumerable<Customer> GetCustomersByCity(string city) {
         Northwind db = new Northwind();
         return Queries.CustomersByCity(myDb, city);
}

Visual Basic

Public Function GetCustomersByCity(city As String) _ 
               As IEnumerable(Of Customer)
         Dim db As Northwind = New Northwind()
         Return Queries.CustomersByCity(myDb, city)
End Function

Překlad SQL

LINQ to SQL ve skutečnosti neprovádí dotazy, ale relační databáze. LINQ to SQL přeloží dotazy, které jste napsali, na ekvivalentní dotazy SQL a odešle je na server ke zpracování. Vzhledem k tomu, že je spuštění odložené, LINQ to SQL může prozkoumat celý dotaz, i když je sestavený z více částí.

Vzhledem k tomu, že relační databázový server ve skutečnosti nespouštějí il (kromě integrace CLR v SQL Server 2005), dotazy se na server nepřenesou jako IL. Ve skutečnosti se přenášejí jako parametrizované dotazy SQL v textové podobě.

SQL – dokonce i T-SQL s integrací CLR – samozřejmě nedokáže spustit různé metody, které jsou pro váš program místně dostupné. Proto musí být dotazy, které napíšete, přeloženy do ekvivalentních operací a funkcí, které jsou k dispozici v prostředí SQL.

Většina metod a operátorů v předdefinovaných typech rozhraní .Net Framework má přímé překlady do SQL. Některé se dají vytvořit z dostupných funkcí. Ty, které nelze přeložit, jsou zakázány. Pokud se je pokusíte použít, vygenerují se výjimky za běhu. Dále v dokumentu je část, která podrobně popisuje metody architektury, které jsou implementovány k překladu do SQL.

Životní cyklus entity

LINQ to SQL je více než jen implementace standardních operátorů dotazů pro relační databáze. Kromě překladu dotazů se jedná o službu, která spravuje vaše objekty po celou dobu jejich životnosti a pomáhá vám při zachování integrity dat a automatizaci procesu převodu úprav zpět do úložiště.

V typickém scénáři se objekty načítají prostřednictvím jednoho nebo více dotazů a pak se s nimi nějakým způsobem manipuluje, dokud aplikace nebude připravena odeslat změny zpět na server. Tento proces se může několikrát opakovat, dokud aplikace tyto informace už nebude používat. V tomto okamžiku jsou objekty uvolněny modulem runtime stejně jako normální objekty. Data však zůstávají v databázi. Objekty představující stejná data je možné načíst i po vymazání ze své existence za běhu. V tomto smyslu existuje skutečná životnost objektu nad rámec jakéhokoli jediného manifestu za běhu.

Tato kapitola se zaměřuje na životní cyklus entity , kde cyklus odkazuje na časové rozmezí jednoho manifestace objektu entity v konkrétním kontextu běhu. Cyklus začíná, když se DataContext dozví o nové instanci, a končí, když objekt nebo DataContext již není potřeba.

Sledování změn

Po načtení entit z databáze s nimi můžete podle potřeby manipulovat. Jsou to vaše objekty; používejte je, jak chcete. Při tom LINQ to SQL sledovat změny, aby je bylo možné při zavolání metody SubmitChanges() uložit do databáze.

LINQ to SQL začne sledovat entity v okamžiku, kdy se načtou z databáze, než na ně položíte ruce. Služba správy identit , o které jsme se bavili dříve, už se samozřejmě také nastartovala. Náklady na sledování změn jsou navíc velmi malé, dokud se skutečně nepustíte do provádění změn.

C#

Customer cust = db.Customers.Single(c => c.CustomerID == "ALFKI");
cust.CompanyName = "Dr. Frogg's Croakers";

Visual Basic

' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = id).First
targetCustomer.CompanyName = "Dr. Frogg's Croakers"

Jakmile je ve výše uvedeném příkladu přiřazeno CompanyName, LINQ to SQL se o změně dozví a může ji zaznamenat. Služba sledování změn uchovává původní hodnoty všech datových členů.

Služba sledování změn také zaznamenává všechny manipulace s vlastnostmi relace. Vlastnosti relace slouží k vytvoření propojení mezi vašimi entitami, i když mohou být propojeny hodnotami klíčů v databázi. Není nutné přímo upravovat členy přidružené ke sloupcům klíčů. LINQ to SQL je před odesláním změn automaticky synchronizuje.

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
foreach (Order o in db.Orders.Where(o => o.CustomerID == custId2)) {
   o.Customer = cust1;
}

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = custId1).First

For Each ord In (From o In db.Orders _ 
                 Where o.CustomerID = custId2) 
   o.Customer = targetCustomer
Next

Objednávky od jednoho zákazníka k druhému můžete jednoduše přesunout tak, že zadáte přiřazení k jeho vlastnosti zákazníka . Vzhledem k tomu, že vztah existuje mezi zákazníkem a objednávkou, můžete ho změnit úpravou obou stran. Mohli jste je stejně snadno odebrat z kolekce Orderscust2 a přidat je do kolekce orders cust1, jak je znázorněno níže.

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
Customer cust2 = db.Customers.Single(c => c.CustomerID == custId2); 
// Pick some order
Order o = cust2.Orders[0]; 
// Remove from one, add to the other
cust2.Orders.Remove(o);
cust1.Orders.Add(o);
// Displays 'true'
Console.WriteLine(o.Customer == cust1);

Visual Basic

Dim targetCustomer1 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
Dim targetCustomer2 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer2.Orders(0) 
' Remove from one, add to the other
targetCustomer2.Orders.Remove(o)
targetCustomer1.Orders.Add(o)
' Displays 'True'
MsgBox(o.Customer = targetCustomer1)

Samozřejmě, pokud přiřadíte relaci hodnotu null, ve skutečnosti se zcela zbavíte relace. Přiřazení vlastnosti Customer objednávky na hodnotu null ve skutečnosti odebere objednávku ze seznamu zákazníka.

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Assign null value
o.Customer = null;
// Displays 'false'
Console.WriteLine(cust.Orders.Contains(o));

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Assign null value
o.Customer = Nothing
' Displays 'False'
Msgbox(targetCustomer.Orders.Contains(o))

Automatická aktualizace obou stran relace je nezbytná pro zachování konzistence grafu objektů. Na rozdíl od normálních objektů jsou relace mezi daty často obousměrné. LINQ to SQL umožňuje použít vlastnosti k reprezentaci relací. Nenabízí ale službu, která by tyto obousměrné vlastnosti automaticky synchronizovala. Jedná se o úroveň služby, která se musí vložit přímo do definic tříd. Třídy entit vygenerované pomocí nástroje pro generování kódu mají tuto funkci. V další kapitole vám ukážeme, jak to udělat u vlastních ručně psaných tříd.

Je však důležité poznamenat, že odebrání relace neznamená, že objekt byl odstraněn z databáze. Nezapomeňte, že životnost podkladových dat přetrvává v databázi, dokud se řádek z tabulky nesmadí. Jediným způsobem, jak objekt skutečně odstranit, je odebrat ho z jeho kolekce Table .

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Remove it directly from the table (I want it gone!)
db.Orders.Remove(o);
// Displays 'false'.. gone from customer's Orders
Console.WriteLine(cust.Orders.Contains(o));
// Displays 'true'.. order is detached from its customer
Console.WriteLine(o.Customer == null);

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                          Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Remove it directly from the table (I want it gone!)
db.Orders.Remove(o)
' Displays 'False'.. gone from customer’s Orders
Msgbox(targetCustomer.Orders.Contains(o))
' Displays 'True'.. order is detached from its customer
Msgbox(o.Customer = Nothing)

Stejně jako u všech ostatních změn se pořadí ve skutečnosti neodstranilo. Jen nám to tak vypadá, protože byl odebrán a odpojen od zbytku našich objektů. Když byl objekt order odebrán z tabulky Orders , služba sledování změn ho označila k odstranění. K skutečnému odstranění z databáze dojde, když se změny odešlou na volání SubmitChanges(). Všimněte si, že samotný objekt se nikdy neodstraní. Modul runtime spravuje životnost instancí objektů, takže se drží tak dlouho, dokud na něj stále máte odkaz. Po odebrání objektu z tabulky a odeslání změn už ho služba sledování změn nesleduje.

Jedinou jinou dobou, kdy entita zůstane nesledovaná, je, když existuje předtím, než o ní DataContext ví. K tomu dochází pokaždé, když v kódu vytvoříte nové objekty. Můžete ve své aplikaci používat instance tříd entit, aniž byste je museli načítat z databáze. Change tacking a správa identit se vztahují pouze na objekty, o kterých dataContext ví. Proto není pro nově vytvořené instance povolená žádná služba, dokud je nepřidáte do objektu DataContext.

K tomu může dojít jedním ze dvou způsobů. Metodu Add() v související kolekci Table můžete volat ručně.

C#

Customer cust =
   new Customer {
      CustomerID = "ABCDE",
      ContactName = "Frond Smooty",
      CompanyTitle = "Eggbert's Eduware",
      Phone = "888-925-6000"
   };
// Add new customer to Customers table
db.Customers.Add(cust);

Visual Basic

Dim targetCustomer = New Customer With { _
         .CustomerID = “ABCDE”, _
         .ContactName = “Frond Smooty”, _
         .CompanyTitle = “Eggbert’s Eduware”, _
         .Phone = “888-925-6000”}
' Add new customer to Customers table
db.Customers.Add(cust)

Případně můžete připojit novou instanci k objektu, o který už dataContext ví.

C#

// Add an order to a customer's Orders
cust.Orders.Add(
   new Order { OrderDate = DateTime.Now }
); 

Visual Basic

' Add an order to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { .OrderDate = DateTime.Now } )

DataContext zjistí vaše nové instance objektů, i když jsou připojené k jiným novým instancím.

C#

// Add an order and details to a customer's Orders
Cust.Orders.Add(
   new Order {
      OrderDate = DateTime.Now,
      OrderDetails = {
         new OrderDetail {
            Quantity = 1,
            UnitPrice = 1.25M,
            Product = someProduct
         }
      }
   }
); 

Visual Basic

' Add an order and details to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { _
      .OrderDate = DateTime.Now, _
      .OrderDetails = New OrderDetail With { _
               .Quantity = 1,
               .UnitPrice = 1.25M,
               .Product = someProduct 
      }
   } )

DataContext v podstatě rozpozná jakoukoli entitu v grafu objektů, která není aktuálně sledována jako nová instance, bez ohledu na to, jestli jste volali metodu Add().

Použití objektu DataContext jen pro čtení

Mnoho scénářů nevyžaduje aktualizaci entit načtených z databáze. Jedním ze zřejmých příkladů je zobrazení tabulky Zákazníků na webové stránce. Ve všech takových případech je možné zvýšit výkon tím, že se dataContextu dá pokyn, aby nesledoval změny entit. Toho dosáhnete zadáním vlastnosti ObjectTracking v objektu DataContext na hodnotu false jako v následujícím kódu:

C#

      db.ObjectTracking = false;
      
      var q = db.Customers.Where( c => c.City = "London");
      foreach(Customer c in q)
         Display(c);

Visual Basic

db.ObjectTracking = False
      
      Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London"
      For Each c in londonCustomers
         Display(c)
Next

Odesílání změn

Bez ohledu na to, kolik změn v objektech provedete, byly tyto změny provedeny pouze v replikách v paměti. Se skutečnými daty v databázi se zatím nic nestalo. K přenosu těchto informací na server nedojde, dokud si je explicitně nevyžádáte voláním Metody SubmitChanges() na objektu DataContext.

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
db.SubmitChanges()

Když zavoláte metodu SubmitChanges(),dataContext se pokusí přeložit všechny vaše změny do ekvivalentních příkazů SQL, a to vložením, aktualizací nebo odstraněním řádků v odpovídajících tabulkách. Pokud chcete, můžete tyto akce přepsat vlastní logikou, ale pořadí odeslání orchestruje služba DataContext označovaná jako procesor změn.

První věc, která se stane, když zavoláte Metodu SubmitChanges(), je, že sada známých objektů jsou prozkoumány, aby se zjistilo, zda byly k nim připojeny nové instance. Tyto nové instance se přidají do sady sledovaných objektů. Dále jsou všechny objekty s čekajícími změnami uspořádány do posloupnosti objektů na základě závislostí mezi nimi. Objekty, jejichž změny závisí na jiných objektech, jsou sekvencovány po jejich závislostech. Omezení cizího klíče a omezení jedinečnosti v databázi hrají velkou roli při určování správného pořadí změn. Potom těsně před přenosem jakýchkoli skutečných změn se spustí transakce zapouzdření řady jednotlivých příkazů, pokud již není v oboru. Nakonec se postupně změny objektů přeloží do příkazů SQL a odešlou se na server.

V tomto okamžiku všechny chyby zjištěné databází způsobí přerušení procesu odeslání a vyvolá se výjimka. Všechny změny v databázi budou vráceny zpět, jako by se nikdy neprodával žádný z odeslání. DataContext bude mít stále úplný záznam všech změn, takže je možné se pokusit problém opravit a znovu je odeslat voláním metody SubmitChanges().

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here 
try {
   db.SubmitChanges();
}
catch (Exception e) {
   // make some adjustments
   ...
   // try again
   db.SubmitChanges();
}

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here 
Try 
   db.SubmitChanges()
Catch e As Exception
   ' make some adjustments
   ...
   ' try again
   db.SubmitChanges()
End Try

Po úspěšném dokončení transakce kolem odeslání DataContext přijme změny objektů tím, že jednoduše zapomene informace sledování změn.

Souběžné změny

Volání metody SubmitChanges() může selhat z různých důvodů. Je možné, že jste vytvořili objekt s neplatným primárním klíčem; ten, který se už používá, nebo s hodnotou, která porušuje určité omezení kontroly databáze. Tyto druhy kontrol je obtížné vložit do obchodní logiky, protože často vyžadují absolutní znalost stavu celé databáze. Nejpravděpodobnější příčinou selhání je ale jednoduše to, že někdo jiný provedl změny objektů před vámi.

Jistě, to by bylo nemožné, pokud jste uzamykat každý objekt v databázi a pomocí plně serializované transakce. Tento styl programování (pesimistická souběžnost) se ale používá zřídka, protože je nákladný a skutečné konflikty se vyskytují jen zřídka. Nejoblíbenější formou řízení souběžných změn je použití formy optimistické souběžnosti. V tomto modelu se vůbec neřídí žádné zámky na řádky databáze. To znamená, že mezi prvním načtením objektů a odesláním změn mohlo v databázi dojít k libovolnému počtu změn.

Proto pokud nechcete použít zásadu, že poslední aktualizace vyhraje a vymažete všechno, co se stalo před vámi, budete pravděpodobně chtít být upozorněni na skutečnost, že podkladová data změnil někdo jiný.

DataContext má integrovanou podporu optimistické souběžnosti díky automatickému zjišťování konfliktů změn. Jednotlivé aktualizace jsou úspěšné pouze v případě, že aktuální stav databáze odpovídá stavu, ve kterému jste při prvním načtení objektů pochopili, že se data mají nacházet. K tomu dochází u jednotlivých objektů a na porušení vás upozorní jenom v případě, že k nim dojde u objektů, u kterých jste provedli změny.

Můžete určit, do jaké míry DataContext rozpozná konflikty změn při definování tříd entit. Každý atribut Column má vlastnost s názvem UpdateCheck , které lze přiřadit jednu ze tří hodnot: Always, Never a WhenChanged. Pokud není nastavena výchozí hodnota atributu Columnje Always, což znamená, že datové hodnoty reprezentované daným členem jsou vždy kontrolovány konflikty, to znamená, pokud neexistuje zřejmé jistič, jako je razítko verze. Atribut Column má vlastnost IsVersion , která umožňuje určit, zda hodnota dat představuje razítko verze spravované databází. Pokud existuje verze, použije se samostatně k určení, jestli došlo ke konfliktu.

Pokud dojde ke konfliktu změn, vyvolá se výjimka, jako by se jednalo o jakoukoli jinou chybu. Transakce kolem odeslání se přeruší, ale Objekt DataContext zůstane stejný, což vám umožní problém opravit a zkusit to znovu.

C#

while (retries < maxRetries) {
   Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");

   // fetch objects and make changes here

   try {
      db.SubmitChanges();
      break;
   }
   catch (ChangeConflictException e) {
      retries++;
   }
}

Visual Basic

Do While retries < maxRetries
   Dim db As New Northwind("c:\northwind\northwnd.mdf")

   ' fetch objects and make changes here

   Try
      db.SubmitChanges()
      Exit Do
   
   catch cce As ChangeConflictException
      retries += 1
   End Try
Loop

Pokud provádíte změny na střední vrstvě nebo serveru, nejjednodušší, co můžete udělat k nápravě konfliktu změn, je jednoduše začít znovu a zkusit to znovu, znovu vytvořit kontext a znovu použít změny. Další možnosti jsou popsané v následující části.

Transakce

Transakce je služba poskytovaná databázemi nebo jiným správcem prostředků, která může být použita k zaručení, že série jednotlivých akcí proběhne automaticky; To znamená, že buď jsou všichni úspěšní, nebo ne. Pokud to neudělá, všechny se také automaticky vrátí zpět, než se může stát cokoli jiného. Pokud již není v oboru žádná transakce, DataContext automaticky spustí databázovou transakci, aby chránil aktualizace při volání SubmitChanges().

Můžete se rozhodnout řídit typ použité transakce, její úroveň izolace nebo to, co skutečně zahrnuje tím, že ji zahájíte sami. Izolace transakce, kterou použije DataContext , se označuje jako ReadCommitted.

C#

Product prod = db.Products.Single(p => p.ProductID == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.SubmitChanges()
   ts.Complete()
End Using

Příklad výše inicializuje plně serializované transakce vytvořením nového objektu oboru transakce. Všechny databázové příkazy spuštěné v rámci transakce budou chráněny transakcí.

C#

Product prod = db.Products.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.ExecuteCommand("exec sp_BeforeSubmit");
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.ExecuteCommand(“exec sp_BeforeSubmit”)
   db.SubmitChanges()
   ts.Complete()
End Using

Tato upravená verze stejného příkladu používá Metodu ExecuteCommand() na DataContext ke spuštění uložené procedury v databázi těsně před odesláním změn. Bez ohledu na to, co uložená procedura s databází dělá, můžeme si být jistí, že její akce jsou součástí stejné transakce.

Pokud se transakce úspěšně dokončí, DataContext vyhodí všechny shromážděné informace o sledování a zachází s novými stavy entit jako s nezměněnými. Pokud však transakce selže, nevrací zpět změny vašich objektů. To vám umožňuje maximální flexibilitu při řešení problémů při odesílání změn.

Je také možné použít místní transakce SQL místo nové TransactionScope. LINQ to SQL tuto možnost nabízí, aby vám pomohla integrovat LINQ to SQL funkce do již existujících ADO.NET aplikací. Pokud ale pojedete touto cestou, budete muset být zodpovědní za mnohem více.

C#

Product prod = q.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

db.Transaction = db.Connection.BeginTransaction();
try {
   db.SubmitChanges();
   db.Transaction.Commit();
}
catch {
   db.Transaction.Rollback();
   throw;
}
finally {
   db.Transaction = null;
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

db.Transaction = db.Connection.BeginTransaction()
Try
   db.SubmitChanges()
   db.Transaction.Commit()

catch e As Exception
   db.Transaction.Rollback()
   Throw e
Finally
   db.Transaction = Nothing
End Try

Jak vidíte, použití ručně řízené databázové transakce je trochu více zapojené. Nejen, že ho musíte spustit sami, musíte dataContext explicitně říct, aby ho použil přiřazením k Transaction vlastnost. Pak musíte použít blok try-catch k uložení logiky odeslání, nezapomeňte explicitně informovat transakci k potvrzení a explicitně informovat DataContext přijmout změny, nebo přerušit transakce, pokud dojde k selhání v jakémkoli okamžiku. Nezapomeňte také nastavit vlastnost Transaction zpět na hodnotu null , jakmile budete hotovi.

Uložené procedury

Při volání Metody SubmitChanges() LINQ to SQL generuje a spouští příkazy SQL pro vložení, aktualizaci a odstranění řádků v databázi. Tyto akce mohou být přepsány vývojáři aplikací a na jejich místě lze k provedení požadovaných akcí použít vlastní kód. Tímto způsobem může procesor změn automaticky vyvolat alternativní zařízení, jako jsou databázové uložené procedury.

Zvažte uloženou proceduru pro aktualizaci jednotek na skladě pro tabulku Products v ukázkové databázi Northwind. Deklarace SQL procedury je následující.

SQL

create proc UpdateProductStock
   @id               int,
   @originalUnits    int,
   @decrement         int
as

Místo normálního automaticky generovaného příkazu pro aktualizaci můžete použít uloženou proceduru tak, že nadefinujete metodu pro DataContext silného typu. I když je třída DataContext automaticky generována nástrojem pro generování kódu LINQ to SQL, můžete tyto metody zadat v částečné třídě vlastní.

C#

public partial class Northwind : DataContext
{
   ...

   public void UpdateProduct(Product original, Product current) {
      // Execute the stored procedure for UnitsInStock update
      if (original.UnitsInStock != current.UnitsInStock) {
         int rowCount = this.ExecuteCommand(
            "exec UpdateProductStock " +
            "@id={0}, @originalUnits={1}, @decrement={2}",
            original.ProductID,
            original.UnitsInStock,
            (original.UnitsInStock - current.UnitsInStock)
         );
         if (rowCount < 1)
            throw new Exception("Error updating");
      }
      ...
   }
}

Visual Basic

Partial Public Class Northwind
         Inherits DataContext

   ...

   Public Sub UpdateProduct(original As Product, current As Product)
      ‘ Execute the stored procedure for UnitsInStock update
      If original.UnitsInStock <> current.UnitsInStock Then
         Dim rowCount As Integer = ExecuteCommand( _
            "exec UpdateProductStock " & _
            "@id={0}, @originalUnits={1}, @decrement={2}", _
            original.ProductID, _
            original.UnitsInStock, _
            (original.UnitsInStock - current.UnitsInStock) )
         If rowCount < 1 Then
            Throw New Exception(“Error updating”)
         End If
      End If
      ...
   End Sub
End Class

Podpis metody a obecný parametr říká DataContext , aby tuto metodu místo vygenerovaného příkazu update používal. Původní a aktuální parametry jsou používány LINQ to SQL pro předávání původních a aktuálních kopií objektu zadaného typu. Pro detekci konfliktů optimistické souběžnosti jsou k dispozici dva parametry.

Poznámka Pokud přepíšete výchozí logiku aktualizace, je na vás detekce konfliktů.

Uložená procedura UpdateProductStock je vyvolána pomocí ExecuteCommand() metody DataContext. Vrátí počet ovlivněných řádků a má následující podpis:

C#

public int ExecuteCommand(string command, params object[] parameters);

Visual Basic

Public Function ExecuteCommand(command As String, _
         ParamArray parameters() As Object) As Integer

Pole objektů se používá k předávání parametrů požadovaných pro spuštění příkazu.

Podobně jako u metody update mohou být zadány metody vložení a odstranění. Metody vložení a odstranění přebírají pouze jeden parametr typu entity, který se má aktualizovat. Například metody vložení a odstranění instance Produktu lze zadat takto:

C#

public void InsertProduct(Product prod) { ... }
public void DeleteProudct(Product prod) { ... }

Visual Basic

Public Sub InsertProduct(prod As Product)  ... 
Public Sub DeleteProudct(prod As Product)  ... 

In-Depth tříd entit

Používání atributů

Třída entity je stejně jako jakákoli normální třída objektů, kterou můžete definovat jako součást aplikace, s tím rozdílem, že je opatřena poznámkami speciálními informacemi, které ji přidružují ke konkrétní databázové tabulce. Tyto poznámky se vytvoří jako vlastní atributy v deklaraci třídy. Atributy jsou smysluplné pouze při použití třídy ve spojení s LINQ to SQL. Jsou podobné atributům serializace XML v rozhraní .NET Framework. Tyto "datové" atributy poskytují LINQ to SQL dostatek informací k překladu dotazů na vaše objekty do dotazů SQL na databázi a změny objektů na příkazy pro vložení, aktualizaci a odstranění SQL.

Informace o mapování je také možné reprezentovat pomocí souboru mapování XML místo atributů. Tento scénář je podrobněji popsán v části Externí mapování.

Atribut databáze

Atribut Database se používá k určení výchozího názvu databáze, pokud není zadán připojením. Atributy databáze lze použít u deklarací DataContext silného typu. Tento atribut je volitelný.

Atribut databáze

Vlastnost Typ Popis
Název Řetězec Určuje název databáze. Informace se použijí pouze v případě, že samotné připojení nezadá název databáze. Pokud tento atribut Database v deklaraci kontextu neexistuje a není určen připojením, předpokládá se, že databáze má stejný název jako třída kontextu.

C#

[Database(Name="Database#5")]
public class Database5 : DataContext {
   ...
}

Visual Basic

<Database(Name:="Database#5")> _
Public Class Database5 
               Inherits DataContext
   ...
End Class

Atribut tabulky

Atribut Table slouží k určení třídy jako třídy entity přidružené k databázové tabulce. Třídy s atributem Table budou speciálně zpracovány LINQ to SQL.

Atribut tabulky

Vlastnost Typ Popis
Název Řetězec Určuje název tabulky. Pokud tyto informace nejsou zadané, předpokládá se, že tabulka má stejný název jako třída entity.

C#

[Table(Name="Customers")]
public class Customer {
   ...
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer 
   ...
End Class

Atribut sloupce

Atribut Column slouží k určení člena třídy entity, který představuje sloupec v databázové tabulce. Dá se použít pro jakékoli pole nebo vlastnost, veřejné, soukromé nebo interní. Při LINQ to SQL uloží změny do databáze pouze členové identifikovaní jako sloupce.

Atribut sloupce

Vlastnost Typ Popis
Název Řetězec Název sloupce v tabulce nebo zobrazení. Pokud není zadaný, předpokládá se, že sloupec má stejný název jako člen třídy.
Storage Řetězec Název základního úložiště. Pokud je zadaný, řekne LINQ to SQL, jak obejít přístup veřejné vlastnosti datového člena a jak pracovat se samotnou nezpracovanou hodnotou. Pokud není zadaný LINQ to SQL získá a nastaví hodnotu pomocí veřejného příslušenství.
Dbtype Řetězec Typ sloupce databáze zadaný pomocí typů a modifikátorů databáze. Toto bude přesný text, který se použije k definování sloupce v příkazu deklarace tabulky T-SQL. Pokud není zadaný, typ sloupce databáze se odvodí z typu člena. Konkrétní typ databáze je nutný pouze v případě, že se očekává použití metody CreateDatabase() k vytvoření instance databáze.
IsPrimaryKey Logická hodnota Pokud je nastavená hodnota true, představuje člen třídy sloupec, který je součástí primárního klíče tabulky. Pokud je jako ID označeno více než jeden člen třídy, primární klíč je považován za složený z přidružených sloupců.
IsDbGenerated Logická hodnota Určuje, že hodnota sloupce člena je automaticky generována databází. Primární klíče, které jsou označené IsDbGenerated=true , by měly mít také DBType s modifikátorem IDENTITY . IsDbGenerated členové se synchronizují okamžitě po vložení řádku dat a jsou k dispozici po dokončení funkce SubmitChanges( ).
Isversion Logická hodnota Identifikuje typ sloupce člena jako časové razítko databáze nebo číslo verze. Čísla verzí se zvýší a sloupce časových razítek se aktualizují databází při každé aktualizaci přidruženého řádku. Členové s IsVersion=true se synchronizují okamžitě po aktualizaci řádku dat. Nové hodnoty se zobrazí po dokončení funkce SubmitChanges().
Kontrola aktualizací Kontrola aktualizací Určuje, jak LINQ to SQL implementuje detekci konfliktů optimistické souběžnosti. Pokud žádný člen není určen jako IsVersion=true detekce se provádí porovnáním hodnot původních členů s aktuálním stavem databáze. Členy, které LINQ to SQL používají při zjišťování konfliktů, můžete určit tak, že každému členovi udělíte hodnotu výčtu UpdateCheck.
  • Vždy: Tento sloupec vždy používejte k detekci konfliktů.
  • Nikdy: Nikdy nepoužívejte tento sloupec pro detekci konfliktů.
  • WhenChanged: Tento sloupec použijte pouze v případě, že aplikace změnila člena.
IsDiskriminátor Logická hodnota Určuje, zda člen třídy má diskriminující hodnotu pro hierarchii dědičnosti.
Výraz Řetězec Nemá vliv na operaci LINQ to SQL, ale používá se během .CreateDatabase() jako nezpracovaný výraz SQL představující výraz počítaný sloupec.
CanBeNull Logická hodnota Označuje, že hodnota může obsahovat hodnotu null. To se obvykle odvozuje z typu CLR člena entity. Tento atribut slouží k označení, že řetězcová hodnota je v databázi reprezentována jako sloupec s možnou hodnotou null.
Autosync Autosync Určuje, jestli se sloupec automaticky synchronizuje s hodnotou vygenerovanou databází pomocí příkazů pro vložení nebo aktualizaci. Platné hodnoty pro tuto značku jsou OnInsert, Always a Never.

Typická třída entity bude používat atributy column u veřejných vlastností a ukládat skutečné hodnoty v privátních polích.

C#

private string _city;

[Column(Storage="_city", DBType="NVarChar(15)")]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

Private _city As String

<Column(Storage:="_city", DBType:="NVarChar(15)")> _
public Property City As String
   Get
   set
End Property

DbType je zadán pouze tak, aby CreateDatabase() metoda může vytvořit tabulku s nejpřesnějším typem. Jinak se znalost, že základní sloupec je omezen na 15 znaků, nepoužívá.

Členy představující primární klíč typu databáze budou často přidruženy k automaticky generovaným hodnotám.

C#

private string _orderId;

[Column(Storage="_orderId", IsPrimaryKey=true, IsDbGenerated = true,
   DBType="int NOT NULL IDENTITY")]
public string OrderId {
   get { ... }
   set { ... }
}

Visual Basic

Private _orderId As String

<Column(Storage:="_orderId", IsPrimaryKey:=true, _
           IsDbGenerated:= true, DBType:="int NOT NULL IDENTITY")> _
public Property OrderId As String
   Get
   Set
End Property

Pokud zadáte DBType, nezapomeňte zahrnout modifikátor IDENTITY . LINQ to SQL nezvětšuje vlastní zadaný typ DBType. Pokud však dbType zůstane nezadané LINQ to SQL z toho odvodí, že při vytváření databáze prostřednictvím metody CreateDatabase() je potřeba modifikátor IDENTITY.

Podobně, pokud IsVersion vlastnost je true, DBType musí zadat správné modifikátory určit číslo verze nebo sloupec časového razítka. Pokud není zadán typ DBType, LINQ to SQL odvodí správné modifikátory.

Můžete řídit přístup k členu přidruženému k automaticky generovanému sloupci, razítku verze nebo libovolnému sloupci, který byste mohli chtít skrýt určením úrovně přístupu člena nebo dokonce omezením samotného přístupového objektu.

C#

private string _customerId;

[Column(Storage="_customerId", DBType="NCHAR(5) ")]
public string CustomerID {
   get { ... }
}

Visual Basic

Private _customerId As String

<Column(Storage:="_customerId", DBType:="NCHAR(5)")> _
Public Property CustomerID As String
   Get
End Property

Vlastnost CustomerID (ID zákazníka ) objednávky je možné nastavit jen pro čtení tak, že se nedefinuje objekt set. LINQ to SQL stále můžete získat a nastavit podkladovou hodnotu prostřednictvím člena úložiště.

Můžete také nastavit člen zcela nedostupný pro zbytek aplikace umístěním atributu Column na soukromého člena. To umožňuje, aby třída entity obsahovala informace relevantní pro obchodní logiku třídy, aniž by je obecně zpřístupňuje. I když jsou soukromí členové součástí přeložených dat, protože jsou soukromá, nemůžete na ně odkazovat v dotazu integrovaném do jazyka.

Ve výchozím nastavení se k detekci optimistické souběžnosti používají všichni členové. Určením jeho hodnoty UpdateCheck můžete určit, jestli se bude používat konkrétní člen.

C#

[Column(Storage="_city", UpdateCheck=UpdateCheck.WhenChanged)]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

<Column(Storage:="_city", UpdateCheck:=UpdateCheck.WhenChanged)> _
Public Property City As String
   Get
   Set
End Property

Následující tabulka ukazuje přípustné mapování mezi typy databáze a odpovídajícím typem CLR. Tuto tabulku použijte jako vodítko při určování typu CLR, který má představovat konkrétní sloupec databáze.

Typ databáze a odpovídající přípustné mapování typu CLR

Typ databáze Typ .NET CLR Komentáře
bit, tinyint, smallint, int, bigint Bye, Int16, Uint16, Int32, Uint32, Int64, Uint64 Možné ztrátové převody. Hodnoty nesmí být zaokrouhlené.
bit Logická hodnota  
decimal, numeric, smallmoney, money Decimal Rozdíl měřítka může mít za následek ztrátové převody. Nesmí být zpáteční.
real, float Jednolůžkový pokoj, dvoulůžkový pokoj Rozdíly v přesnosti.
char, varchar, text, nchar, nvarchar, ntext Řetězec Jsou možné rozdíly mezi národními prostředími.
datetime, smalldatetime DateTime Jiná přesnost může způsobit ztrátový převod a problémy s odezvou.
uniqueidentifier Identifikátor GUID Různá pravidla kolace Řazení nemusí fungovat podle očekávání.
časové razítko Byte[] (Byte() in Visual Basic), Binary Bajtové pole je považováno za skalární typ. Uživatel zodpovídá za přidělení odpovídajícího úložiště při zavolání konstruktoru. Považuje se za neměnnou a nesleduje se pro změny.
binary, varbinary Byte[] (Byte() in Visual Basic), Binary  

Atribut přidružení

Atribut Association slouží k určení vlastnosti, která představuje přidružení databáze, jako je vztah cizího klíče k primárnímu klíči.

Atribut přidružení

Vlastnost Typ Popis
Název Řetězec Název přidružení. To je často stejné jako název omezení cizího klíče databáze. Používá se, když createDatabase() slouží k vytvoření instance databáze za účelem vygenerování příslušného omezení. Používá se také k odlišení více relací v jedné třídě entity odkazujících na stejnou cílovou třídu entity. V tomto případě musí mít vlastnosti relace na stranách relace (pokud jsou definovány obě) stejný název.
Storage Řetězec Název základního člena úložiště. Pokud je zadaný, řekne LINQ to SQL, jak obejít přístup k veřejné vlastnosti datového člena a pracovat se samotnou nezpracovanou hodnotou. Pokud není zadaný LINQ to SQL získá a nastaví hodnotu pomocí veřejného přístupového objektu. Doporučujeme, aby všichni členové přidružení byly vlastnosti se samostatnými členy úložiště.
ThisKey Řetězec Čárkami oddělený seznam názvů jednoho nebo více členů této třídy entity, které představují klíčové hodnoty na této straně přidružení. Pokud není zadaný, předpokládá se, že členové jsou členy, které tvoří primární klíč.
Jiný klíč Řetězec Čárkami oddělený seznam názvů jednoho nebo více členů cílové třídy entity, které představují hodnoty klíčů na druhé straně přidružení. Pokud nejsou zadané, předpokládá se, že členy tvoří primární klíč třídy ostatních entit.
Isunique Logická hodnota True , pokud pro cizí klíč existuje omezení jedinečnosti, což značí vztah true 1:1. Tato vlastnost se používá jen zřídka, protože relace 1:1 jsou téměř nemožné spravovat v rámci databáze. Většinou se modely entit definují pomocí relací 1:n, i když je vývojáři aplikací považují za 1:1.
IsForeignKey Logická hodnota Hodnota True , pokud cílový typ "jiné" přidružení je nadřazený typu zdroje. U vztahů cizího klíče k primárnímu klíči je strana držící cizí klíč podřízená a strana držící primární klíč je nadřazená.
Odstranit pravidla Řetězec Používá se k přidání chování odstranění k tomuto přidružení. Například "CASCADE" přidá do relace FK "ON DELETE CASCADE". Pokud je nastavená hodnota null, nepřidá se žádné chování při odstraňování.

Vlastnosti přidružení představují buď jeden odkaz na jinou instanci třídy entity, nebo představují kolekci odkazů. Jednoúčelové odkazy musí být kódovány ve třídě entity pomocí hodnotového typu EntityRef<T>(EntityRef (OfT) v jazyce Visual Basic) pro uložení skutečného odkazu. Typ EntityRef je způsob, jakým LINQ to SQL umožňuje odložené načítání odkazů.

C#

class Order
{
   ...
   private EntityRef<Customer> _Customer;

   [Association(Name="FK_Orders_Customers", Storage="_Customer",
      ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value;
            // Additional code to manage changes }
   }
}

Visual Basic

Class Order

   ...
   Private _customer As EntityRef(Of Customer)

   <Association(Name:="FK_Orders_Customers", _
            Storage:="_Customer", ThisKey:="CustomerID")> _
   public Property Customer() As Customer
      Get  
         Return _customer.Entity
      End Get   
   Set (value As Customer)
      _customer.Entity = value
      ‘ Additional code to manage changes
   End Set
End Class

Veřejná vlastnost je zadána jako Zákazník, nikoli Jako Zákazník EntityRef<.> Je důležité nezpřístupnit typ EntityRef jako součást veřejného rozhraní API, protože odkazy na tento typ v dotazu nebudou přeloženy do SQL.

Podobně musí vlastnost přidružení představující kolekci používat typ kolekce EntitySet<T> (EntitySet(OfT) v jazyce Visual Basic) k uložení relace.

C#

class Customer
{
   ...
   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
} 

Visual Basic

Class Customer

   ...
   Private _Orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As EntitySet(Of Order)
      Get
           Return _Orders
      End Get
   Set (value As EntitySet(Of Order))
      _Orders.Assign(value)
   End Property
End Class

Vzhledem k tomu, že EntitySet<T> (EntitySet(OfT) v jazyce Visual Basic) je kolekce, je platné použít EntitySet jako návratový typ. Je také platné maskovat true typ kolekce, pomocí ICollection<T> (ICollection(OfT) v jazyce Visual Basic) rozhraní místo.

C#

class Customer
{
   ...

   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public ICollection<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

Class Customer

   ...
   Private _orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As ICollection (Of Order)
      Get
           Return _orders
      End Get
Set (value As ICollection (Of Order))
         _orders.Assign(value)
      End Property
End Class

Ujistěte se, že používáte metodu Assign() pro EntitySet , pokud pro vlastnost zveřejňujete veřejné setter. To umožňuje třídě entity dál používat stejnou instanci kolekce, protože už může být svázaná se službou sledování změn.

Atribut ResultType

Tento atribut určuje typ prvku výčtu sekvence, který může být vrácen z funkce, která byla deklarována k vrácení IMultipleResults rozhraní. Tento atribut lze zadat více než jednou.

Atribut ResultType

Vlastnost Typ Popis
Typ Typ Typ vrácených výsledků

Atribut StoredProcedure

Atribut StoredProcedure slouží k deklaraci, že volání metody definované v typu DataContext nebo Schema je přeloženo jako volání databázové uložené procedury.

Atribut StoredProcedure

Vlastnost Typ Popis
Název Řetězec Název uložené procedury v databázi. Pokud není zadaný, předpokládá se, že uložená procedura má stejný název jako metoda.

Atribut funkce

Atribut Function slouží k deklarování, že volání metody definované na DataContext nebo Schema je přeloženo jako volání uživatelem definované skalární nebo tabulkové funkce databáze.

Atribut funkce

Vlastnost Typ Popis
Název Řetězec Název funkce v databázi. Pokud není zadaný, předpokládá se, že má funkce stejný název jako metoda.

Atribut parametru

Atribut Parameter slouží k deklaraci mapování mezi metodou a parametry databázové uložené procedury nebo uživatelem definované funkce.

Atribut parametru

Vlastnost Typ Popis
Název Řetězec Název parametru v databázi. Pokud parametr nezadáte, odvodí se z názvu parametru metody.
Dbtype Řetězec Typ parametru zadaný pomocí typů a modifikátorů databáze.

Atribut InheritanceMapping

Atribut InheritanceMapping se používá k popisu korespondence mezi konkrétními diskriminujícími kódy a podtypem dědičnosti. Všechny atributy InheritanceMapping používané pro hierarchii dědičnosti musí být deklarovány na kořenovém typu hierarchie.

Atribut InheritanceMapping

Propety Typ Description
Kód Objekt Hodnota diskriminujícího kódu.
Typ Typ Podtyp Dědičnost. Může se jednat o libovolný ne abstraktní typ v hierarchii dědičnosti, včetně kořenového typu.
IsDefault Logická hodnota Určuje, zda je zadaný podtyp dědičnosti výchozím typem vytvořeným při LINQ to SQL najde diskriminující kód, který není definován atributy InheritanceMapping. Přesně jeden z atributů InheritanceMapping musí být deklarován s IsDefault jako true.

Konzistence grafů

Graf je obecný termín pro datovou strukturu objektů, které na sebe odkazují odkazy. Hierarchie (neboli strom) je degenerovaná forma grafu. Objektové modely specifické pro doménu často popisují síť odkazů, které se nejlépe popisují jako graf objektů. Stav grafu objektů je zásadně důležitý pro stabilitu vaší aplikace. Proto je důležité zajistit, aby odkazy v grafu zůstaly konzistentní s vašimi obchodními pravidly nebo omezeními definovanými v databázi.

LINQ to SQL za vás automaticky nespravuje konzistenci odkazů na relace. Pokud jsou relace obousměrné, změna na jedné straně relace by měla automaticky aktualizovat druhou stranu relace. Všimněte si, že je neobvyklé, aby se normální objekty chovaly tímto způsobem, takže je nepravděpodobné, že byste objekty jinak navrhli tímto způsobem.

LINQ to SQL poskytuje několik mechanismů, které tuto práci usnadňují, a vzor, který vám umožní postupovat, abyste měli jistotu, že odkazy spravujete správně. Třídy entit vygenerované nástrojem pro generování kódu automaticky implementují správné vzory.

C#

public class Customer() {
   this._Orders =
      new EntitySet<Order>(
         new Action<Order>(this.attach_Orders),
         new Action<Order>(this.detach_Orders));
);}

Visual Basic

Public Class Customer()
         _Orders = New EntitySet(Of Order)( _
              New Action(Of Order)(attach_Orders), _
                 New Action(Of Order)(detach_Orders))
      End Class
);}

Typ EntitySet<T> (EntitySet(OfT) v jazyce Visual Basic) má konstruktor, který umožňuje zadat dva delegáty, které se mají použít jako zpětná volání; první při přidání položky do kolekce, druhý při odebrání. Jak je vidět v příkladu, kód, který zadáte pro tyto delegáty, může a měl by být napsán tak, aby aktualizoval vlastnost zpětné relace. Takto se vlastnost Customer v instanci Order automaticky změní, když se objednávka přidá do kolekce Orders zákazníka.

Implementace vztahu na druhém konci není tak snadná. EntityRef<T> (EntityRef(OfT) v jazyce Visual Basic) je typ hodnoty definovaný tak, aby obsahoval co nejmenší dodatečnou režii skutečného odkazu na objekt. Nemá místo pro dvojici delegátů. Místo toho by měl být kód spravující konzistenci grafu jednoúčelových odkazů vložený do samotných přístupových objektů vlastností.

C#

[Association(Name="FK_Orders_Customers", Storage="_Customer",
   ThisKey="CustomerID")]
public Customer Customer {
   get {
      return this._Customer.Entity;
   }
   set {
      Customer v = this._Customer.Entity;
      if (v != value) {
         if (v != null) {
            this._Customer.Entity = null;
            v.Orders.Remove(this);
         }
         this._Customer.Entity = value;
         if (value != null) {
            value.Orders.Add(this);
         }
      }
   }
}

Visual Basic

<Association(Name:="FK_Orders_Customers", _
         Storage:="_Customer", ThisKey:="CustomerID")> _
Public Property Customer As Customer 
   Get
      Return _Customer.Entity
   End Get
   Set (value As Customer)
      Dim cust As Customer v = _customer.Entity
      if cust IsNot value Then
         If cust IsNot Nothing Then
            _Customer.Entity = Nothing
            cust.Orders.Remove(Me)
         End If

         _customer.Entity = value
         if value IsNot Nothing Then
            value.Orders.Add(Me)
         End If
      End If
   End Set
End Property

Podívej se na settera. Při změně vlastnosti Customer se instance objednávky nejprve odebere z kolekce Orders aktuálního zákazníka a teprve později se přidá do nové kolekce zákazníka. Všimněte si, že před voláním remove() je skutečný odkaz na entitu nastavený na hodnotu null. To se provádí, aby se zabránilo rekurzi při zavolání metody Remove(). Nezapomeňte, že EntitySet použije delegáty zpětného volání k přiřazení vlastnosti Customer tohoto objektu na hodnotu null. Totéž se stane těsně před voláním add(). Skutečný odkaz na entitu se aktualizuje na novou hodnotu. Tím se opět omezí případná rekurze a samozřejmě se v první řadě splní úkol settera.

Definice relace 1:1 je velmi podobná definici relace 1:N ze strany jednoúčelového odkazu. Místo volaného příkazu Add() a Remove() se přiřadí nový objekt nebo je přiřazena hodnota null pro oddělení relace.

Opět je důležité, aby vlastnosti relace udržovaly konzistenci grafu objektů. Pokud je graf objektů v paměti nekonzistentní s databázovými daty, pak se při zavolání Metody SubmitChanges vygeneruje výjimka za běhu. Zvažte použití nástroje pro generování kódu, který vám umožní zachovat konzistenci.

Oznámení o změnách

Vaše objekty se můžou účastnit procesu sledování změn. Není to nutné, ale mohou výrazně snížit množství režie potřebné ke sledování potenciálních změn objektů. Je pravděpodobné, že vaše aplikace načte z dotazů mnohem více objektů, než bude nakonec upraveno. Bez proaktivní pomoci od vašich objektů je služba sledování změn omezená v tom, jak může skutečně sledovat změny.

Vzhledem k tomu, že v modulu runtime není žádná služba skutečného zachycování, formální sledování ve skutečnosti neprovádí. Místo toho jsou při prvním načtení uloženy duplicitní kopie objektů. Později, když zavoláte SubmitChanges(), se tyto kopie použijí k porovnání s těmi, které jste dostali. Pokud se jejich hodnoty liší, objekt byl změněn. To znamená, že každý objekt vyžaduje dvě kopie v paměti, i když je nikdy nezměníte.

Lepším řešením je nechat samotné objekty oznámit službě sledování změn, když se skutečně změní. Toho lze dosáhnout tím, že objekt implementuje rozhraní, které zveřejňuje událost zpětného volání. Služba sledování změn pak může připojit každý objekt a přijímat oznámení, když se změní.

C#

[Table(Name="Customers")]
public partial class Customer: INotifyPropertyChanging {

   public event PropertyChangingEventHandler PropertyChanging;

   private void OnPropertyChanging() {
      if (this.PropertyChanging != null) {
         this.PropertyChanging(this, emptyEventArgs);
      }
   }

   private string _CustomerID;

   [Column(Storage="_CustomerID", IsPrimaryKey=true)]
   public string CustomerID {
      get {
         return this._CustomerID;
      }
      set {
         if ((this._CustomerID != value)) {
            this.OnPropertyChanging("CustomerID");
            this._CustomerID = value;
         }
      }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Partial Public Class Customer 
         Inherits INotifyPropertyChanging
Public Event PropertyChanging As PropertyChangingEventHandler _
        Implements INotifyPropertyChanging.PropertyChanging

   Private Sub OnPropertyChanging()
         RaiseEvent PropertyChanging(Me, emptyEventArgs)
   End Sub

   private _customerID As String 

   <Column(Storage:="_CustomerID", IsPrimaryKey:=True)>
   public Property CustomerID() As String
      Get
         Return_customerID
      End Get
      Set (value As Customer)
         If _customerID IsNot value Then
            OnPropertyChanging(“CustomerID”)
            _CustomerID = value
         End IF
      End Set
   End Function
End Class

Aby bylo možné zlepšit sledování změn, musí třídy entit implementovat rozhraní INotifyPropertyChanging . Vyžaduje pouze, abyste definovali událost s názvem PropertyChanging – služba sledování změn se pak zaregistruje s vaší událostí, když objekty přejdou do jejího vlastnictví. Vše, co musíte udělat, je vyvolat tuto událost bezprostředně předtím, než se chystáte změnit hodnotu vlastnosti.

Nezapomeňte také vložit stejnou logiku generování událostí do setter vlastností relace. Pro EntitySets vytvořte události v delegátech, které zadáte.

C#

public Customer() {
   this._Orders =
      new EntitySet<Order>(
         delegate(Order entity) {
            this.OnPropertyChanging("Orders");
            entity.Customer = this;
         },
         delegate(Order entity) {
            this.onPropertyChanging("Orders");
            entity.Customer = null;
         }
      );
}

Visual Basic

Dim _orders As EntitySet(Of Order)
Public Sub New()
   _orders = New EntitySet(Of Order)( _
      AddressOf OrderAdding, AddressOf OrderRemoving)
End Sub

Sub OrderAdding(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Me
End Sub

Sub OrderRemoving(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Nothing
End Sub

Dědičnost

LINQ to SQL podporuje mapování s jednou tabulkou, kdy je celá hierarchie dědičnosti uložena v jedné databázové tabulce. Tabulka obsahuje zploštělé sjednocení všech možných datových sloupců pro celou hierarchii a každý řádek má ve sloupcích hodnoty null, které se nevztahují na typ instance reprezentované řádkem. Strategie mapování s jednou tabulkou je nejjednodušší reprezentací dědičnosti a poskytuje dobré charakteristiky výkonu pro mnoho různých kategorií dotazů.

Mapování

Pokud chcete toto mapování implementovat pomocí LINQ to SQL, musíte zadat následující atributy a vlastnosti atributů v kořenové třídě hierarchie dědičnosti:

  • Atribut [Table] (<Tabulka> v jazyce Visual Basic).
  • Atribut [InheritanceMapping] (<InheritanceMapping> v jazyce Visual Basic) pro každou třídu ve struktuře hierarchie. Pro ne abstraktní třídy musí tento atribut definovat vlastnost Code (hodnota, která se zobrazí v databázové tabulce ve sloupci Dědičnost diskriminátoru a označuje, do které třídy nebo podtřídy patří tento řádek dat) a vlastnost Type (která určuje, kterou třídu nebo podtřídu označuje hodnota klíče).
  • Vlastnost IsDefault v jednom atributu [InheritanceMapping] (<InheritanceMapping> v jazyce Visual Basic). Tato vlastnost slouží k určení "náhradního" mapování v případě, že diskriminující hodnota z tabulky databáze neodpovídá žádné z hodnot kódu v mapování dědičnosti.
  • Vlastnost IsDiscriminator pro atribut [Column] (<Sloupec> v jazyce Visual Basic) značí, že se jedná o sloupec, který obsahuje hodnotu kódu pro mapování dědičnosti.

U podtříd nejsou vyžadovány žádné speciální atributy ani vlastnosti. Všimněte si zejména, že podtřídy nemají atribut [Table] (<Tabulka> v jazyce Visual Basic).

V následujícím příkladu jsou data obsažená v podtřídách Car a Truck mapována na jednoúčelovou tabulku Databáze Vozidlo. (Pro zjednodušení příkladu používá ukázkový kód pro mapování sloupců místo vlastností pole.)

C#

[Table]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
public class Vehicle
{
   [Column(IsDiscriminator = true)]
   public string Key;
   [Column(IsPrimaryKey = true)]
   public string VIN;
   [Column]
   public string MfgPlant;
}
public class Car : Vehicle
{
   [Column]
   public int TrimCode;
   [Column]
   public string ModelName;
}

public class Truck : Vehicle
{
   [Column]
   public int Tonnage;
   [Column]
   public int Axles;
}

Visual Basic

<Table> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), _
              IsDefault:=true)> _
Public Class Vehicle

   <Column(IsDiscriminator:=True)> _
   Public Key As String
   <Column(IsPrimaryKey:=True)> _
   Public VIN As String
   <Column> _
   Public MfgPlant As String
End Class
Public Class Car
       Inherits Vehicle
   <Column> _
   Public TrimCode As Integer
   <Column> _
   Public ModelName As String
End Class

Public class Truck
       Inherits Vehicle 
   <Column> _
   public Tonnage As Integer
   <Column> _
   public Axles As Integer
End Class

Diagram tříd se zobrazí takto:

Obrázek 1: Diagram tříd vozidel

Při zobrazení výsledného diagramu databáze v Průzkumníku serveru uvidíte, že všechny sloupce byly namapovány na jednu tabulku, jak je znázorněno tady:

Obrázek 2. Sloupce mapované na jednu tabulku

Všimněte si, že typy sloupců, které představují pole v podtypech, musí být s možnou hodnotou null nebo musí být zadán výchozí. To je nezbytné k tomu, aby příkazy pro vložení byly úspěšné.

Dotazování

Následující kód poskytuje příchuť toho, jak můžete v dotazech používat odvozené typy:

C#

var q = db.Vehicle.Where(p => p is Truck);
//or
var q = db.Vehicle.OfType<Truck>();
//or
var q = db.Vehicle.Select(p => p as Truck).Where(p => p != null);
foreach (Truck p in q)
   Console.WriteLine(p.Axles);

Visual Basic

Dim trucks = From veh In db.Vehicle _ 
             Where TypeOf(veh) Is Truck

For Each truck In trucks
   Console.WriteLine(p.Axles) 
Next

Pokročilý

Hierarchii můžete rozšířit dalece nad rámec již poskytnuté jednoduché ukázky.

Příklad 1

Tady je mnohem hlubší hierarchie a složitější dotaz:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle), IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "D", Type = typeof(DumpTruck))]
public class Truck: Vehicle { ... }

public class Semi: Truck { ... }

public class DumpTruck: Truck { ... }

...
// Get all trucks along with a flag indicating industrial application.

db.Vehicles.OfType<Truck>.Select(t => 
   new {Truck=t, IsIndustrial=t is Semi || t is DumpTruck }
);

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), IsDefault:=True)> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="S", Type:=Typeof(Semi))> _
<InheritanceMapping(Code:="D", Type:=Typeof(DumpTruck))> _
Public Class Truck
       InheritsVehicle
Public Class Semi
       Inherits Truck

Public Class DumpTruck
       InheritsTruck 
...
' Get all trucks along with a flag indicating industrial application.
Dim trucks = From veh In db.Vehicle _ 
             Where Typeof(veh) Is Truck And _ 
             IsIndustrial = (Typeof(veh) Is Semi _ 
             Or Typeof(veh) Is DumpTruck)

Příklad 2

Následující hierarchie zahrnuje rozhraní:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "H", Type = typeof(Helicopter))]

public class Truck: Vehicle
public class Semi: Truck, IRentableVehicle
public class Helicopter: Vehicle, IRentableVehicle

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=TypeOf(Vehicle),
   IsDefault:=True) > _
<InheritanceMapping(Code:="C", Type:=TypeOf(Car)) > _
<InheritanceMapping(Code:="T", Type:=TypeOf(Truck)) > _
<InheritanceMapping(Code:="S", Type:=TypeOf(Semi)) > _
<InheritanceMapping(Code:="H", Type:=TypeOf(Helicopter)) > _
Public Class Truck
       Inherits Vehicle
Public Class Semi
       InheritsTruck, IRentableVehicle
Public Class Helicopter
       InheritsVehicle, IRentableVehicle

Mezi možné dotazy patří:

C#

// Get commercial vehicles ordered by cost to rent.
db.Vehicles.OfType<IRentableVehicle>.OrderBy(cv => cv.RentalRate);

// Get all non-rentable vehicles
db.Vehicles.Where(v => !(v is IRentableVehicle));

Visual Basic

' Get commercial vehicles ordered by cost to rent.
Dim rentableVehicles = From veh In _ 
                       db.Vehicles.OfType(Of IRentableVehicle).OrderBy( _ 
                       Function(cv) cv.RentalRate)

' Get all non-rentable vehicles
Dim unrentableVehicles = From veh In _ 
                         db.Vehicles.OfType(Of Vehicle).Where( _ 
                         Function(uv) Not (TypeOf(uv) Is IRentableVehicle))

Pokročilá témata

Vytváření databází

Vzhledem k tomu, že třídy entit mají atributy popisující strukturu tabulek a sloupců relační databáze, je možné tyto informace použít k vytvoření nových instancí databáze. Můžete volat Metodu CreateDatabase() na DataContext, aby LINQ to SQL vytvořit novou instanci databáze se strukturou definovanou vašimi objekty. Může to být z mnoha důvodů: možná vytváříte aplikaci, která se automaticky nainstaluje do systému zákazníka, nebo klientskou aplikaci, která potřebuje k uložení stavu offline místní databázi. Pro tyto scénáře je ideální CreateDatabase() – zejména pokud je k dispozici známý zprostředkovatel dat, jako je SQL Server Express 2005.

Atributy dat však nemusí kódovat vše o existující databázové struktuře. Obsah uživatelem definovaných funkcí, uložených procedur, triggerů a omezení kontroly nejsou reprezentovány atributy. Funkce CreateDatabase() vytvoří repliku databáze pouze pomocí informací, které zná, což je struktura databáze a typy sloupců v každé tabulce. Pro celou řadu databází to ale stačí.

Níže je příklad vytvoření nové databáze s názvem MyDVDs.mdf:

C#

[Table(Name="DVDTable")]
public class DVD
{
   [Column(Id = true)]
   public string Title;
   [Column]
   public string Rating;
}

public class MyDVDs : DataContext
{
   public Table<DVD> DVDs;

   public MyDVDs(string connection) : base(connection) {}
}

Visual Basic

<Table(Name:="DVDTable")> _
Public Class DVD

   <Column(Id:=True)> _
   public Title As String
   <Column> _
   Public Rating As String
End Class

Public Class MyDVDs  
         Inherits DataContext

   Public DVDs As Table(Of DVD)

   Public Sub New(connection As String) 
End Class

Objektový model lze použít k vytvoření databáze pomocí SQL Server Express 2005 následujícím způsobem:

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");
db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = new MyDVDs("c:\mydvds.mdf")
db.CreateDatabase()

LINQ to SQL také poskytuje rozhraní API pro vyřazení existující databáze před vytvořením nové databáze. Výše uvedený kód pro vytvoření databáze lze upravit tak, aby nejprve zkontroloval existující verzi databáze pomocí DatabaseExists() a pak ho pomocí deleteDatabase() vyřadil.

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");

if (db.DatabaseExists()) {
   Console.WriteLine("Deleting old database...");
   db.DeleteDatabase();
}

db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = New MyDVDs("c:\mydvds.mdf")

If (db.DatabaseExists()) Then
   Console.WriteLine("Deleting old database...")
   db.DeleteDatabase()
End If

db.CreateDatabase()

Po volání CreateDatabase() může nová databáze přijímat dotazy a příkazy, jako je SubmitChanges(), a přidávat objekty do souboru MDF.

Je také možné použít CreateDatabase() s jinou skladovou položkou než SQL Server Express, a to buď pomocí souboru MDF, nebo jenom názvu katalogu. Vše závisí na tom, co použijete pro připojovací řetězec. Informace v připojovacím řetězci slouží k definování databáze, která bude existovat, nemusí nutně existovat. LINQ to SQL vyloví relevantní části informací a použije je k určení databáze, která se má vytvořit a na jakém serveru ji vytvořit. K tomu budete samozřejmě potřebovat práva správce databáze nebo ekvivalentní oprávnění na serveru.

Spolupráce s ADO.NET

LINQ to SQL je součástí ADO.NET řady technologií. Je založená na službách poskytovaných modelem poskytovatele ADO.NET, takže je možné kombinovat LINQ to SQL kód s existujícími aplikacemi ADO.NET.

Když vytváříte LINQ to SQL DataContext, můžete mu poskytnout existující připojení ADO.NET. Všechny operace se službou DataContext, včetně dotazů, budou používat připojení, které jste zadali. Pokud už bylo připojení otevřeno, LINQ to SQL bude respektovat vaše oprávnění k připojení a po dokončení připojení ho ponecháte beze spojení. Za normálních okolností LINQ to SQL ukončí své připojení hned po dokončení operace, pokud transakce není v oboru.

C#

SqlConnection con = new SqlConnection( ... );
con.Open(); 
...

// DataContext takes a connection
Northwind db = new Northwind(con);
...

var q =
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim con As SqlConnection = New SqlConnection( ... )
con.Open()
...

' DataContext takes a connection
Dim db As Northwind = new Northwind(con)
...

Dim q = From c In db.Customers _
        Where c.City = "London" _
        Select c

K připojení používanému vaším objektem DataContext můžete kdykoli přistupovat prostřednictvím vlastnosti Connection a zavřít ho sami.

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

DataContext můžete také zadat s vlastní databázovou transakcí v případě, že vaše aplikace již jednu iniciovala a vy si přejete, aby dataContext hrál spolu s ní.

C#

IDbTransaction = con.BeginTransaction();
...

db.Transaction = myTransaction;
db.SubmitChanges();
db.Transaction = null;

Visual Basic

Dim db As IDbTransaction = con.BeginTransaction()
...

db.Transaction = myTransaction
db.SubmitChanges()
db.Transaction = Nothing

Kdykoli je nastavena transakce , DataContext ji použije při každém vydání dotazu nebo spuštění příkazu. Až budete hotovi, nezapomeňte přiřadit vlastnost zpět na hodnotu null .

Nicméně upřednostňovanou metodou provádění transakcí s rozhraním .NET Framework je použití TransactionScope objektu. Umožňuje provádět distribuované transakce, které fungují napříč databázemi a dalšími správci prostředků rezidentní v paměti. Myšlenka je, že rozsahy transakcí začínají levně, pouze se propagují na plné distribuované transakce, pokud ve skutečnosti odkazují na více databází nebo více připojení v rámci rozsahu transakce.

C#

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Using ts As TransactionScope= New TransactionScope()
   db.SubmitChanges()
   ts.Complete()
End Using

Přímé spouštění příkazů SQL

Připojení a transakce nejsou jediným způsobem, jak můžete spolupracovat s ADO.NET. V některých případech může dojít k tomu, že dotazování nebo odeslání změn objektu DataContext není dostatečné pro specializovanou úlohu, kterou chcete provést. Za těchto okolností je možné použít DataContext k vydávání nezpracovaných příkazů SQL přímo do databáze.

Metoda ExecuteQuery() umožňuje spustit nezpracovaný dotaz SQL a převede výsledek dotazu přímo na objekty. Například za předpokladu, že data pro třídu Customer jsou rozložena do dvou tabulek customer1 a customer2, následující dotaz vrátí sekvenci objektů Customer .

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   @"select c1.custid as CustomerID, c2.custName as ContactName
      from customer1 as c1, customer2 as c2
      where c1.custid = c2.custid"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
          "select c1.custid as CustomerID, " & _
          "c2.custName as ContactName " & _
          "from customer1 as c1, customer2 as c2 "& _
          "where c1.custid = c2.custid" )

Pokud názvy sloupců v tabulkových výsledcích odpovídají vlastnostem sloupce vaší třídy entit LINQ to SQL materializují objekty z jakéhokoli dotazu SQL.

Metoda ExecuteQuery() také povoluje parametry. V následujícím kódu se spustí parametrizovaný dotaz:

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   "select contactname from customers where city = {0}",
   "London"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
   "select contactname from customers where city = {0}", _
   "London" )

Parametry jsou vyjádřeny v textu dotazu pomocí stejného složeného zápisu, který používá Console.WriteLine() a String.Format(). Řetězec String.Format() se ve skutečnosti volá v řetězci dotazu, který zadáte, a nahrazuje parametry s složenými složenými závorkami vygenerovanými názvy parametrů, jako je p0, @p1 ..., p(n).

Řešení konfliktů změn

Description

Ke konfliktu změn dojde, když se klient pokusí odeslat změny objektu a jedna nebo více hodnot použitých při kontrole aktualizací byly aktualizovány v databázi od doby, kdy je klient naposledy přečetl.

Poznámka Kontroly optimistické souběžnosti se účastní jenom členové namapované na UpdateCheck.Always nebo UpdateCheck.WhenChanged . U členů s označením UpdateCheck.Never se neprovádí žádná kontrola.

Řešení tohoto konfliktu zahrnuje zjištění, kteří členové objektu jsou v konfliktu, a následné rozhodnutí, co s tím dělat. Mějte na paměti, že optimistická souběžnost nemusí být nejlepší strategií ve vaší konkrétní situaci. Někdy je naprosto rozumné nechat poslední aktualizaci vyhrát.

Zjišťování, generování sestav a řešení konfliktů v LINQ to SQL

Řešení konfliktů je proces aktualizace konfliktní položky opětovným dotazováním databáze a odsouhlasení případných rozdílů. Při aktualizaci objektu má sledování změn původní staré hodnoty a nové hodnoty databáze. LINQ to SQL pak určí, jestli je objekt v konfliktu nebo ne. Pokud ano, LINQ to SQL určí, kterých členů se to týká. Pokud se hodnota nové databáze člena liší od původní původní (která byla použita pro kontrolu aktualizace, která selhala), jedná se o konflikt. Všechny konflikty členů se přidají do seznamu konfliktů.

Například v následujícím scénáři uživatel1 začne připravovat aktualizaci dotazem databáze na řádek. Před odesláním změn uživatelem User1 uživatel2 změnil databázi. Odeslání uživatele User1 selže, protože se změnily očekávané hodnoty pro sloupce B a C.

Konflikt aktualizace databáze

Uživatel Sloupec A Sloupec B Sloupec C
Původní stav Alfrédové Maria Sales
Uživatel 1 Alfred   Marketing
Uživatel 2   Mary Služba

V LINQ to SQL objekty, které se nepodaří aktualizovat kvůli konfliktům optimistické souběžnosti, způsobí výjimku (ChangeConflictException). Můžete určit, jestli má být výjimka vyvolána při prvním selhání, nebo jestli se mají pokusit o všechny aktualizace s případnými chybami, které jsou nahromaděny a hlášeny ve výjimce.

// [C#]
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
db.SubmitChanges(ConflictMode.ContinueOnConflict);
' [Visual Basic]
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
db.SubmitChanges(ConflictMode.ContinueOnConflict)

Při vyvolání výjimky poskytuje přístup ke kolekci ObjectChangeConflict . Podrobnosti jsou k dispozici pro každý konflikt (mapované na jeden neúspěšný pokus o aktualizaci), včetně přístupu k seznamu MemberConflicts . Každý konflikt člena se mapuje na jednoho člena v aktualizaci, který selhal při kontrole souběžnosti.

Zpracování konfliktů

V předchozím scénáři má Uživatel1 možnosti RefreshMode popsané níže pro odsouhlasení rozdílů před pokusem o opětovné odeslání. Ve všech případech se záznam v klientovi nejprve "aktualizuje" stažením aktualizovaných dat z databáze. Tato akce zajistí, že další pokus o aktualizaci nebude při stejných kontrolách souběžnosti neúspěšný.

Uživatel1 se zde rozhodne sloučit hodnoty databáze s aktuálními hodnotami klienta tak, aby se hodnoty databáze přepsaly pouze v případě, že aktuální sada změn také změnila tuto hodnotu. (Viz příklad 1 dále v této části.)

Ve výše uvedeném scénáři je po vyřešení konfliktu výsledek v databázi následující:

KeepChanges

  Sloupec A Sloupec B Sloupec C
KeepChanges Alfred (Uživatel 1) Mary (Uživatel 2) Marketing (uživatel 1)
  • Sloupec A: Zobrazí se změna uživatele 1 (Alfred).
  • Sloupec B: Zobrazí se změna uživatele2 (Mary). Tato hodnota byla sloučena, protože uživatel User1 ji nezměnil.
  • Sloupec C: Zobrazí se změna uživatele User1 (Marketing). Změna uživatele User2 (Služba) se nesloučí, protože uživatel User1 také změnil tuto položku.

Níže uživatel User1 zvolí přepsání všech databázových hodnot aktuálními hodnotami. (Viz Příklad 2 dále v této části.)

Po aktualizaci se odesílají změny uživatele User1. Výsledek v databázi je následující:

KeepCurrentValues

  Sloupec A Sloupec B Sloupec C
KeepCurrentValues Alfred (Uživatel 1) Maria (původní) Marketing (uživatel 1)
  • A: Zobrazí se změna uživatele User1 (Alfred).
  • Původní Maria zůstává; Změna uživatele User2 se zahodí.
  • Sloupec C: Zobrazí se změna uživatele User1 (Marketing). Změna uživatele User2 (Service) se zahodí.

V dalším scénáři uživatel User1 povolí, aby hodnoty databáze přepsaly aktuální hodnoty v klientovi. (Viz Příklad 3 dále v této části.)

Ve výše uvedeném scénáři je po vyřešení konfliktu výsledek v databázi následující:

OverwriteCurrentValues

  Sloupec A Sloupec B Sloupec C
OverwriteCurrentValues Alfreds (originál) Marie (uživatel 2) Služba (uživatel 2)
  • Col A: Původní hodnota (Alfreds) zůstává; Hodnota uživatele User1 (Alfred) se zahodí.
  • Sloupec B: Zobrazí se změna uživatele 2 (Mary).
  • Zobrazí se sloupec C: Změna uživatele2 (služba). Změna uživatele User1 (Marketing) se zahodí.

Po vyřešení konfliktů se můžete pokusit o opětovné odeslání. Vzhledem k tomu, že i tato druhá aktualizace může selhat, zvažte použití smyčky pro pokusy o aktualizaci.

Příklady

Následující úryvky kódu ukazují různé informační členy a techniky, které máte k dispozici pro zjišťování a řešení konfliktů členů.

Příklad 1

V tomto příkladu se konflikty řeší "automaticky". To znamená, že hodnoty databáze jsou sloučeny s aktuálními hodnotami klienta, pokud klient nezměnil také tuto hodnotu (KeepChanges). Neprobíhá žádná kontrola ani vlastní řešení konfliktů jednotlivých členů.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   //automerge database values into current for members
   //that client has not modified
   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges);
}
//submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict);

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   ' automerge database values into current for members
   ' that client has not modified   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges)
End Try
' submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict)

Příklad 2

V tomto příkladu se konflikty znovu vyřeší bez vlastního zpracování. Tentokrát se ale hodnoty databáze nesloučí s aktuálními hodnotami klienta.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ‘No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues)
   Next
End Try

Příklad 3

Ani tady se neprovádí žádné vlastní zpracování. V tomto případě se ale všechny hodnoty klienta aktualizují aktuálními hodnotami databáze.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict); 
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.OverwriteCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ' No database values are automerged into current
      cc.Resolve(RefreshMode. OverwriteCurrentValues)
   Next
End Try

Příklad 4

Tento příklad ukazuje způsob přístupu k informacím o entitě, která je v konfliktu.

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   Next
End Try

Příklad 5

Tento příklad přidá smyčku mezi jednotlivými členy. Tady můžete zadat vlastní zpracování libovolného člena.

Poznámka Přidání pomocí System.Reflection; a zadejte Informace o členech.

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
      foreach (MemberChangeConflict mc in         cc.MemberConflicts) {
         object currVal = mc.CurrentValue;
         object origVal = mc.OriginalValue;
         object databaseVal = mc.DatabaseValue;
         MemberInfo mi = mc. Member;
         Console.WriteLine("Member: {0}", mi.Name);
         Console.WriteLine("current value: {0}", currVal);
         Console.WriteLine("original value: {0}", origVal);
         Console.WriteLine("database value: {0}", databaseVal);
         Console.ReadLine();
      }
   }
}

Visual Basic

Try 
   user1.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   For Each mc As MemberChangeConflict In   cc.MemberConflicts
         Dim currVal As Object = mc.CurrentValue
         Dim origVal As Object = mc.OriginalValue
         Dim databaseVal As Object = mc.DatabaseValue
         Dim mi As MemberInfo = mc.Member
         Console.WriteLine("Member: {0}", mi.Name)
         Console.WriteLine("current value: {0}", currVal)
         Console.WriteLine("original value: {0}", origVal)
         Console.WriteLine("database value: {0}", databaseVal)
         Console.ReadLine()
      Next

   Next
End Try

Vyvolání uložených procedur

LINQ to SQL podporuje uložené procedury a uživatelem definované funkce. LINQ to SQL mapuje tyto databázové abstrakce na klientské objekty generované kódem, abyste k nim měli přístup způsobem se silnými typy z klientského kódu. Tyto metody můžete snadno objevit pomocí technologie IntelliSense a signatury metod se co nejvíce podobají signaturám procedur a funkcí definovaných v databázi. Sada výsledků vrácená voláním mapované procedury je kolekce silného typu. LINQ to SQL může automaticky generovat mapované metody, ale podporuje také ruční mapování v situacích, kdy se rozhodnete nepoužívat generování kódu.

LINQ to SQL mapuje uložené procedury a funkce na metody pomocí atributů. Atributy StoredProcedure, Parameter a Function podporují vlastnost Name a atribut Parameter také vlastnost DBType . Tady jsou dva příklady:

C#

   [StoredProcedure()]
   public IEnumerable<CustOrderHistResult> CustOrderHist(
      [Parameter(Name="CustomerID", DBType="NChar(5)")] string customerID) {

      IExecuteResult result = this.ExecuteMethodCall(this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);

      return ((IEnumerable<CustOrderHistResult>)(result.ReturnValue));
   }

[Function(Name="[dbo].[ConvertTemp]")]
public string ConvertTemp(string string) { ... }

Visual Basic

<StoredProcedure()> _
   Public Function CustOrderHist( _
         <Parameter(Name:="CustomerID", DBType:="NChar(5)")> _
         customerID As String) As IEnumerable(Of CustOrderHistResult)

         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
                 CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)

         Return CType(result.ReturnValue, IEnumerable(Of CustOrderHistResult))
   End Function

<Function(Name:="[dbo].[ConvertTemp]")> _
Public Function ConvertTemp(str As String) As String

Následující příklady ukazují mapování pro různé druhy uložených procedur.

Příklad 1

Následující uložená procedura přijímá jeden vstupní parametr a vrací celé číslo:

CREATE PROCEDURE GetCustomerOrderCount(@CustomerID nchar(5))
AS
Declare @count int
SELECT @count = COUNT(*) FROM ORDERS WHERE CustomerID = @CustomerID
RETURN @count

Mapovaná metoda by byla následující:

C#

[StoredProcedure(Name = "GetCustomerOrderCount")]
public int GetCustomerOrderCount(
      [Parameter(Name = "CustomerID")] string customerID) {
         IExecuteResult result = this.ExecuteMethodCall(this, 
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID); 
          return (int) result.ReturnValue;
}

Visual Basic

<StoredProcedure (Name:="GetCustomerOrderCount")> _
public Function GetCustomerOrderCount( _
      <Parameter(Name:= "CustomerID")> customerID As String) As Integer
         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)
          return CInt(result.ReturnValue)
End Function

Příklad 2

Pokud uložená procedura může vrátit více výsledných obrazců, návratový typ nemůže být silně zadán do jednoho obrazce projekce. V následujícím příkladu obrazec výsledku závisí na vstupu:

CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
   select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
   select OrderID, ShipName from orders

Mapovaná metoda je následující:

C#

      [StoredProcedure(Name = "VariableResultShapes")]
      [ResultType(typeof(Customer))]
      [ResultType(typeof(Order))]
      public IMultipleResults VariableResultShapes(System.Nullable<int> shape) {
         IExecuteResult result = this.ExecuteMethodCallWithMultipleResults(this,
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), shape);
         return (IMultipleResults) result.ReturnValue;
      }

Visual Basic

<StoredProcedure(Name:= "VariableResultShapes")> _
      <ResultType(typeof(Customer))> _
      <ResultType(typeof(Order))> _
   public VariableResultShapes(shape As Integer?) As IMultipleResults
      Dim result As IExecuteResult =
                ExecuteMethodCallWithMultipleResults(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), shape)
         return CType(result.ReturnValue, IMultipleResults)
      End Function

Tuto uloženou proceduru můžete použít takto:

C#

      IMultipleResults result = db.VariableResultShapes(1);
      foreach (Customer c in result.GetResult<Customer>()) {
         Console.WriteLine(c.CompanyName);
      }

      result = db.VariableResultShapes(2);
      foreach (Order o in result.GetResult<Order>()) {
         Console.WriteLine(o.OrderID);
      }           

Visual Basic

Dim result As IMultipleResults = db.VariableResultShapes(1)
      For Each c As Customer In result.GetResult(Of Customer)()
         Console.WriteLine(c.CompanyName)
      Next

      result = db.VariableResultShapes(2);
      For Each o As Order In result.GetResult(Of Order)()
         Console.WriteLine(o.OrderID)
      Next 
         
      }           

Tady potřebujete použít vzor GetResult , abyste získali enumerátor správného typu na základě vašich znalostí uložené procedury. LINQ to SQL může vygenerovat všechny možné typy projekce, ale nedokáže zjistit, v jakém pořadí se budou vracet. Jediný způsob, jak zjistit, které vygenerované typy projekce odpovídají mapované metodě, je použití vygenerovaných komentářů kódu u metod.

Příklad 3

Tady je T-SQL uložené procedury, která postupně vrací více obrazců výsledku:

CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers

LINQ to SQL by tento postup namapoval stejně jako v příkladu 2 výše. V tomto případě ale existují dvě sekvenční sady výsledků.

C#

[StoredProcedure(Name="MultipleResultTypesSequentially")]      
[ResultType(typeof(Product))]
[ResultType(typeof(Customer))]
public IMultipleResults MultipleResultTypesSequentially() {
   return ((IMultipleResults)(
      this.ExecuteMethodCallWithMultipleResults (this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue
      )
   );
}

Visual Basic

<StoredProcedure(Name:="MultipleResultTypesSequentially")> _
<ResultType(typeof(Customer))> _
<ResultType(typeof(Order))> _
public Function MultipleResultTypesSequentially() As IMultipleResults
   Return CType( ExecuteMethodCallWithMultipleResults (Me, _
         CType(MethodInfo.GetCurrentMethod(), MethodInfo)), _
         IMultipleResults).ReturnValue
      
End Function

Tuto uloženou proceduru můžete použít takto:

C#

      IMultipleResults sprocResults = db.MultipleResultTypesSequentially();

      //first read products
      foreach (Product p in sprocResults.GetResult<Product>()) {
         Console.WriteLine(p.ProductID);
      }

      //next read customers
      foreach (Customer c in sprocResults.GetResult<Customer>()){
         Console.WriteLine(c.CustomerID);
      }

Visual Basic

Dim sprocResults As IMultipleResults = db.MultipleResultTypesSequentially()

   ' first read products
   For Each P As Product In sprocResults.GetResult(Of Product)()
      Console.WriteLine(p.ProductID)
   Next

   ' next read customers
   For Each c As Customer c In sprocResults.GetResult(Of Customer)()
      Console.WriteLine(c.CustomerID) 
   Next

Příklad 4

LINQ to SQL mapuje out parametry na referenční parametry (klíčové slovo odkaz) a pro typy hodnot deklaruje parametr s možnou hodnotou null (například int?). Postup v následujícím příkladu přebírá jeden vstupní parametr a vrací out parametr .

CREATE PROCEDURE GetCustomerCompanyName(
   @customerID nchar(5),
   @companyName nvarchar(40) output
   )
AS
SELECT @companyName = CompanyName FROM Customers
WHERE CustomerID=@CustomerID

Mapovaná metoda je následující:

C#

      [StoredProcedure(Name = "GetCustomerCompanyName")]
      public int GetCustomerCompanyName(
         string customerID, ref string companyName) {

         IExecuteResult result =
            this.ExecuteMethodCall(this,
               ((MethodInfo)(MethodInfo.GetCurrentMethod())),
               customerID, companyName);

         companyName = (string)result.GetParameterValue(1);
         return (int)result.ReturnValue;
      }

Visual Basic

   <StoredProcedure(Name:="GetCustomerCompanyName")> _
      Public Function GetCustomerCompanyName( _
               customerID As String, ByRef companyName As String) As Integer

      Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID, _
               companyName)

         companyName = CStr(result.GetParameterValue(1))
         return CInt(result.ReturnValue)
      End Function

V tomto případě metoda nemá explicitní návratovou hodnotu, ale výchozí návratová hodnota je přesto mapována. Pro výstupní parametr se použije odpovídající výstupní parametr podle očekávání.

Výše uvedenou uloženou proceduru byste volali takto:

C#

string CompanyName = "";
string customerID = "ALFKI";
db.GetCustomerCompanyName(customerID, ref CompanyName);
Console.WriteLine(CompanyName);

Visual Basic

Dim CompanyName As String = ""
Dim customerID As String = "ALFKI"
db.GetCustomerCompanyName(customerID, CompanyName)
Console.WriteLine(CompanyName)

Uživatelem definované funkce

LINQ to SQL podporuje skalární i tabulkové funkce a podporuje vložený protějšek obou funkcí.

LINQ to SQL zpracovává vložená skalární volání podobně jako systémově definované funkce. Zamyslete se nad následujícím dotazem:

C#

var q =
   from p in db.Products
   select
      new {
         pid = p.ProductID,
         unitp = Math.Floor(p.UnitPrice.Value)
      };

Visual Basic

Dim productInfos = From prod In db.Products _
                   Select p.ProductID, price = Math.Floor(p.UnitPrice.Value)

Volání metody Math.Floor je zde přeloženo na volání systémové funkce "FLOOR". Stejným způsobem se volání funkce, která je namapovaná na funkci definovanou uživatelem, přeloží na volání funkce definované uživatelem v SQL.

Příklad 1

Tady je skalární uživatelem definovaná funkce (UDF) ReverseCustName(). V SQL Server může být funkce definována takto:

CREATE FUNCTION ReverseCustName(@string varchar(100))
RETURNS varchar(100)
AS
BEGIN
   DECLARE @custName varchar(100)
   -- Impl. left as exercise for the reader
   RETURN @custName
END

Pomocí následujícího kódu můžete namapovat metodu klienta definovanou ve třídě schématu na tuto funkci definovanou uživatelem. Všimněte si, že tělo metody vytvoří výraz, který zachycuje záměr volání metody a předá tento výraz dataContext k překladu a provedení. (K tomuto přímému spuštění dojde pouze v případě, že je volána funkce.)

C#

[Function(Name = "[dbo].[ReverseCustName]")]
public string ReverseCustName(string string1) {
   IExecuteResult result = this.ExecuteMethodCall(this,
      (MethodInfo)(MethodInfo.GetCurrentMethod())), string1);
   return (string) result.ReturnValue;
}

Visual Basic

Function(Name:= "[dbo].[ReverseCustName]")> _
Public Function ReverseCustName(string1 As String) As String

    Dim result As IExecuteResult = ExecuteMethodCall(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), string1)
   return CStr(result.ReturnValue)

Příklad 2

V následujícím dotazu vidíte vložené volání vygenerované metody UDF ReverseCustName. V tomto případě se funkce nespustí okamžitě. Jazyk SQL sestavený pro tento dotaz se přeloží na volání funkce definované uživatelem v databázi (viz kód SQL za dotazem).

C#

var q =
   from c in db.Customers
   select
      new {
         c.ContactName,
         Title = db.ReverseCustName(c.ContactTitle)
      };

Visual Basic

Dim customerInfos = From cust In db.Customers _
                    Select c.ContactName, _
                    Title = db.ReverseCustName(c.ContactTitle)



SELECT [t0].[ContactName],
   dbo.ReverseCustName([t0].[ContactTitle]) AS [Title]
FROM [Customers] AS [t0]

Když zavoláte stejnou funkci mimo dotaz, LINQ to SQL vytvoří jednoduchý dotaz z výrazu volání metody s následující syntaxí SQL (kde je parametr @p0 vázán na předanou konstantu):

V LINQ to SQL:

C#

string str = db.ReverseCustName("LINQ to SQL");

Visual Basic

Dim str As String = db.ReverseCustName("LINQ to SQL")

Převede na:

SELECT dbo.ReverseCustName(@p0)

Příklad 3

Funkce TVF (Table-Valued) vrací jednu sadu výsledků (na rozdíl od uložených procedur, které mohou vracet více obrazců výsledků). Vzhledem k tomu, že návratový typ TVF je tabulka, můžete tvf použít kdekoli v SQL, kde můžete použít tabulku, a s TVF pracovat stejným způsobem jako s tabulkou.

Zvažte následující SQL Server definici funkce vracející tabulku:

CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
   SELECT ProductID, UnitPrice
   FROM Products
   WHERE UnitPrice > @cost

Tato funkce explicitně uvádí, že vrací tabulku TABLE, takže vrácená struktura sady výsledků je implicitně definována. LINQ to SQL mapuje funkci následujícím způsobem:

C#

       [Function(Name = "[dbo].[ProductsCostingMoreThan]")]
      public IQueryable<Product> ProductsCostingMoreThan(
            System.Nullable<decimal> cost) {

         return this.CreateMethodCallQuery<Product>(this,
            (MethodInfo)MethodInfo.GetCurrentMethod(),
            cost);
      }

Visual Basic

   <Function(Name:="[dbo].[ProductsCostingMoreThan]")> _
      Public Function ProductsCostingMoreThan(
            cost As System.Nullable(Of Decimal)) As IQueryable(Of Product)

    Return CreateMethodCallQuery(Of Product)(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), cost)

Následující kód SQL ukazuje, že se můžete připojit k tabulce vrácené funkcí a jinak s ní zacházet stejně jako s jakoukoli jinou tabulkou:

SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID

V LINQ to SQL by se dotaz vykresloval následujícím způsobem (pomocí nové syntaxe "join"):

C#

var q =
   from p in db.ProductsCostingMoreThan(80.50m)
   join s in db.Products on p.ProductID equals s.ProductID
   select new {p.ProductID, s.UnitPrice};

Visual Basic

Dim productInfos = From costlyProd In db.ProductsCostingMoreThan(80.50m) _
                   Join prod In db.Products _
                   On costlyProd.ProductID Equals prod.ProductID _
                   Select costlyProd.ProductID, prod.UnitPrice

LINQ to SQL omezení uložených procedur

LINQ to SQL podporuje generování kódu pro uložené procedury, které vracejí staticky určené sady výsledků. Proto generátor kódu LINQ to SQL nepodporuje následující:

  • Uložené procedury, které k vrácení sad výsledků používají dynamické SQL. Pokud uložená procedura obsahuje podmíněnou logiku pro sestavení dynamického příkazu SQL, LINQ to SQL nemůže získat metadata pro sadu výsledků, protože dotaz použitý ke generování sady výsledků je neznámý až do doby běhu.
  • Uložené procedury, které vytvářejí výsledky založené na dočasné tabulce.

Nástroj Generátor tříd entit

Pokud máte existující databázi, není nutné vytvořit úplný objektový model ručně, jen abyste ho reprezentovali. K distribuci LINQ to SQL se dodává nástroj s názvem SQLMetal. Jedná se o nástroj příkazového řádku, který automatizuje úlohu vytváření tříd entit odvozením příslušných tříd z metadat databáze.

SqlMetal můžete použít k extrakci metadat SQL z databáze a vygenerování zdrojového souboru obsahujícího deklarace tříd entit. Případně můžete proces rozdělit do dvou kroků. Nejprve vygenerujete soubor XML představující metadata SQL a později tento soubor XML přeložíte do zdrojového souboru obsahujícího deklarace třídy. Tento proces rozdělení umožňuje zachovat metadata jako soubor, abyste ho mohli upravovat. Proces extrakce, který vytváří soubor, vytvoří několik odvození o odpovídajících názvech tříd a vlastností zadaných názvy tabulek a sloupců databáze. Možná bude nutné soubor XML upravit, aby generátor mohl přinést příjemnější výsledky nebo skrýt aspekty databáze, které nechcete mít v objektech.

Nejjednodušším scénářem použití SQLMetal je přímé generování tříd z existující databáze. Tady je postup, jak nástroj vyvolat:

C#

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.cs

Visual Basic

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.vb /language:vb

Spuštěním nástroje se vytvoří soubor Northwind.cs nebo .vb, který obsahuje objektový model vygenerovaný čtením metadat databáze. Toto použití funguje dobře, pokud se názvy tabulek v databázi podobají názvům objektů, které chcete vygenerovat. Pokud ne, budete chtít použít dvoustupňový přístup.

Pokud chcete dát nástroji SQLMetal pokyn, aby vygeneroval soubor DBML, použijte nástroj následujícím způsobem:

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize
   /xml:Northwind.dbml

Jakmile je soubor dbml vygenerován, můžete pokračovat a přidat k němu poznámky pomocí atributu třídy a vlastnosti , abyste popsali, jak se tabulky a sloupce mapují na třídy a vlastnosti. Jakmile dokončíte přidávání poznámek k souboru dbml, můžete spuštěním následujícího příkazu vygenerovat objektový model:

C#

SqlMetal /namespace:nwind /code:Northwind.cs Northwind.dbml

Visual Basic

SqlMetal /namespace:nwind /code:Northwind.vb Northwind.dbml /language:vb

Podpis použití SQLMetal je následující:

SqlMetal [options] [filename]

Následující tabulka obsahuje dostupné možnosti příkazového řádku pro SQLMetal.

Možnosti příkazového řádku pro SQLMetal

Možnost Popis
/server:<název> Označuje server, ke kterému se má připojit, aby bylo možné získat přístup k databázi.
/database:<name> Určuje název databáze, ze které se mají číst metadata.
/user:<name> Přihlašovací ID uživatele pro server.
/password:<name> Přihlašovací heslo k serveru.
/Zobrazení Extrahujte zobrazení databáze.
/Funkce Extrahujte databázové funkce.
/sprocs Extrahujte uložené procedury.
/code[:<název_souboru>] Označuje, že výstupem nástroje je zdrojový soubor deklarací tříd entit.
/language:<language> Použijte Visual Basic nebo C# (výchozí).
/xml[:<název_souboru>] Označuje, že výstupem nástrojů je soubor DBML popisující metadata databáze a první odhad názvů tříd a vlastností.
/map[:<název_souboru>] Označuje, že by se místo atributů měl použít externí soubor mapování.
/pluralize Označuje, že nástroj by měl provádět anglický jazyk pluralizaci / de-pluralizaci heuristice na názvy tabulek, aby se vytvořily odpovídající názvy tříd a vlastností.
/namespace:<name> Označuje obor názvů, ve kterém budou třídy entit generovány.
/timeout:<seconds> Hodnota časového limitu v sekundách, která se má použít pro databázové příkazy.

Poznámka Chcete-li extrahovat metadata ze souboru MDF, musíte zadat název souboru MDF za všemi ostatními možnostmi. Pokud není zadán žádný /server, předpokládá se localhost.

Referenční dokumentace k nástroji generátoru DBML

Soubor DBML (Database Mapping Language) je především popisem metadat SQL pro danou databázi. Nástroj SQLMetal ho extrahuje na základě metadat databáze. Stejný soubor používá také SQLMetal k vygenerování výchozího objektového modelu pro reprezentaci databáze.

Tady je prototypový příklad syntaxe DBML:

<?xml version="1.0" encoding="utf-16"?>
<Database Name="Northwind" EntityNamespace="Mappings.FunctionMapping"
   ContextNamespace="Mappings.FunctionMapping"
   Provider="System.Data.Linq.SqlClient.Sql2005Provider"
   xmlns="https://schemas.microsoft.com/dsltools/LINQ to SQLML">
   <Table Name="Categories">
      <Type Name="Category">
         <Column Name="CategoryID" Type="System.Int32"
            DbType="Int NOT NULL IDENTITY" IsReadOnly="False" 
            IsPrimaryKey="True" IsDbGenerated="True" CanBeNull="False" />
         <Column Name="CategoryName" Type="System.String"
            DbType="NVarChar(15) NOT NULL" CanBeNull="False" />
         <Column Name="Description" Type="System.String"
            DbType="NText" CanBeNull="True" UpdateCheck="Never" />
         <Column Name="Picture" Type="System.Byte[]"
            DbType="Image" CanBeNull="True" UpdateCheck="Never" />
         <Association Name="FK_Products_Categories" Member="Products"
            ThisKey="CategoryID" OtherKey="CategoryID"
            OtherTable="Products" DeleteRule="NO ACTION" />
      </Type>
   </Table>

   <Function Name="GetCustomerOrders">
      <Parameter Name="customerID" Type="System.String" DbType="NChar(5)" />
      <ElementType Name="GetCustomerOrdersResult">
         <Column Name="OrderID" Type="System.Int32"
            DbType="Int" CanBeNull="True" />
         <Column Name="ShipName" Type="System.String"
            DbType="NVarChar(40)" CanBeNull="True" />
         <Column Name="OrderDate" Type="System.DateTime"
            DbType="DateTime" CanBeNull="True" />
         <Column Name="Freight" Type="System.Decimal"
            DbType="Money" CanBeNull="True" />
      </ElementType>
   </Function>
</Database>

Prvky a jejich atributy jsou popsány následujícím způsobem.

Databáze

Toto je nejkrajnější prvek ve formátu XML. Tento prvek se volně mapuje na atribut Database na vygenerovaném DataContext.

Atributy databáze

Atribut Typ Výchozí Description
Název Řetězec Žádný Název databáze. Pokud existuje a pokud generuje DataContext, připojí k němu atribut Database s tímto názvem. Používá se také jako název třídy DataContext , pokud atribut třídy není k dispozici.
EntityNamespace Silná Žádný Výchozí obor názvů pro třídy generované z elementů Type v elementech tabulky. Pokud zde není zadán žádný obor názvů, třídy entit se vygenerují v kořenovém oboru názvů.
ContextNamespace Řetězec Žádný Výchozí obor názvů pro vygenerovanou třídu DataContext . Pokud zde není zadán žádný obor názvů, že DataContext třída je generována v kořenovém oboru názvů.
Třída Řetězec Database.Name Název vygenerované třídy DataContext . Pokud neexistuje, použijte atribut Name elementu Database.
AccessModifier AccessModifier Veřejná Úroveň přístupnosti vygenerované Třídy DataContext . Platné hodnoty jsou Veřejné, Chráněné, Interní a Soukromé.
BaseType Řetězec "System.Data.Linq.DataContext" Základní typ Třídy DataContext .
Poskytovatel Řetězec "System.Data.Linq.SqlClient.Sql2005Provider" Jako zprostředkovatel DataContext použijte jako výchozí zprostředkovatele Sql2005.
Externí mapování Logická hodnota Ne Určete, jestli se DBML používá ke generování externího souboru mapování.
Serializace SerializationMode SerializationMode.None Určete, zda jsou vygenerované třídy DataContext a entity serializovatelné.

Atributy Sub-Element databáze

Sub-Element Typ prvku Rozsah výskytů Description
<Tabulka> Tabulka Bez vazby 0 Představuje SQL Server tabulku nebo zobrazení, které bude mapováno buď na jeden typ, nebo na hierarchii dědičnosti.
<Funkce> Funkce Bez vazby 0 Představuje SQL Server uloženou proceduru nebo funkci databáze, která bude mapována na metodu vygenerované DataContext třídy.
<Připojení> Připojení 0-1 Představuje připojení k databázi, které bude tento DataContext používat.

Tabulka

Tento element představuje tabulku databáze (nebo zobrazení), která bude mapována buď na jeden typ, nebo na hierarchii dědičnosti. Tento prvek se volně mapuje na atribut Table ve vygenerované třídě entity.

Atributy tabulky

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název tabulky v databázi. V případě potřeby slouží jako základ výchozího názvu adaptéru tabulky.
Člen Řetězec Table.Name Název pole člena vygenerovaného pro tuto tabulku v rámci Třídy DataContext .
AccessModifier AccessModifier Veřejná Úroveň přístupnosti odkazu na tabulku<T> v rámci objektu DataContext. Platné hodnoty jsou Veřejné, Chráněné, Interní a Soukromé.

Atributy Sub-Element tabulky

Sub-Element Typ prvku Rozsah výskytů Description
<Typ> Typ 1-1 Představuje hierarchii typu nebo dědičnosti mapovanou na tuto tabulku.
<InsertFunction> TableFunction 0-1 Metoda pro vložení. Když je k dispozici, vygeneruje se metoda InsertT .
<UpdateFunction> TableFunction 0-1 Metoda aktualizace. Když je k dispozici, vygeneruje se metoda UpdateT .
<DeleteFunction> TableFunction 0-1 Metoda pro odstranění. Když je k dispozici, vygeneruje se metoda DeleteT .

Typ

Tento prvek představuje definici typu pro obrazec výsledku tabulky nebo uložené procedury. Tím se kód-gen změní na nový typ CLR se zadanými sloupci a přidruženími.

Typ může také představovat součást hierarchie dědičnosti s více typy mapování na stejnou tabulku. V tomto případě jsou prvky Type vnořené tak, aby reprezentovaly vztahy dědičnosti nadřazenosti a podřízenosti a jsou v databázi odlišeny zadaným kódem dědičnosti .

Atributy typů

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název typu CLR, který se má vygenerovat.
Kód dědičnosti Řetězec Žádné Pokud se tento typ účastní dědičnosti, může mít přidružený kód dědičnosti pro rozlišení typů CLR při načítání řádků z tabulky. Typ, jehož DědičnostCode odpovídá hodnotě sloupce IsDiscriminator se používá k vytvoření instance načteného objektu. Pokud kód dědičnosti neexistuje, vygenerovaná třída entity je abstraktní.
IsInheritanceDefault Logická hodnota Ne Pokud to platí pro typ v hierarchii dědičnosti, použije se tento typ při načítání řádků, které se neshodují s žádnými definovanými kódy dědičnosti.
AccessModifier AccessModifier Veřejná Úroveň přístupnosti vytvářeného typu CLR. Platné hodnoty jsou: Veřejné, Chráněné, Interní a Soukromé.
Id Řetězec Žádné Typ může mít jedinečné ID. ID typu lze použít v jiných tabulkách nebo funkcích. ID se zobrazí pouze v souboru DBML, nikoli v objektovém modelu.
Idref Řetězec Žádné IdRef se používá k odkazovat na ID jiného typu. Pokud je IdRef přítomen v elementu type, musí element type obsahovat pouze idRef informace. IdRef se zobrazí pouze v souboru DBML, nikoli v objektovém modelu.

Zadejte atributy Sub-Element.

Sub-Element Typ prvku Rozsah výskytů Description
<Sloupcový> Sloupec Bez vazby 0 Představuje vlastnost v rámci tohoto typu, která bude vázána na pole v tabulce tohoto typu.
<Přidružení> Přidružení Bez vazby 0 Představuje vlastnost v rámci tohoto typu, která bude vázána na jeden konec relace cizího klíče mezi tabulkami.
<Typ> Podtyp Bez vazby 0 Představuje podtypy tohoto typu v hierarchii dědičnosti.

Podtyp

Tento prvek představuje odvozený typ v hierarchii dědičnosti. Ten se vygeneruje do nového typu CLR se sloupci a přidruženími zadanými v tomto typu. Pro podtypy se negenerují žádné atributy dědičnosti.

Ve srovnání s typem nemají elementy SubTypeAccessModifier , protože všechny odvozené typy musí být veřejné. Podtypy nemohou být znovu použity jinými tabulkami a funkcemi, takže v nich není žádné ID a IdRef .

Atributy podtypu

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název typu CLR, který se má vygenerovat.
Kód dědičnosti Řetězec Žádný Pokud se tento typ účastní dědičnosti, může mít přidružený kód dědičnosti pro rozlišení typů CLR při načítání řádků z tabulky. Typ, jehož DědičnostCode odpovídá hodnotě sloupce IsDiscriminator se používá k vytvoření instance načteného objektu. Pokud kód dědičnosti neexistuje, vygenerovaná třída entity je abstraktní.
IsInheritanceDefault Logická hodnota Ne Pokud to platí pro typ v hierarchii dědičnosti, použije se tento typ při načítání řádků, které se neshodují s žádnými definovanými kódy dědičnosti.

Atributy Sub-Element podtypu

Sub-Element Typ prvku Rozsah výskytů Description
<Sloupcový> Sloupec Bez vazby 0 Představuje vlastnost v rámci tohoto typu, která bude vázána na pole v tabulce tohoto typu.
<Přidružení> Přidružení Bez vazby 0 Představuje vlastnost v rámci tohoto typu, která bude vázána na jeden konec relace cizího klíče mezi tabulkami.
<Typ> Podtyp Bez vazby 0 Představuje podtypy tohoto typu v hierarchii dědičnosti.

Sloupec

Tento element představuje sloupec v tabulce, který je mapován na vlastnost (a záložní pole) v rámci třídy. Pro žádný konec vztahu cizího klíče však nebude k dispozici žádný element Column , protože je zcela reprezentován (na obou koncích) prvky Association.

Atributy sloupce

Atributy Typ Výchozí Description
Název Řetězec Žádný Název pole databáze, na které bude tento sloupec mapovat.
Člen Řetězec Název Název vlastnosti CLR, která se má vygenerovat u obsahujícího typu.
Storage Řetězec _Členské Název privátního záložního pole CLR, do kterého se uloží hodnota tohoto sloupce. Při serializaci neodstraňujte úložiště , a to ani v případě, že je výchozí.
AccessModifier AccessModifier Veřejná Úroveň přístupnosti vytvářené vlastnosti CLR. Platné hodnoty jsou: Veřejné, Chráněné, Interní a Soukromé.
Typ Řetězec (povinné) Název typu vytvářené vlastnosti CLR i záložního pole. Může se jednat o cokoli od plně kvalifikovaného názvu až po pouze přímý název třídy, pokud bude název nakonec v oboru při kompilaci vygenerovaného kódu.
Dbtype Řetězec Žádný Úplný typ SQL Server (včetně poznámek, například NOT NULL) pro tento sloupec. Používá ho LINQ to SQL, pokud ho zadáte k optimalizaci vygenerovaných dotazů a konkrétnější při vytváření databáze CreateDatabase(). Vždy serializovat DbType.
IsReadOnly Logická hodnota Ne Pokud je nastaven IsReadOnly , setter vlastností není vytvořen, což znamená, že lidé nemohou změnit hodnotu tohoto sloupce pomocí tohoto objektu.
IsPrimaryKey Logická hodnota Ne Označuje, že tento sloupec je součástí primárního klíče tabulky. Tyto informace jsou nutné k tomu, aby LINQ to SQL fungoval správně.
IsDbGenerated Logická hodnota Ne Označuje, že data tohoto pole jsou generována databází. To platí především pro pole automatického číslo a pro počítaná pole. Přiřazení hodnot k těmto polím není smysluplné, a proto jsou automaticky IsReadOnly.
CanBeNull Logická hodnota Žádný Označuje, že hodnota může obsahovat hodnotu null. Pokud chcete skutečně použít hodnoty null v CLR, musíte stále zadat ClrType jako Nullable<T>.
Kontrola aktualizací Kontrola aktualizací Vždy (pokud aspoň jeden další člen nemá nastavený IsVersion , pak Nikdy) Určuje, jestli LINQ to SQL použít tento sloupec při zjišťování konfliktů optimistické souběžnosti. Za normálních okolností se ve výchozím nastavení účastní všechny sloupce, pokud neexistuje sloupec IsVersion , který se pak účastní sám. Může to být : Always, Never nebo WhenChanged (to znamená, že sloupec se účastní, pokud se změnila jeho vlastní hodnota).
IsDiskriminátor Logická hodnota Ne Určuje, zda toto pole obsahuje diskriminující kód použitý pro volbu mezi typy v hierarchii dědičnosti.
Výraz Řetězec Žádný Nemá vliv na operaci LINQ to SQL, ale používá se během .CreateDatabase() jako nezpracovaný výraz SQL představující výraz počítaný sloupec.
Isversion Logická hodnota Ne Označuje, že toto pole představuje pole TIMESTAMP v SQL Server, které se automaticky aktualizuje při každé změně řádku. Toto pole pak můžete použít k zajištění efektivnějšího zjišťování konfliktů optimistické souběžnosti.
IsDelayLoaded Logická hodnota Ne Označuje, že tento sloupec by neměl být načten okamžitě při materializaci objektu, ale pouze při prvním přístupu k příslušné vlastnosti. To je užitečné pro velká pole poznámek nebo binární data v řádku, která nejsou vždy potřeba.
Autosync Autosync If (IsDbGenerated && IsPrimaryKey) OnInsert;

Else if (IsDbGenerated) Always

Else Never

Určuje, jestli se sloupec automaticky synchronizuje z hodnoty vygenerované databází. Platné hodnoty pro tuto značku jsou: OnInsert, Always a Never.

Přidružení

Tento prvek představuje konec vztahu cizího klíče. U relací 1:N to bude EntitySet<T> na jedné straně a EntityRef<T> na straně N. U relací 1:1 to bude EntityRef<T> na obou stranách.

Všimněte si, že není nutné mít na obou stranách přidružení položku Přidružení . V tomto případě se vlastnost vygeneruje pouze na straně, která má položku (tvoří jednosměrnou relaci).

Atributy přidružení

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název relace (obvykle název omezení cizího klíče). To může být technicky volitelné, ale vždy by měl být generován kódem, aby se zabránilo nejednoznačnosti, když existuje více relací mezi stejnými dvěma tabulkami.
Člen Řetězec Název Název vlastnosti CLR, která se má vygenerovat na této straně přidružení.
Storage Řetězec Pokud OneToMany a not IsForeignKey:

_OtherTable

Jiného:

_TypeName(jiná tabulka)

Název privátního záložního pole CLR, do kterého se uloží hodnota tohoto sloupce.
AccessModifier AccessModifier Veřejná Úroveň přístupnosti vytvářené vlastnosti CLR. Platné hodnoty jsou Veřejné, Chráněné, Interní a Soukromé.
ThisKey Řetězec Vlastnost IsIdentity v rámci třídy obsahující Čárkami oddělený seznam klíčů na této straně přidružení
Jiná tabulka Řetězec Viz popis. Tabulka na druhém konci relace. Za normálních okolností to může být určeno modulem runtime LINQ to SQL odpovídajícími názvy relací, ale u jednosměrných přidružení nebo anonymních přidružení to není možné.
Jiný klíč Řetězec Primární klíče v cizí třídě Čárkami oddělený seznam klíčů na druhé straně přidružení.
IsForeignKey Logická hodnota Ne Označuje, jestli se jedná o podřízenou stranu relace, stranu N 1:N.
Relationshiptype Relationshiptype OneToMany Určuje, zda uživatel tvrdí, že data související s tímto přidružením splňují kritéria 1:1, nebo odpovídají obecnějšímu případu 1:N. U 1:1 uživatel tvrdí, že pro každý řádek na straně primárního klíče ("jeden") je na straně cizího klíče ("mnoho") pouze jeden řádek. To způsobí, že se EntityRef<T> vygeneruje na straně "jedna" místo EntitySet<T>. Platné hodnoty jsou OneToOne a OneToMany.
Odstranitrule Řetězec Žádný Používá se k přidání chování při odstraňování do tohoto přidružení. Například "CASCADE" přidá do relace FK "ONDELETECASCADE". Pokud je nastavená hodnota null, nepřidá se žádné chování při odstraňování.

Funkce

Tento prvek představuje uloženou proceduru nebo funkci databáze. Pro každý uzel funkce je vygenerována metoda ve třídě DataContext .

Atributy funkce

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název uložené procedury v databázi.
Metoda Řetězec Metoda Název metody CLR, která se má vygenerovat, která umožňuje vyvolání uložené procedury. Výchozí název pro metodu obsahuje například [dbo]. Odstraněný název.
AccessModifier AccessModifier Veřejná Úroveň přístupnosti uložené metody procedury. Platné hodnoty jsou Veřejné, Chráněné, Interní a Soukromé.
HasMultipleResults Logická hodnota # of Types > 1 Určuje, jestli uložená procedura reprezentovaná tímto uzlem funkce vrací více sad výsledků. Každá sada výsledků je tabulkový obrazec, který může být buď existující typ , nebo sada sloupců. V druhém případě se pro sadu sloupců vytvoří uzel Typ .
IsComposable Logická hodnota Ne Určuje, jestli je možné funkci nebo uloženou proceduru sestavit v LINQ to SQL dotazech. Mohou být složeny pouze funkce databáze, které nevracely hodnotu void.

Atributy Sub-Element funkcí

Sub-Element Typy elementů Rozsah výskytů Description
<Parametr> Parametr 0 – bez vazby Představuje parametry in a out této uložené procedury.
<Typ elementu> Typ 0 – bez vazby Představuje tabulkové obrazce, které může odpovídající uložená procedura vrátit.
<Vrácení> Vrácení 0-1 Vrácený skalární typ této funkce databáze nebo uložené procedury. Pokud je hodnota Return null, vrátí funkce void. Funkce nemůže mít Return i ElementType.

TableFunction

Tento element představuje funkce přepsání CUD pro tabulky. Návrhář LINQ to SQL umožňuje vytváření metod přepsání Insert, Update a Delete pro LINQ TO SQL a umožňuje mapování názvů vlastností entit na názvy parametrů uložené procedury.

Název metody pro funkce CUD je opravený, takže pro elementy TableFunction neexistuje žádný atribut Method v DBML. Například pro tabulku Customer jsou metody CUD pojmenované jako InsertCustomer, UpdateCustomer a DeleteCustomer.

Tabulková funkce nemůže vrátit tabulkový obrazec, takže v elementu TableFunction není žádný atribut ElementType.

Atributy funkce TableFunction

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název uložené procedury v databázi.
AccessModifier AccessModifier Privátní Úroveň přístupnosti metody uložené procedury. Platné hodnoty jsou Veřejné, Chráněné, Interní a Soukromé.
HasMultipleResults Logická hodnota # of Types > 1 Určuje, jestli uložená procedura reprezentovaná tímto uzlem funkce vrací více sad výsledků. Každá sada výsledků je tabulkový obrazec, který může být buď existující typ , nebo sada sloupců. V druhém případě se pro sadu sloupců vytvoří uzel Typ .
IsComposable Logická hodnota Ne Určuje, jestli je možné funkci nebo uloženou proceduru sestavit v LINQ to SQL dotazech. Mohou být složeny pouze funkce databáze, které nevracely hodnotu void.

Atributy Sub-Element funkce TableFunction

Sub-Elements Typ elementu Rozsah výskytů Description
<Parametr> TableFunctionParameter 0 – bez vazby Představuje parametry in a out této funkce tabulky.
<Vrácení> TableFunctionReturn 0-1 Vrácený skalární typ této funkce tabulky. Pokud je hodnota Return null, vrátí funkce void.

Parametr

Tento prvek představuje uloženou proceduru nebo parametr funkce. Parametry můžou předávat data.

Atributy parametrů

Atribut Typ Výchozí Popisy
Název Řetězec (povinné) Název databáze uloženého parametru proc/function.
Parametr Řetězec Název Název CLR parametru metody.
  Řetězec (povinné) Název CLR parametru metody.
Dbtype Řetězec Žádný Typ databáze uloženého parametru proc/function.
Směr ParametrDirection V Směr, kterým parametr prochází. Může to být in,out a inout.

Vrácení

Tento prvek představuje návratový typ uložené procedury/funkce.

Návratové atributy

Atribut Typ Výchozí Description
Typ Řetězec (povinné) Typ CLR uloženého výsledku proc/function.
Dbtype Řetězec Žádný Typ databáze uloženého výsledku funkce

TableFunctionParameter

Tento prvek představuje parametr funkce CUD. Parametry můžou předávat data. Každý parametr je mapován na sloupec Tabulky , do kterého patří tato funkce CUD. V tomto elementu není žádné atributy Type nebo DbType , protože informace o typu lze získat ze sloupce, na který se parametr mapuje.

Atributy TableFunctionParameter

Atribut Typ Výchozí Description
Název Řetězec (povinné) Název databáze parametru funkce CUD.
Parametr Řetězec Název Název CLR parametru metody.
Sloupec Řetězec Název Název sloupce, na který se tento parametr mapuje.
Směr ParametrDirection V Směr, kterým parametr prochází. Může to být in,out nebo inout.
Verze Verze Current Určuje , jestli PropertyName odkazuje na aktuální nebo původní verzi daného sloupce. Platí pouze během přepsání aktualizací . Může být Aktuální nebo Původní.

TableFunctionReturn

Tento prvek představuje návratový typ funkce CUD. Ve skutečnosti obsahuje pouze název sloupce, který je namapován na výsledek funkce CUD. Informace o typu návratu lze získat ze sloupce .

Atribut TableFunctionReturn

Attrobite Typ Výchozí Description
Sloupec Řetězec Žádný Název sloupce, na který se návrat mapuje.

Připojení

Tento element představuje výchozí parametry připojení k databázi. To umožňuje vytvořit výchozí konstruktor pro typ DataContext , který už ví, jak se připojit k databázi.

Existují dva typy výchozích připojení, jeden s přímým připojovacím řetězcem a druhý, který čte z App.Settings.

Atributy připojení

Atribut Typ Výchozí Description
UseApplicationSettings Logická hodnota Ne Určuje, jestli se má použít soubor App.Settings nebo získat nastaveníaplikace z přímého připojovacího řetězce.
Connectionstring Řetězec Žádný Připojovací řetězec, který se odešle zprostředkovateli dat SQL.
SettingsObjectName Řetězec Nastavení Objekt App.Settings, ze který se mají načíst vlastnosti.
SettingsPropertyName Řetězec Connectionstring App.Settings Vlastnost, která obsahuje ConnectionString.

Vícevrstvé entity

Ve dvouvrstvých aplikacích zpracovává dotazy a aktualizace jeden objekt DataContext . U aplikací s dalšími úrovněmi je však často nutné pro dotazy a aktualizace používat samostatné instance DataContext . Například v případě ASP.NET aplikací se dotazování a aktualizace provádí pro samostatné požadavky na webový server. Proto je nepraktické používat stejnou instanci DataContext napříč několika požadavky. V takových případech musí být instance DataContext schopná aktualizovat objekty, které nenačetla. Podpora vícevrstvých entit v LINQ to SQL takovou možnost poskytuje prostřednictvím metody Attach().

Tady je příklad změny objektu Customer pomocí jiné instance DataContext :

C#

// Customer entity changed on another tier – for example, through a browser
// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create a new entity for applying changes
Customer C2 = new Customer();
C2.CustomerID ="NewCustID";

// Set other properties needed for optimistic concurrency check
C2.CompanyName = "New Company Name Co.";

...

// Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2);

// Now apply the changes
C2.ContactName = "Mary Anders";

// DataContext now knows how to update the customer
db2.SubmitChanges();

Visual Basic

' Customer entity changed on another tier – for example, through a browser
' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create a new entity for applying changes
Dim C2 As New Customer()
C2.CustomerID =”NewCustID”

' Set other properties needed for optimistic concurrency check
C2.CompanyName = ”New Company Name Co.”

...

' Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2)

' Now apply the changes
C2.ContactName = "Mary Anders"

' DataContext now knows how to update the customer
db2.SubmitChanges()

Ve vícevrstvých aplikacích se kvůli jednoduchosti, interoperabilitě nebo ochraně osobních údajů často celá entita neposílají mezi úrovněmi. Dodavatel může například definovat kontrakt dat pro webovou službu, který se liší od entity Order použité na střední vrstvě. Podobně webová stránka může zobrazit pouze podmnožinu členů entity Zaměstnanec. Proto je vícevrstvová podpora navržená tak, aby těmto případům vyhovovala. Před voláním Attach() je potřeba přenášet mezi vrstvami a nastavit pouze členy patřící do jedné nebo více následujících kategorií.

  1. Členové, kteří jsou součástí identity entity.
  2. Členové, kteří byli změněni.
  3. Členové, kteří se účastní optimistické kontroly souběžnosti.

Pokud se pro optimistickou kontrolu souběžnosti používá sloupec s časovým razítkem nebo číslem verze, musí být před voláním attach() nastaven odpovídající člen. Před voláním Attach() není nutné nastavit hodnoty pro ostatní členy. LINQ to SQL používá minimální aktualizace s optimistickými kontrolami souběžnosti. To znamená, že člen, u kterého není nastavená nebo nekontrolovaná optimistická souběžnost, bude ignorován.

Původní hodnoty vyžadované pro optimistické kontroly souběžnosti se můžou zachovat pomocí různých mechanismů mimo rozsah rozhraní API LINQ to SQL. Aplikace ASP.NET může používat stav zobrazení (nebo ovládací prvek, který používá stav zobrazení). Webová služba může použít DataContract pro metodu aktualizace k zajištění, že původní hodnoty jsou k dispozici pro zpracování aktualizací. V zájmu interoperability a obecnosti LINQ to SQL nediktuje tvar dat vyměňovaných mezi úrovněmi ani mechanismy používané pro opětovné zařazení původních hodnot.

Entity pro vložení a odstranění nevyžadují metodu Attach(). Metody používané pro dvouvrstvé aplikace – Table.Add() a Table.Remove() lze použít pro vložení a odstranění. Stejně jako v případě dvouvrstvých aktualizací zodpovídá za zpracování omezení cizího klíče uživatel. Zákazníka s objednávkami nelze jednoduše odebrat bez zpracování objednávek, pokud v databázi existuje omezení cizího klíče, které brání odstranění zákazníka s objednávkami.

LINQ to SQL také přechodně zpracovává přílohy entit pro aktualizace. Uživatel v podstatě podle potřeby vytvoří graf objektů před aktualizací a zavolá Attach(). Všechny změny se pak dají "přehrát" v připojeném grafu, abyste dosáhli potřebných aktualizací, jak je znázorněno níže:

C#

Northwind db1 = new Northwind(…);
// Assume Customer c1 and related Orders o1, o2 are retrieved

// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create new entities for applying changes
Customer c2 = new Customer();
c2.CustomerID = c.CustomerID;
Order o2 = new Order();
o2.OrderID = ...;

c2.Orders.Add(o2);

// Add other related objects needed for updates

// Set properties needed for optimistic concurrency check
...
// Order o1 to be deleted
Order o1 = new Order();
o1.OrderID = ...;

// Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2);
// Now "replay" all the changes

// Updates
c2.ContactName = ...;
o2.ShipAddress = ...;

// New object for insertion
Order o3 = new Order();
o3.OrderID = ...;
c2.Orders.Add(o3);

// Remove order o1
db2.Orders.Remove(o1);

// DataContext now knows how to do update/insert/delete
db2.SubmitChanges();

Visual Basic

Dim db1 As Northwind = New Northwind(…)
' Assume Customer c1 and related Orders o1, o2 are retrieved

' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create new entities for applying changes
Customer c2 = new Customer()
c2.CustomerID = c.CustomerID
Dim o2 As Order = New Order()
o2.OrderID = ...

c2.Orders.Add(o2)

' Add other related objects needed for updates

' Set properties needed for optimistic concurrency check
...
' Order o1 to be deleted
Dim o1 As Order = New Order()
o1.OrderID = ...

' Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2)
' Now "replay" all the changes

' Updates
c2.ContactName = ...
o2.ShipAddress = ...

' New object for insertion
Dim o3 As Order = New Order()
o3.OrderID = ...
c2.Orders.Add(o3)

' Remove order o1
db2.Orders.Remove(o1)

' DataContext now knows how to do update/insert/delete
db2.SubmitChanges()

Externí mapování

Kromě mapování na základě atributů LINQ to SQL podporuje také externí mapování. Nejběžnější formou externího mapování je soubor XML. Soubory mapování umožňují další scénáře, ve kterých je žádoucí oddělit mapování od kódu.

DataContext poskytuje další konstruktor pro poskytování MappingSource. Jednou z forem MappingSource je XmlMappingSource , který lze vytvořit ze souboru mapování XML.

Tady je příklad použití souboru mapování:

C#

String path = @"C:\Mapping\NorthwindMapping.xml";
XmlMappingSource prodMapping = 
   XmlMappingSource.FromXml(File.ReadAllText(path));
Northwind db = new Northwind(
   @"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf",
   prodMapping
   );

Visual Basic

Dim path As String = "C:\Mapping\NorthwindMapping.xml"
Dim prodMapping As XmlMappingSource = _
   XmlMappingSource.FromXml(File.ReadAllText(path))
Dim db As Northwind = New Northwind( _
   "Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf", _
   prodMapping   )

Tady je odpovídající fragment kódu ze souboru mapování zobrazující mapování pro třídu Product . Zobrazuje třídu Product v mapování oboru názvů na tabulku Products v databázi Northwind . Elementy a atributy jsou konzistentní s názvy a parametry atributů.

<?xml version="1.0" encoding="utf-8"?>
<Database xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="Northwind"
   ProviderType="System.Data.Linq.SqlClient.Sql2005Provider">
   <Table Name="Products">
      <Type Name="Mappings.FunctionMapping.Product">
         <Column Name="ProductID" Member="ProductID" Storage="_ProductID"
            DbType="Int NOT NULL IDENTITY" IsPrimaryKey="True"
            IsDBGenerated="True" AutoSync="OnInsert" />
         <Column Name="ProductName" Member="ProductName" Storage="_ProductName"
            DbType="NVarChar(40) NOT NULL" CanBeNull="False" />
         <Column Name="SupplierID" Member="SupplierID" Storage="_SupplierID"
            DbType="Int" />
         <Column Name="CategoryID" Member="CategoryID" Storage="_CategoryID"
            DbType="Int" />
         <Column Name="QuantityPerUnit" Member="QuantityPerUnit"
            Storage="_QuantityPerUnit" DbType="NVarChar(20)" />
         <Column Name="UnitPrice" Member="UnitPrice" Storage="_UnitPrice"
            DbType="Money" />
         <Column Name="UnitsInStock" Member="UnitsInStock" Storage="_UnitsInStock"
            DbType="SmallInt" />
         <Column Name="UnitsOnOrder" Member="UnitsOnOrder" Storage="_UnitsOnOrder"
            DbType="SmallInt" />
         <Column Name="ReorderLevel" Member="ReorderLevel" Storage="_ReorderLevel"
            DbType="SmallInt" />
         <Column Name="Discontinued" Member="Discontinued" Storage="_Discontinued"
            DbType="Bit NOT NULL" />
         <Association Name="FK_Order_Details_Products" Member="OrderDetails"
            Storage="_OrderDetails" ThisKey="ProductID" OtherTable="Order Details"
            OtherKey="ProductID" DeleteRule="NO ACTION" />
         <Association Name="FK_Products_Categories" Member="Category"
            Storage="_Category" ThisKey="CategoryID" OtherTable="Categories"
            OtherKey="CategoryID" IsForeignKey="True" />
         <Association Name="FK_Products_Suppliers" Member="Supplier"
            Storage="_Supplier" ThisKey="SupplierID" OtherTable="Suppliers"
            OtherKey="SupplierID" IsForeignKey="True" />
      </Type>
   </Table>

</Database>

Podpora funkcí a poznámky k rozhraní NET Framework

Následující odstavce poskytují základní informace o podpoře typů LINQ to SQL a rozdílech od rozhraní .NET Framework.

Primitivní typy

Implementována

  • Aritmetické a relační operátory
  • Operátory směny:<< a >>
  • Převod mezi znakem a číslicí se provádí pomocí unicode/NCHAR, v opačném případě se použije funkce CONVERT SQL.

Není implementováno

  • <Zadejte>. Analyzovat
  • Výčty lze použít a mapovat na celá čísla a řetězce v tabulce. Pro druhé se používají metody Parse a ToString().

Rozdíl oproti .NET

  • Výstup ToString pro double používá funkci CONVERT(NVARCHAR(30), @x, 2) v SQL, která vždy používá 16 číslic a "vědecký zápis". Příklad: "0,000000000000000e+000" pro 0, takže nedává stejný řetězec jako . NET je Convert.ToString().

System.string

Implementována

  • Nestatické metody:

    • Délka, Podřetězcec, Obsahuje, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, Toupper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. Podporují se všechny podpisy s výjimkou případů, kdy přebírají parametr StringComparison atd., jak je podrobně popsáno níže.
  • Statické metody:

       Concat(...)               all signatures
       Compare(String, String)
       String (indexer) 
       Equals(String, String)
    
  • Konstruktor:

        String(Char, Int32)
    
  • Operátory:

      +, ==, != (+, =, and <> in Visual Basic)
    

Není implementováno

  • Metody, které přebírají nebo vytvářejí pole znaků.

  • Metody, které přebírají CultureInfo/StringComparison/IFormatProvider.

  • Statické (sdílené v jazyce Visual Basic):

       Copy(String str)
       Compare(String, String, Boolean)
       Compare(String, String, StringComparison)
       Compare(String, String, Boolean, CultureInfo) 
       Compare(String, Int32, String, Int32, Int32)
       Compare(String, Int32, String, Int32, Int32,   Boolean)
       Compare(String, Int32, String, Int32, Int32, StringComparison)
       Compare(String, Int32, String, Int32, Int32, Boolean, CultureInfo)
       CompareOrdinal(String, String)
       CompareOrdinal(String, Int32, String, Int32, Int32)
       Join(String, ArrayOf String [,...]) All Join version with first three args
    
  • Instance:

       ToUpperInvariant()
       Format(String, Object)      + overloads
       IndexOf(String, Int32, StringComparison)
       IndexOfAny(ArrayOf Char)
       Normalize()
       Normalize(NormalizationForm)
       IsNormalized()
       Split(...)
       StartsWith(String, StringComparison)
       ToCharArray()
       ToUpper(CultureInfo)
       TrimEnd(ParamArray Char)
       TrimStart(ParamArray Char)
    

Omezení / rozdíl oproti .NET

SQL používá kolace k určení rovnosti a řazení řetězců. Můžete je zadat u instance SQL Server, databáze, sloupce tabulky nebo výrazu.

Překlady dosud implementovaných funkcí nemění kolaci ani nezadávají jinou kolaci přeložených výrazů. Pokud tedy výchozí kolace nerozlišuje velká a malá písmena, můžou funkce jako CompareTo nebo IndexOf poskytovat výsledky, které se liší od funkcí .NET (rozlišují se malá a malá písmena).

Metody StartsWith(str)/EndsWith(str) předpokládají, že argument str je konstanta nebo výraz, který je vyhodnocen na klientovi. To znamená, že v současné době není možné použít sloupec pro str.

System.Math

Implementace statických metod

  • Všechny podpisy:
    • Abs, Acos, Asin, Atan, Atan2, BigMul, Ceiling, Cos, Cosh, Exp, Floor, Log, Log10, Max, Min, Pow, Sign, Sinh, Sqrt, Tan, Tanh nebo Truncate.

Není implementováno

  • IEEERemainder.
  • DivRem má out parametr, takže ho nemůžete použít ve výrazu. Konstanty Math.PI a Math.E se vyhodnocují na klientovi, takže nepotřebují překlad.

Rozdíl oproti .NET

Překladem funkce .NET Math.Round je funkce SQL ROUND. Překlad je podporován pouze v případech, kdy je zadáno přetížení, které označuje hodnotu výčtu MidpointRounding . MidpointRounding.AwayFromZero je chování SQL a MidpointRounding.ToEven označuje chování CLR.

System.Convert

Implementována

  • Metody formuláře Do<typu1>(<Typ2> x), kde Type1, Type2 je jedna z těchto možností:
    • bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 nebo string.
  • Chování je stejné jako při přetypování:
    • Pro ToString(Double) je k dispozici speciální kód pro získání úplné přesnosti.
    • Pro převod Int32/Char používá LINQ to SQL funkci UNICODE/NCHAR jazyka SQL.
    • V opačném případě je překladem convert.

Neimplementováno

  • ToSByte, UInt16, 32, 64: Tyto typy v SQL neexistují.

    To<integer type>(String, Int32) 
    ToString(..., Int32)       any overload ending with an Int32 toBase
    IsDBNull(Object)
    GetTypeCode(Object)
    ChangeType(...)
    
  • Verze s parametrem IFormatProvider .

  • Metody, které zahrnují pole (To/FromBase64CharArray, To/FromBase64String).

System.TimeSpan

Implementována

  • Konstruktory:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • Operátory:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Statické metody (sdílené v jazyce Visual Basic):

       Compare(t1,t2)
    
  • Nestatické (instance) metody a vlastnosti:

       Ticks, Milliseconds, Seconds, Hours, Days
       TotalMilliseconds, TotalSeconds, TotalMinutes, TotalHours, TotalDays,
       Equals, CompareTo(TimeSpan)
       Add(TimeSpan), Subtract(TimeSpan)
       Duration() [= ABS], Negate()
    

Není implementováno

   ToString()
   TimeSpan FromDay(Double), FromHours,   all From Variants
   TimeSpan Parse(String)

System.datetime

Implementována

  • Konstruktory:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • Operátory:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • Statické (sdílené) metody:

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • Nestatické (instance) metody a vlastnosti:

       Day, Month, Year, Hour, Minute, Second, Millisecond, DayOfWeek
       CompareTo(DateTime)
       TimeOfDay()
       Equals
       ToString()
    

Rozdíl oproti .NET

Hodnoty data a času sql se zaokrouhlují na 000, 003 nebo 007 sekund, takže jsou méně přesné než hodnoty v rozhraní .NET.

Rozsah data a času SQL začíná 1. ledna 1753.

SQL nemá integrovaný typ pro TimeSpan. Používá různé metody DATEDIFF , které vrací 32bitová celá čísla. První je DATEDIFF(DAY,...), který udává počet dnů; další je DATEDIFF(MILISEKUND,...), který udává počet milisekund. Pokud jsou hodnoty DateTime od sebe delší než 24 dnů, dojde k chybě. Naproti tomu .NET používá 64bitová celá čísla a měří timeSpans v odškrtávkách.

Abyste se co nejvíce přiblížili sémantice .NET v SQL, LINQ to SQL přeloží TimeSpans na 64bitová celá čísla a pomocí dvou výše uvedených metod DATEDIFF vypočítá počet zaškrtnutí mezi dvěma daty.

Datetime Funkce UtcNow se na klientovi vyhodnotí při překladu dotazu (stejně jako jakýkoli výraz, který nezahrnuje data databáze).

Není implementováno

   IsDaylightSavingTime()
   IsLeapYear(Int32)
   DaysInMonth(Int32, Int32)
   ToBinary()
   ToFileTime()
   ToFileTimeUtc()
   ToLongDateString()
   ToLongTimeString()
   ToOADate()
   ToShortDateString()
   ToShortTimeString()
   ToUniversalTime()
   FromBinary(Long), FileTime, FileTimeUtc, OADate
   GetDateTimeFormats(...)
   constructor DateTime(Long)
   Parse(String)
   DayOfYear

Podpora ladění

DataContext poskytuje metody a vlastnosti pro získání SQL vygenerovaného pro dotazy a zpracování změn. Tyto metody můžou být užitečné pro pochopení LINQ to SQL funkcí a pro ladění specifických problémů.

Metody DataContext pro získání vygenerovaného SQL

Člen Účel
Protokol Vytiskne SQL před jeho spuštěním. Zahrnuje příkazy pro dotazy, vložení, aktualizaci a odstranění. Použití:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(dotaz) Vrátí text dotazu bez jeho spuštění. Použití:

C#

Console.WriteLine(db.GetQueryText(db.Customers));

Visual Basic

Console.WriteLine(db.GetQueryTest(db.Customers))

GetChangeText() Vrátí text příkazů SQL pro vložení, aktualizaci nebo odstranění bez jejich spuštění. Použití:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())