Udostępnij za pośrednictwem


LINQ to SQL: Zapytanie Language-Integrated platformy .NET dla danych relacyjnych

 

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

Marzec 2007 r.

Dotyczy:
   nazwa Visual Studio Code "Orcas"
   .Net Framework 3.5

Podsumowanie: LINQ to SQL zapewnia infrastrukturę środowiska uruchomieniowego do zarządzania danymi relacyjnymi jako obiektami bez utraty możliwości wykonywania zapytań. Aplikacja może swobodnie manipulować obiektami, podczas gdy LINQ to SQL pozostaje w tle automatycznie śledząc zmiany. (119 wydrukowanych stron)

Zawartość

Wprowadzenie
Krótki przewodnik
   Tworzenie klas jednostek
   The DataContext
   Definiowanie relacji
   Wykonywanie zapytań w relacjach
   Modyfikowanie i zapisywanie jednostek
Zapytania In-Depth
   Wykonywanie zapytania
   Tożsamość obiektu
   Relacje
   Sprzężenia
   Projekcje
   Skompilowane zapytania
   Tłumaczenie SQL
Cykl życia jednostki
   Śledzenie zmian
   Przesyłanie zmian
   Równoczesne zmiany
   Transakcje
   Procedury składowane
Klasy jednostek In-Depth
   Korzystanie z atrybutów
   Spójność grafu
   Zmienianie powiadomień
   Dziedziczenie
Tematy zaawansowane
   Tworzenie baz danych
   Współdziałanie z ADO.NET
   Rozwiązywanie konfliktów zmian
   Wywołanie procedur składowanych
   Narzędzie generatora klas jednostek
   Dokumentacja dbML narzędzia generatora
   Jednostki wielowarstwowe
   Mapowanie zewnętrzne
   Obsługa i uwagi dotyczące funkcji programu NET Framework
   Obsługa debugowania

Wprowadzenie

Większość programów napisanych obecnie manipuluje danymi w jeden lub inny sposób i często te dane są przechowywane w relacyjnej bazie danych. Istnieje jednak ogromny podział między nowoczesnymi językami programowania i bazami danych w tym, jak reprezentują i manipulują informacjami. Ta niezgodność impedancji jest widoczna na wiele sposobów. Najbardziej godne uwagi jest to, że języki programowania uzyskują dostęp do informacji w bazach danych za pośrednictwem interfejsów API, które wymagają określenia zapytań jako ciągów tekstowych. Te zapytania są znaczną częścią logiki programu. Jednak są one nieprzezroczyste w języku, nie mogą korzystać z funkcji weryfikacji czasu kompilacji i czasu projektowania, takich jak IntelliSense.

Oczywiście różnice idą znacznie głębiej niż to. Sposób przedstawiania informacji — modelu danych — różni się zupełnie między nimi. Nowoczesne języki programowania definiują informacje w postaci obiektów. Relacyjne bazy danych używają wierszy. Obiekty mają unikatową tożsamość, ponieważ każde wystąpienie różni się fizycznie od innego. Wiersze są identyfikowane przez wartości klucza podstawowego. Obiekty mają odwołania do wystąpień identyfikujących i łączących je ze sobą. Wiersze są pozostawione celowo odrębne, wymagając, aby powiązane wiersze były luźno powiązane przy użyciu kluczy obcych. Obiekty są autonomiczne, istniejące tak długo, jak długo są one nadal przywołytywne przez inny obiekt. Wiersze istnieją jako elementy tabel, znikają po ich usunięciu.

Nic dziwnego, że aplikacje, które mają wypełnić tę lukę, są trudne do utworzenia i utrzymania. Z pewnością uprościłoby to równanie, aby pozbyć się jednej strony lub drugiej. Jednak relacyjne bazy danych zapewniają infrastrukturę krytyczną dla długoterminowego przechowywania i przetwarzania zapytań, a nowoczesne języki programowania są niezbędne do elastycznego tworzenia i rozbudowanych obliczeń.

Do tej pory zadaniem dewelopera aplikacji było rozwiązanie tego niezgodności w każdej aplikacji oddzielnie. Najlepsze rozwiązania do tej pory były rozbudowane warstwy abstrakcji bazy danych, które promują informacje między modelami obiektów specyficznymi dla domeny aplikacji i tabelarycznym reprezentacją bazy danych, przekształcając i ponownie przekształcając dane w każdy sposób. Jednak zaciemniając prawdziwe źródło danych, te rozwiązania wyrzucają najbardziej atrakcyjną funkcję relacyjnych baz danych; możliwość wykonywania zapytań dotyczących danych.

LINQ to SQL składnik nazwy Visual Studio Code "Orcas" zapewnia infrastrukturę czasu wykonywania do zarządzania danymi relacyjnymi jako obiektami bez utraty możliwości wykonywania zapytań. Robi to, tłumacząc zapytania zintegrowane z językiem do języka SQL do wykonywania przez bazę danych, a następnie tłumacząc wyniki tabelaryczne z powrotem do zdefiniowanych obiektów. Aplikacja jest następnie bezpłatna do manipulowania obiektami, podczas gdy LINQ to SQL pozostaje w tle automatycznie śledząc zmiany.

  • LINQ to SQL została zaprojektowana tak, aby nie natrętna dla aplikacji.
    • Istnieje możliwość migracji bieżących rozwiązań ADO.NET do LINQ to SQL w sposób fragmentacyjny (udostępnianie tych samych połączeń i transakcji), ponieważ LINQ to SQL jest po prostu innym składnikiem rodziny ADO.NET. LINQ to SQL ma również rozbudowaną obsługę procedur składowanych, umożliwiając ponowne użycie istniejących zasobów przedsiębiorstwa.
  • LINQ to SQL aplikacje są łatwe do rozpoczęcia pracy.
    • Obiekty połączone z danymi relacyjnymi można zdefiniować tak samo jak zwykłe obiekty, ozdobione tylko atrybutami w celu zidentyfikowania sposobu, w jaki właściwości odpowiadają kolumnom. Oczywiście, nie jest to nawet konieczne, aby to zrobić ręcznie. Narzędzie do czasu projektowania jest udostępniane w celu zautomatyzowania tłumaczenia wstępnie istniejących schematów relacyjnych baz danych na definicje obiektów.

Razem LINQ to SQL infrastruktury czasu wykonywania i narzędzi czasu projektowania znacznie zmniejszają obciążenie dewelopera aplikacji bazy danych. Poniższe rozdziały zawierają omówienie sposobu, w jaki LINQ to SQL można używać do wykonywania typowych zadań związanych z bazą danych. Zakłada się, że czytelnik jest zaznajomiony z Language-Integrated Query i standardowymi operatorami zapytań.

LINQ to SQL jest niezależny od języka. Każdy język utworzony w celu zapewnienia Language-Integrated Zapytanie może użyć go w celu umożliwienia dostępu do informacji przechowywanych w relacyjnych bazach danych. Przykłady w tym dokumencie są wyświetlane zarówno w języku C#, jak i Visual Basic; LINQ to SQL można również używać z wersją kompilatora Języka Visual Basic z obsługą LINQ.

Krótki przewodnik

Pierwszym krokiem tworzenia aplikacji LINQ to SQL jest deklarowanie klas obiektów używanych do reprezentowania danych aplikacji. Przyjrzyjmy się przykładowi.

Tworzenie klas jednostek

Zaczniemy od prostej klasy Customer i skojarzymy ją z tabelą klientów w przykładowej bazie danych Northwind. W tym celu potrzebujemy tylko zastosowania atrybutu niestandardowego do góry deklaracji klasy. LINQ to SQL definiuje atrybut Tabela w tym celu.

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

Atrybut Tabela ma właściwość Name , której można użyć do określenia dokładnej nazwy tabeli bazy danych. Jeśli nie podano właściwości Name, LINQ to SQL przyjmie, że tabela bazy danych ma taką samą nazwę jak klasa. W bazie danych będą przechowywane tylko wystąpienia klas zadeklarowanych jako tabele. Wystąpienia tych typów klas są nazywane jednostkami. Same klasy są nazywane klasami jednostek.

Oprócz kojarzenia klas do tabel należy oznaczyć każde pole lub właściwość, którą zamierzasz skojarzyć z kolumną bazy danych. W tym celu LINQ to SQL definiuje atrybut Kolumna.

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

Atrybut Kolumna ma różne właściwości, których można użyć do dostosowania dokładnego mapowania między polami a kolumnami bazy danych. Jedną z właściwości notatek jest właściwość Id . Informuje LINQ to SQL, że kolumna bazy danych jest częścią klucza podstawowego w tabeli.

Podobnie jak w przypadku atrybutu Tabela , musisz podać informacje w atrybucie Kolumna , jeśli różni się od tego, co można odróżnić od deklaracji pola lub właściwości. W tym przykładzie należy poinformować LINQ to SQL, że pole CustomerID jest częścią klucza podstawowego w tabeli, ale nie musisz określać dokładnej nazwy ani typu.

Tylko pola i właściwości zadeklarowane jako kolumny będą utrwalane do lub pobierane z bazy danych. Inne będą traktowane jako przejściowe części logiki aplikacji.

The DataContext

Element DataContext jest głównym kombinezonem, za pomocą którego pobierasz obiekty z bazy danych i ponownie przesyłasz zmiany. Używasz go w taki sam sposób, jak w przypadku używania połączenia ADO.NET. W rzeczywistości element DataContext jest inicjowany przy użyciu parametrów połączenia lub połączenia, które podajesz. Celem obiektu DataContext jest tłumaczenie żądań dla obiektów na zapytania SQL wykonane względem bazy danych, a następnie zebranie obiektów z wyników. Element DataContext umożliwia zintegrowane z językiem zapytanie , implementując ten sam wzorzec operatora co standardowe operatory zapytań , takie jak Where i Select.

Na przykład możesz użyć obiektu DataContext , aby pobrać obiekty klienta, których miasto jest Londynem w następujący sposób:

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żda tabela bazy danych jest reprezentowana jako kolekcja tabel , dostępna za pośrednictwem metody GetTable() przy użyciu klasy jednostki, aby ją zidentyfikować. Zaleca się zadeklarowanie silnie typizowanego elementu DataContext zamiast polegania na podstawowej klasie DataContext i metodzie GetTable(). Silnie typizowane daneContext deklaruje wszystkie kolekcje tabel jako elementy członkowskie kontekstu.

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

Zapytanie dla klientów z Londynu może być następnie wyrażone bardziej po prostu jako:

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

W pozostałej części dokumentu omówienia będziemy nadal używać silnie typizowanej klasy Northwind .

Definiowanie relacji

Relacje w relacyjnych bazach danych są zwykle modelowane jako obce wartości kluczy odwołujące się do kluczy podstawowych w innych tabelach. Aby nawigować między nimi, należy jawnie połączyć dwie tabele przy użyciu operacji sprzężenia relacyjnego. Z drugiej strony obiekty odwołują się do siebie przy użyciu odwołań do właściwości lub kolekcji odwołań nawigowanych przy użyciu notacji "kropka". Oczywiście kropkowanie jest prostsze niż łączenie, ponieważ nie trzeba przypominać jawnego warunku sprzężenia za każdym razem, gdy przechodzisz.

W przypadku relacji danych, takich jak te, które zawsze będą takie same, staje się dość wygodne, aby kodować je jako odwołania do właściwości w klasie jednostki. LINQ to SQL definiuje atrybut skojarzenia, który można zastosować do elementu członkowskiego używanego do reprezentowania relacji. Relacja skojarzenia jest taka jak relacja klucza obcego do relacji klucza podstawowego, która jest dokonana przez dopasowywanie wartości kolumn między tabelami.

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

Klasa Customer ma teraz właściwość, która deklaruje relację między klientami i ich zamówieniami. Właściwość Orders jest typu EntitySet , ponieważ relacja jest typu jeden do wielu. Używamy właściwości OtherKey w atrybucie Skojarzenie , aby opisać sposób wykonywania tego skojarzenia. Określa nazwy właściwości w powiązanej klasie do porównania z tą klasą. Nie określono również właściwości ThisKey . Zwykle używalibyśmy go do wyświetlania listy elementów członkowskich po tej stronie relacji. Pomijając je jednak, pozwalamy LINQ to SQL wnioskować je od członków tworzących klucz podstawowy.

Zwróć uwagę, jak jest to odwrócone w definicji klasy 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

Klasa Order używa typu EntityRef , aby opisać relację z powrotem do klienta. Użycie klasy EntityRef jest wymagane do obsługi odroczonego ładowania (omówionego w dalszej części). Atrybut Skojarzenie dla właściwości Customer określa właściwość ThisKey , ponieważ niepodejmowane elementy członkowskie znajdują się teraz po tej stronie relacji.

Zapoznaj się również z właściwością Storage . Informuje LINQ to SQL, który prywatny element członkowski jest używany do przechowywania wartości właściwości. Dzięki temu LINQ to SQL obejść metody dostępu do właściwości publicznej, gdy przechowuje i pobiera ich wartość. Jest to niezbędne, jeśli chcesz, aby LINQ to SQL uniknąć dowolnej niestandardowej logiki biznesowej napisanej w akcesorach. Jeśli właściwość magazynu nie zostanie określona, zamiast tego zostaną użyte publiczne metody dostępu. Można również użyć właściwości Storage z atrybutami kolumny .

Po wprowadzeniu relacji w klasach jednostek ilość kodu, który trzeba napisać, rośnie wraz z wprowadzeniem obsługi powiadomień i spójności grafu. Na szczęście istnieje narzędzie (opisane później), które może służyć do generowania wszystkich niezbędnych definicji jako klas częściowych, co pozwala na użycie kombinacji wygenerowanego kodu i niestandardowej logiki biznesowej.

W pozostałej części tego dokumentu zakładamy, że narzędzie zostało użyte do wygenerowania kompletnego kontekstu danych Northwind i wszystkich klas jednostek.

Wykonywanie zapytań w relacjach

Teraz, gdy masz relacje, możesz ich używać podczas pisania zapytań po prostu, odwołując się do właściwości relacji zdefiniowanych w klasie.

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

Powyższe zapytanie używa właściwości Orders w celu utworzenia produktu krzyżowego między klientami i zamówieniami, tworząc nową sekwencję par Customer and Order .

Można również wykonać odwrotnie.

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

W tym przykładzie zamówienia są odpytywane, a relacja klienta służy do uzyskiwania dostępu do informacji na temat skojarzonego obiektu Klienta .

Modyfikowanie i zapisywanie jednostek

Niewiele aplikacji jest tworzonych tylko z myślą o zapytaniach. Dane muszą być również tworzone i modyfikowane. LINQ to SQL zaprojektowano w celu zapewnienia maksymalnej elastyczności w manipulowaniu i utrwalaniu zmian wprowadzonych w obiektach. Gdy tylko obiekty jednostki są dostępne — pobierając je za pomocą zapytania lub tworząc je na nowo, możesz manipulować nimi jako normalne obiekty w aplikacji, zmieniać ich wartości lub dodawać i usuwać je z kolekcji w miarę dopasowania. LINQ to SQL śledzi wszystkie zmiany i jest gotowy do przesłania ich z powrotem do bazy danych zaraz po zakończeniu.

W poniższym przykładzie użyto klas Customer and Order wygenerowanych przez narzędzie z metadanych całej przykładowej bazy danych Northwind. Definicje klas nie zostały pokazane dla zwięzłości.

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

Po wywołaniu funkcji SubmitChanges() LINQ to SQL automatycznie generuje i wykonuje polecenia SQL w celu przesłania zmian z powrotem do bazy danych. Istnieje również możliwość zastąpienia tego zachowania za pomocą logiki niestandardowej. Logika niestandardowa może wywoływać procedurę składowaną bazy danych.

Zapytania In-Depth

LINQ to SQL zapewnia implementację standardowych operatorów zapytań dla obiektów skojarzonych z tabelami w relacyjnej bazie danych. W tym rozdziale opisano LINQ to SQL specyficzne aspekty zapytań.

Wykonywanie zapytania

Niezależnie od tego, czy piszesz zapytanie jako wyrażenie zapytania wysokiego poziomu, czy kompilujesz jedno z poszczególnych operatorów, zapytanie, które piszesz, nie jest natychmiast wykonywaną instrukcją imperatywne. Jest to opis. Na przykład w deklaracji poniżej zmiennej lokalnej q odnosi się do opisu zapytania, a nie wyniku jego wykonania.

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

Rzeczywistym typem q w tym wystąpieniu jest klient> IQueryable<. Dopiero aplikacja podejmie próbę wyliczenia zawartości zapytania, które rzeczywiście wykonuje. W tym przykładzie instrukcja foreach powoduje wystąpienie wykonania.

Obiekt IQueryable jest podobny do obiektu polecenia ADO.NET. Posiadanie jednej strony nie oznacza, że zapytanie zostało wykonane. Obiekt polecenia zawiera ciąg opisujący zapytanie. Podobnie obiekt IQueryable zawiera opis zapytania zakodowanego jako struktura danych znana jako wyrażenie. Obiekt polecenia ma metodę ExecuteReader(), która powoduje wykonanie, zwracając wyniki jako element DataReader. Obiekt IQueryable ma metodę GetEnumerator(), która powoduje wykonanie, zwracając wyniki jako klient> IEnumerator<.

W związku z tym wynika, że jeśli zapytanie zostanie wyliczone dwa razy, zostanie wykonane dwa razy.

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

To zachowanie jest nazywane odroczonym wykonaniem. Podobnie jak w przypadku obiektu polecenia ADO.NET można trzymać na zapytaniu i ponownie go wykonać.

Oczywiście autorzy aplikacji często muszą być bardzo jawni co do tego, gdzie i kiedy jest wykonywane zapytanie. Byłoby nieoczekiwane, gdyby aplikacja wykonała zapytanie wiele razy, ponieważ musiała zbadać wyniki więcej niż raz. Na przykład możesz powiązać wyniki zapytania z elementem przypominającym usługę DataGrid. Kontrolka może wyliczać wyniki za każdym razem, gdy maluje na ekranie.

Aby uniknąć wielokrotnego wykonywania, przekonwertuj wyniki na dowolną liczbę standardowych klas kolekcji. Łatwo jest przekonwertować wyniki na listę lub tablicę przy użyciu standardowych operatorów zapytań ToList() lub 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 

Jedną z zalet odroczonego wykonywania jest to, że zapytania mogą być konstruowane w sposób fragmentowany z wykonywaniem tylko wtedy, gdy konstrukcja jest zakończona. Możesz zacząć komponować część zapytania, przypisując ją do zmiennej lokalnej, a następnie później kontynuować stosowanie większej liczby operatorów.

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 

W tym przykładzie q zaczyna się od zapytania dla wszystkich klientów w Londynie. Później zmieni się w uporządkowane zapytanie w zależności od stanu aplikacji. Odroczenie wykonywania zapytania można skonstruować zgodnie z dokładnymi potrzebami aplikacji bez konieczności ryzykownego manipulowania ciągami.

Tożsamość obiektu

Obiekty w środowisku uruchomieniowym mają unikatową tożsamość. Jeśli dwie zmienne odwołują się do tego samego obiektu, w rzeczywistości odwołują się do tego samego wystąpienia obiektu. Z tego powodu zmiany wprowadzane za pośrednictwem ścieżki przez jedną zmienną są natychmiast widoczne przez drugą. Wiersze w tabeli relacyjnej bazy danych nie mają unikatowej tożsamości. Jednak mają one klucz podstawowy i ten klucz podstawowy może być unikatowy, co oznacza, że żadne dwa wiersze nie mogą współużytkować tego samego klucza. Jednak ogranicza to tylko zawartość tabeli bazy danych. Dlatego tak długo, jak tylko wchodzimy w interakcje z danymi za pośrednictwem poleceń zdalnych, oznacza to mniej więcej to samo.

Jednak rzadko zdarza się tak. Najczęściej dane są wyprowadzane z bazy danych i do innej warstwy, w której aplikacja manipuluje nimi. Oczywiście jest to model, który LINQ to SQL jest przeznaczony do obsługi. Gdy dane są wyprowadzane z bazy danych jako wiersze, nie ma oczekiwań, że dwa wiersze reprezentujące te same dane rzeczywiście odpowiadają tym samym wystąpieniom wierszy. W przypadku dwukrotnego zapytania dotyczącego określonego klienta otrzymujesz dwa wiersze danych, z których każdy zawiera te same informacje.

Jednak w przypadku obiektów spodziewasz się czegoś zupełnie innego. Oczekujesz, że jeśli ponownie zapytasz obiekt DataContext o te same informacje, w rzeczywistości zwróci to samo wystąpienie obiektu. Oczekujesz tego, ponieważ obiekty mają specjalne znaczenie dla aplikacji i oczekujesz, że będą zachowywać się jak zwykłe obiekty. Zaprojektowano je jako hierarchie lub grafy i z pewnością oczekujesz ich pobrania jako takich, bez hord replikowanych wystąpień tylko dlatego, że prosiłeś o to samo dwa razy.

W związku z tym obiekt DataContext zarządza tożsamością obiektu. Za każdym razem, gdy nowy wiersz jest pobierany z bazy danych, jest on rejestrowany w tabeli tożsamości za pomocą klucza podstawowego i tworzony jest nowy obiekt. Za każdym razem, gdy ten sam wiersz jest ponownie pobierany, oryginalne wystąpienie obiektu jest przekazywane do aplikacji. W ten sposób funkcja DataContext tłumaczy koncepcję tożsamości (kluczy) baz danych na koncepcje języków (wystąpienia). Aplikacja widzi tylko obiekt w stanie, że został on po raz pierwszy pobrany. Nowe dane, jeśli są inne, są odrzucane.

Być może zastanawiasz się nad tym, dlaczego każda aplikacja wyrzuca dane? Jak się okazuje, LINQ to SQL zarządza integralnością obiektów lokalnych i jest w stanie obsługiwać optymistyczne aktualizacje. Ponieważ jedynymi zmianami występującymi po początkowym utworzeniu obiektu są zmiany wprowadzone przez aplikację, intencja aplikacji jest jasna. Jeśli zmiany ze strony zewnętrznej wystąpiły w międzyczasie, zostaną one zidentyfikowane w momencie wywołania metody SubmitChanges(). Więcej z tych informacji wyjaśniono w sekcji Równoczesne zmiany.

Należy pamiętać, że w przypadku, gdy baza danych zawiera tabelę bez klucza podstawowego, LINQ to SQL umożliwia przesłania zapytań do tabeli, ale nie zezwala na aktualizacje. Dzieje się tak, ponieważ struktura nie może zidentyfikować wiersza do zaktualizowania, biorąc pod uwagę brak unikatowego klucza.

Oczywiście, jeśli obiekt żądany przez zapytanie jest łatwo rozpoznawalny przez jego klucz podstawowy, ponieważ jeden już nie pobrał kwerendy nie jest w ogóle wykonywany. Tabela tożsamości działa jako pamięć podręczna przechowując wszystkie wcześniej pobrane obiekty.

Relacje

Jak pokazano w krótkim przewodniku, odwołania do innych obiektów lub kolekcji innych obiektów w definicjach klas bezpośrednio odpowiadają relacjom klucza obcego w bazie danych. Te relacje można używać podczas wykonywania zapytań przy użyciu notacji kropkowej, aby uzyskać dostęp do właściwości relacji, przechodząc z jednego obiektu do drugiego. Te operacje dostępu przekładają się na bardziej skomplikowane sprzężenia lub skorelowane podzadania w równoważnym języku SQL, co pozwala na przechodzenie przez graf obiektu podczas wykonywania zapytania. Na przykład następujące zapytanie przechodzi z zamówień do klientów jako sposób ograniczenia wyników tylko do tych zamówień dla klientów znajdujących się w Londynie.

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"

Jeśli właściwości relacji nie istnieją, należy je zapisać ręcznie jako sprzężenia tak samo jak w zapytaniu 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

Właściwość relacji umożliwia zdefiniowanie tej konkretnej relacji po włączeniu bardziej wygodnej składni kropki. Jednak nie jest to powód, dla którego istnieją właściwości relacji. Istnieją one, ponieważ zwykle definiujemy modele obiektów specyficzne dla domeny jako hierarchie lub grafy. Obiekty, które wybieramy do programowania, mają odwołania do innych obiektów. Jest to tylko szczęśliwy przypadek, że ponieważ relacje między obiektami odpowiadają relacjom stylu klucza obcego w bazach danych, które dostęp do właściwości prowadzą do wygodnego sposobu zapisu sprzężeń.

W związku z tym istnienie właściwości relacji jest ważniejsze po stronie wyników zapytania niż w ramach samego zapytania. Gdy masz już konkretnego klienta, definicja klasy informuje o tym, że klienci mają zamówienia. Dlatego gdy przyjrzysz się właściwości Orders określonego klienta, spodziewasz się, że kolekcja zostanie wypełniona wszystkimi zamówieniami klienta, ponieważ w rzeczywistości jest to umowa zadeklarowana przez zdefiniowanie klas w ten sposób. Oczekujesz, że zamówienia tam będą widoczne, nawet jeśli nie prosisz o zamówienia z góry. Oczekujesz, że model obiektów zachowa iluzję, że jest to rozszerzenie bazy danych w pamięci z powiązanymi obiektami natychmiast dostępnymi.

LINQ to SQL implementuje technikę o nazwie odroczone ładowanie w celu utrzymania tej iluzji. Podczas wykonywania zapytania o obiekt pobierasz tylko obiekty, o które prosisz. Powiązane obiekty nie są pobierane automatycznie w tym samym czasie. Jednak fakt, że powiązane obiekty nie są jeszcze załadowane, nie jest zauważalne, ponieważ gdy tylko próbujesz uzyskać do nich dostęp, żądanie zostanie wycofane w celu ich pobrania.

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

Na przykład możesz chcieć wykonać zapytanie dotyczące określonego zestawu zamówień, a następnie tylko od czasu do czasu wysyłać powiadomienia e-mail do konkretnych klientów. Nie trzeba pobierać wszystkich danych klientów z góry przy każdym zamówieniu. Odroczone ładowanie umożliwia odroczenie kosztu pobierania dodatkowych informacji, dopóki nie będzie to absolutnie konieczne.

Oczywiście, odwrotnie może być również prawdziwe. Być może masz aplikację, która musi jednocześnie przeglądać dane klienta i zamówienia. Wiesz, że potrzebujesz obu zestawów danych. Wiesz, że aplikacja będzie przechodzić do szczegółów zamówień każdego klienta, gdy tylko je otrzymasz. Niefortunne byłoby wyzwolenie poszczególnych zapytań dotyczących zamówień dla każdego klienta. To, co naprawdę chcesz zrobić, to mieć dane zamówienia pobrane razem z klientami.

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

Z pewnością zawsze możesz znaleźć sposób, aby połączyć klientów i zamówienia razem w zapytaniu, tworząc produkt krzyżowy i pobierając wszystkie względne bity danych jako jedną wielką projekcję. Ale wtedy wyniki nie byłyby jednostkami. Jednostki to obiekty z tożsamością, które można modyfikować, podczas gdy wyniki będą projekcjami, których nie można zmienić i utrwały. Co gorsza, pobieranie ogromnej ilości nadmiarowych danych jest powtarzane przez każdego klienta dla każdego zamówienia w spłaszczonej danych wyjściowych sprzężenia.

To, czego naprawdę potrzebujesz, to sposób pobierania zestawu powiązanych obiektów w tym samym czasie — odkreślinej części grafu, aby nigdy nie pobierać więcej lub mniej niż było to konieczne do zamierzonego użycia.

LINQ to SQL umożliwia natychmiastowe załadowanie regionu modelu obiektów z tego powodu. Robi to, zezwalając na specyfikację elementu DataShape dla obiektu DataContext. Klasa DataShape służy do poinstruowania struktury o tym, które obiekty mają być pobierane po pobraniu określonego typu. Jest to realizowane przy użyciu metody LoadWith , tak jak w następujących kwestiach:

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

W poprzednim zapytaniu wszystkie zamówienia dla wszystkich klientów mieszkających w Londynie są pobierane po wykonaniu zapytania, dzięki czemu kolejny dostęp do właściwości Orders w obiekcie Customer nie wyzwala zapytania bazy danych.

Klasa DataShape może również służyć do określania podzadań, które są stosowane do nawigacji relacji. Jeśli na przykład chcesz pobrać tylko zamówienia , które zostały wysłane dzisiaj, możesz użyć metody AssociateWith w elemecie DataShape w następujący sposób:

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

W poprzednim kodzie instrukcja wewnętrznego foreach iteruje nieco ponad zamówienia , które zostały wysłane dzisiaj, ponieważ tylko takie zamówienia zostały pobrane z bazy danych.

Ważne jest, aby zauważyć dwa fakty dotyczące klasy DataShape :

  1. Po przypisaniu elementu DataShape do elementu DataContext nie można zmodyfikować elementu DataShape . Każde wywołanie metody LoadWith lub AssociateWith dla takiego modułu DataShape zwróci błąd w czasie wykonywania.

  2. Nie można tworzyć cykli przy użyciu funkcji LoadWith lub AssociateWith. Na przykład następujące elementy generują błąd w czasie wykonywania:

    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)
    

Sprzężenia

Większość zapytań dotyczących modeli obiektów w dużym stopniu polega na nawigowaniu po odwołaniach do obiektów w modelu obiektów. Istnieją jednak interesujące "relacje" między jednostkami, które mogą nie być przechwytywane w modelu obiektów jako odwołania. Na przykład Customer.Orders to przydatna relacja oparta na relacjach kluczy obcych w bazie danych Northwind. Jednak dostawcy i klienci w tym samym mieście lub kraju to relacja ad hoc , która nie jest oparta na relacji klucza obcego i może nie być przechwytywana w modelu obiektów. Sprzężenia zapewniają dodatkowy mechanizm obsługi takich relacji. LINQ to SQL obsługuje nowe operatory sprzężenia wprowadzone w LINQ.

Rozważmy następujący problem — znajdź dostawców i klientów z siedzibą w tym samym mieście. Poniższe zapytanie zwraca nazwy dostawców i firm klientów oraz wspólne miasto jako spłaszczone wyniki. Jest to odpowiednik sprzężenia wewnętrznego w relacyjnych bazach danych:

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

Powyższe zapytanie eliminuje dostawców, którzy nie znajdują się w tym samym mieście co określony klient. Jednak czasami nie chcemy wyeliminować jednej z jednostek w relacji ad hoc . Poniższe zapytanie zawiera listę wszystkich dostawców z grupami klientów dla każdego z dostawców. Jeśli określony dostawca nie ma żadnego klienta w tym samym mieście, wynik jest pustą kolekcją klientów odpowiadających temu dostawcy. Należy pamiętać, że wyniki nie są płaskie — każdy dostawca ma skojarzona kolekcję. W rzeczywistości zapewnia to sprzężenia grupy — łączy dwie sekwencje i grupy elementów drugiej sekwencji przez elementy pierwszej sekwencji.

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

Sprzężenie grup można również rozszerzyć na wiele kolekcji. Poniższe zapytanie rozszerza powyższe zapytanie, wyświetlając listę pracowników, którzy znajdują się w tym samym mieście co dostawca. W tym miejscu wynik przedstawia dostawcę z (prawdopodobnie pustymi) kolekcjami klientów i pracowników.

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

Wyniki sprzężenia grupowego można również spłaszczać. Wyniki spłaszczania połączenia grupy między dostawcami a klientami to wiele wpisów dla dostawców z wieloma klientami w ich mieście — jeden na klienta. Puste kolekcje są zastępowane wartościami null. Jest to odpowiednik lewego zewnętrznego sprzężenia równoczesnego w relacyjnych bazach danych.

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 dla podstawowych operatorów sprzężenia są definiowane w standardowym dokumencie operatorów zapytań. Obsługiwane są tylko sprzężenia równoczesne, a dwa operandy równe muszą mieć ten sam typ.

Projekcje

Do tej pory przyjrzeliśmy się tylko zapytaniom o pobieranie jednostek — obiektów bezpośrednio skojarzonych z tabelami bazy danych. Nie musimy ograniczać się tylko do tego. Piękno języka zapytań polega na tym, że można pobrać informacje w dowolnej formie. Nie będzie można korzystać z automatycznego śledzenia zmian ani zarządzania tożsamościami, gdy to zrobisz. Możesz jednak pobrać tylko żądane dane.

Na przykład może być konieczne zapoznanie się z nazwami firm wszystkich klientów w Londynie. Jeśli tak jest, nie ma szczególnego powodu, aby pobrać całe obiekty klienta tylko do wybierania nazw. Nazwy można projektować w ramach zapytania.

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

W takim przypadku q staje się zapytaniem, które pobiera sekwencję ciągów.

Jeśli chcesz wrócić więcej niż tylko jedną nazwę, ale nie wystarczy, aby uzasadnić pobranie całego obiektu klienta, możesz określić dowolny podzbiór, tworząc wyniki w ramach zapytania.

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

W tym przykładzie użyto inicjatora obiektu anonimowego do utworzenia struktury zawierającej zarówno nazwę firmy, jak i numer telefonu. Być może nie wiesz, co należy wywołać, ale z niejawnie typizowanej deklaracji zmiennej lokalnej w języku, który niekoniecznie musi być potrzebny.

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

Jeśli dane są używane natychmiast, typy anonimowe stanowią dobrą alternatywę dla jawnego definiowania klas do przechowywania wyników zapytania.

Można również tworzyć krzyżowe produkty całych obiektów, choć rzadko ma to powó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 londonOrders = From cust In db.Customer, _
                   ord In db.Orders _
                   Where cust.City = "London" _
                   Select Customer = cust, Order = ord

To zapytanie tworzy sekwencję par obiektów klienta i zamówień.

Istnieje również możliwość tworzenia projekcji na dowolnym etapie zapytania. Dane można projektować w nowo skonstruowanych obiektach, a następnie odwoływać się do elementów członkowskich tych obiektów w kolejnych operacjach zapytań.

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

Należy jednak uważać na używanie sparametryzowanych konstruktorów na tym etapie. Jest to technicznie ważne, ale nie można LINQ to SQL śledzić, jak użycie konstruktora wpływa na stan członkowski bez zrozumienia rzeczywistego kodu wewnątrz konstruktora.

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

Ponieważ LINQ to SQL próbuje przetłumaczyć zapytanie na czyste relacyjne typy obiektów zdefiniowanych lokalnie sql nie są dostępne na serwerze do rzeczywistej konstrukcji. Wszystkie konstrukcje obiektów są w rzeczywistości odroczone do momentu odzyskania danych z bazy danych. Zamiast rzeczywistych konstruktorów wygenerowany program SQL używa normalnego projekcji kolumny SQL. Ponieważ translator zapytań nie może zrozumieć, co dzieje się podczas wywołania konstruktora, nie może ustanowić znaczenia dla pola NazwamyType.

Zamiast tego najlepszym rozwiązaniem jest zawsze używanie inicjatorów obiektów do kodowania projekcji.

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

Jedynym bezpiecznym miejscem do użycia konstruktora sparametryzowanego jest ostateczne projekcje zapytania.

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>

Można nawet użyć rozbudowanego zagnieżdżania konstruktorów obiektów, jeśli chcesz, podobnie jak w tym przykładzie, który tworzy kod XML bezpośrednio z wyniku zapytania. Działa tak długo, jak jest to ostatnia projekcja zapytania.

Mimo to, nawet jeśli wywołania konstruktora są zrozumiałe, wywołania metod lokalnych mogą nie być. Jeśli ostateczna projekcja wymaga wywołania metod lokalnych, jest mało prawdopodobne, że LINQ to SQL będzie w stanie zobowiązać. Wywołania metod, które nie mają znanego tłumaczenia na język SQL, nie mogą być używane jako część zapytania. Jednym wyjątkiem od tej reguły są wywołania metod, które nie mają argumentów zależnych od zmiennych zapytania. Nie są one traktowane jako część przetłumaczonego zapytania i zamiast tego są traktowane jako parametry.

Nadal skomplikowane projekcje (przekształcenia) mogą wymagać lokalnej logiki proceduralnej do zaimplementowania. Aby użyć własnych metod lokalnych w ostatecznej projekcji, musisz dwukrotnie wykonać projekt. Pierwsza projekcja wyodrębnia wszystkie wartości danych, do których należy się odwołać, a druga projekcja wykonuje transformację. Między tymi dwoma projekcjami jest wywołanie operatora AsEnumerable(), który przenosi przetwarzanie w tym momencie z LINQ to SQL zapytania do wykonywanego lokalnie.

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

Uwaga Operator AsEnumerable() w przeciwieństwie do toList() i ToArray() nie powoduje wykonania zapytania. Nadal jest on odroczony. Operator AsEnumerable() tylko zmienia statyczne wpisywanie zapytania, zamieniając IQueryable T> (IQueryable<(ofT) w visual basic) na IEnumerable<T> (IEnumerable (ofT) w Visual Basic), co skłoniło kompilatora do traktowania reszty zapytania jako wykonywanego lokalnie.

Skompilowane zapytania

W wielu aplikacjach często wykonywane są podobne zapytania strukturalne. W takich przypadkach można zwiększyć wydajność, kompilując zapytanie raz i wykonując je kilka razy w aplikacji z różnymi parametrami. Ten wynik jest uzyskiwany w LINQ to SQL przy użyciu klasy CompiledQuery. Poniższy kod pokazuje, jak zdefiniować skompilowane zapytanie:

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 zwraca delegata, który może być buforowany i wykonywany później kilka razy przez zmianę parametrów wejściowych. Poniższy kod przedstawia przykład:

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

Tłumaczenie SQL

LINQ to SQL nie wykonuje zapytań; relacyjna baza danych nie wykonuje. LINQ to SQL tłumaczy zapytania napisane na równoważne zapytania SQL i wysyła je do serwera na potrzeby przetwarzania. Ponieważ wykonanie jest odroczone, LINQ to SQL jest w stanie zbadać całe zapytanie nawet w przypadku złożenia z wielu części.

Ponieważ serwer relacyjnej bazy danych nie wykonuje faktycznie il (oprócz integracji CLR w SQL Server 2005 r.), zapytania nie są przesyłane do serwera jako IL. Są one w rzeczywistości przesyłane jako sparametryzowane zapytania SQL w formie tekstowej.

Oczywiście język SQL — nawet język T-SQL z integracją środowiska CLR — nie jest w stanie wykonać różnych metod dostępnych lokalnie dla programu. W związku z tym zapytania, które piszesz, muszą zostać przetłumaczone na równoważne operacje i funkcje dostępne w środowisku SQL.

Większość metod i operatorów w typach wbudowanych platformy .Net Framework ma bezpośrednie tłumaczenia na język SQL. Niektóre z dostępnych funkcji można wygenerować. Te, których nie można przetłumaczyć, są niedozwolone, generując wyjątki czasu wykonywania, jeśli spróbujesz ich użyć. W dalszej części dokumentu znajduje się sekcja zawierająca szczegółowe informacje o metodach struktury implementowanych do tłumaczenia na język SQL.

Cykl życia jednostki

LINQ to SQL to nie tylko implementacja standardowych operatorów zapytań dla relacyjnych baz danych. Oprócz tłumaczenia zapytań jest to usługa, która zarządza obiektami przez cały okres ich istnienia, ułatwiając utrzymanie integralności danych i automatyzowanie procesu tłumaczenia modyfikacji z powrotem do magazynu.

W typowym scenariuszu obiekty są pobierane za pośrednictwem co najmniej jednego zapytania, a następnie manipulowane w jakiś sposób lub inny, dopóki aplikacja nie będzie gotowa do wysłania zmian z powrotem do serwera. Ten proces może powtarzać się kilka razy, dopóki aplikacja nie będzie już używać tych informacji. W tym momencie obiekty są odzyskiwane przez środowisko uruchomieniowe tak samo jak zwykłe obiekty. Dane pozostają jednak w bazie danych. Nawet po usunięciu ich istnienia w czasie wykonywania można pobrać obiekty reprezentujące te same dane. W tym sensie prawdziwy okres istnienia obiektu istnieje poza żadnym pojedynczym przejawem czasu wykonywania.

Celem tego rozdziału jest cykl życia jednostki , w którym cykl odnosi się do przedziału czasu pojedynczego przejawu obiektu jednostki w określonym kontekście czasu wykonywania. Cykl rozpoczyna się, gdy obiekt DataContext staje się świadomy nowego wystąpienia i kończy się, gdy obiekt lub dataContext nie jest już potrzebny.

Śledzenie zmian

Po pobraniu jednostek z bazy danych możesz manipulować nimi tak, jak chcesz. Są to twoje obiekty; użyj ich tak, jak to zrobisz. W tym celu LINQ to SQL śledzi zmiany, aby można było je utrwalać w bazie danych po wywołaniu funkcji SubmitChanges().

LINQ to SQL rozpoczyna śledzenie jednostek po pobraniu ich z bazy danych, zanim kiedykolwiek położysz na nich ręce. Rzeczywiście, omówiona wcześniej usługa zarządzania tożsamościami już się wyrzucona. Śledzenie zmian kosztuje bardzo mało w dodatkowych kosztach, dopóki rzeczywiście nie zaczniesz wprowadzać zmian.

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"

Jak tylko nazwa_firmy jest przypisana w powyższym przykładzie, LINQ to SQL staje się świadomy zmiany i jest w stanie go zarejestrować. Oryginalne wartości wszystkich elementów członkowskich danych są zachowywane przez usługę śledzenia zmian.

Usługa śledzenia zmian rejestruje również wszystkie manipulacje właściwościami relacji. Właściwości relacji służą do ustanawiania łączy między jednostkami, mimo że mogą być połączone przez wartości kluczy w bazie danych. Nie ma potrzeby bezpośredniego modyfikowania elementów członkowskich skojarzonych z kolumnami kluczy. LINQ to SQL automatycznie synchronizuje je przed przesłaniem zmian.

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

Zamówienia można przenosić od jednego klienta do innego, po prostu przypisując do swojej właściwości Customer . Ponieważ relacja istnieje między klientem a zamówieniem, można zmienić relację, modyfikując jedną z tych stron. Można było je równie łatwo usunąć z kolekcji Orderscust2 i dodać je do kolekcji zamówień cust1, jak pokazano poniżej.

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)

Oczywiście, jeśli przypiszesz relację wartość null, w rzeczywistości całkowicie pozbysz się relacji. Przypisanie właściwości Customer zamówienia do wartości null w rzeczywistości usuwa zamówienie z listy klienta.

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

Automatyczne aktualizowanie obu stron relacji jest niezbędne do utrzymania spójności grafu obiektu. W przeciwieństwie do normalnych obiektów relacje między danymi są często dwukierunkowe. LINQ to SQL umożliwia używanie właściwości do reprezentowania relacji. Jednak usługa nie oferuje usługi do automatycznego utrzymywania synchronizacji tych właściwości dwukierunkowych. Jest to poziom usługi, który musi być pieczony bezpośrednio w definicjach klasy. Klasy jednostek generowane przy użyciu narzędzia do generowania kodu mają tę możliwość. W następnym rozdziale pokażemy, jak to zrobić z własnymi klasami odręcznymi.

Należy jednak pamiętać, że usunięcie relacji nie oznacza usunięcia obiektu z bazy danych. Pamiętaj, że okres istnienia danych bazowych będzie się powtarzać w bazie danych, dopóki wiersz nie zostanie usunięty z tabeli. Jedynym sposobem usunięcia obiektu jest usunięcie go z kolekcji Tabel .

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)

Podobnie jak w przypadku wszystkich innych zmian, kolejność nie została rzeczywiście usunięta. Wygląda na to, że sposób na nas, ponieważ został usunięty i odłączony od reszty naszych obiektów. Gdy obiekt zamówienia został usunięty z tabeli Orders (Zamówienia), został oznaczony do usunięcia przez usługę śledzenia zmian. Rzeczywiste usunięcie z bazy danych nastąpi po przesłaniu zmian w wywołaniu funkcji SubmitChanges(). Należy pamiętać, że sam obiekt nigdy nie jest usuwany. Środowisko uruchomieniowe zarządza okresem istnienia wystąpień obiektów, więc utrzymuje się tak długo, jak nadal przechowujesz odwołanie do niego. Jednak po usunięciu obiektu z tabeli i przesłanych zmianach nie są już śledzone przez usługę śledzenia zmian.

Jedynym innym razem, gdy jednostka zostanie pozostawiona bez śledzenia, jest wtedy, gdy istnieje, zanim obiekt DataContext zostanie o nim świadomy. Dzieje się tak za każdym razem, gdy tworzysz nowe obiekty w kodzie. Możesz używać wystąpień klas jednostek w aplikacji bez pobierania ich z bazy danych. Zmiany tackingu i zarządzania tożsamościami mają zastosowanie tylko do tych obiektów, o których jest świadomy obiekt DataContext . W związku z tym żadna z usług nie jest włączona dla nowo utworzonych wystąpień do momentu dodania ich do elementu DataContext.

Może się to zdarzyć na jeden z dwóch sposobów. Możesz ręcznie wywołać metodę Add() w powiązanej kolekcji tabel .

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)

Alternatywnie możesz dołączyć nowe wystąpienie do obiektu, o którym jest już wiadomo, że element DataContext jest już świadomy.

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

Obiekt DataContext odnajdzie nowe wystąpienia obiektów, nawet jeśli są dołączone do innych nowych wystąpień.

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

Zasadniczo obiekt DataContext rozpozna dowolną jednostkę na grafie obiektu, która nie jest obecnie śledzona jako nowe wystąpienie, bez względu na to, czy została wywołana metoda Add().

Korzystanie z danych tylko do odczytu

Wiele scenariuszy nie wymaga aktualizacji jednostek pobranych z bazy danych. Wyświetlanie tabeli Klienci na stronie sieci Web jest jednym z oczywistych przykładów. We wszystkich takich przypadkach można poprawić wydajność, instruując element DataContext , aby nie śledzić zmian w jednostkach. Jest to osiągane przez określenie właściwości ObjectTracking w obiekcie DataContext na wartość false, jak w poniższym kodzie:

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

Przesyłanie zmian

Niezależnie od liczby zmian wprowadzonych w obiektach te zmiany zostały wprowadzone tylko w replikach w pamięci. Nic jeszcze nie stało się z rzeczywistymi danymi w bazie danych. Przekazywanie tych informacji do serwera nie nastąpi, dopóki jawnie nie zażądasz ich przez wywołanie metody SubmitChanges() w obiekcie 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()

Po wywołaniu metody SubmitChanges()obiekt DataContext podejmie próbę przetłumaczenia wszystkich zmian na równoważne polecenia SQL, wstawiania, aktualizowania lub usuwania wierszy w odpowiednich tabelach. Te akcje mogą być zastępowane przez własną logikę niestandardową, jeśli chcesz, jednak kolejność przesyłania jest organizowana przez usługę DataContext znaną jako procesor zmian.

Pierwszą rzeczą, która dzieje się po wywołaniu funkcji SubmitChanges(), jest to, że zestaw znanych obiektów jest badany w celu określenia, czy nowe wystąpienia zostały dołączone do nich. Te nowe wystąpienia są dodawane do zestawu śledzonych obiektów. Następnie wszystkie obiekty z oczekującymi zmianami są uporządkowane w sekwencji obiektów na podstawie zależności między nimi. Te obiekty, których zmiany zależą od innych obiektów, są sekwencjonowane po ich zależnościach. Ograniczenia klucza obcego i ograniczenia unikatowości w bazie danych odgrywają dużą rolę w określaniu prawidłowej kolejności zmian. Następnie, tuż przed przesłaniem rzeczywistych zmian, transakcja zostanie uruchomiona, aby hermetyzować serię poszczególnych poleceń, chyba że jest już w zakresie. Na koniec jeden po drugim zmiany w obiektach są tłumaczone na polecenia SQL i wysyłane do serwera.

W tym momencie wszelkie błędy wykryte przez bazę danych spowodują przerwanie procesu przesyłania i zostanie zgłoszony wyjątek. Wszystkie zmiany w bazie danych zostaną wycofane tak, jakby żadne z przesłanych nie miało miejsca. Element DataContext nadal będzie miał pełne nagranie wszystkich zmian, więc można spróbować rozwiązać problem i ponownie przesłać je, wywołując ponownie funkcję 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 pomyślnym zakończeniu transakcji wokół przesyłania element DataContext zaakceptuje zmiany w obiektach, zapominając o informacjach o śledzeniu zmian.

Równoczesne zmiany

Istnieje wiele powodów, dla których wywołanie funkcji SubmitChanges() może zakończyć się niepowodzeniem. Być może utworzono obiekt z nieprawidłowym kluczem podstawowym; taki, który jest już używany, lub z wartością, która narusza pewne ograniczenie kontrolne bazy danych. Tego rodzaju kontrole są trudne do pieczenia w logice biznesowej, ponieważ często wymagają absolutnej wiedzy o całym stanie bazy danych. Jednak najbardziej prawdopodobną przyczyną niepowodzenia jest po prostu to, że ktoś inny dokonał zmian w obiektach przed tobą.

Z pewnością byłoby to niemożliwe, gdyby zablokowano każdy obiekt w bazie danych i używano w pełni serializowanej transakcji. Jednak ten styl programowania (pesymistyczne współbieżność) jest rzadko używany, ponieważ jest kosztowny i prawdziwe starcia rzadko występują. Najpopularniejszą formą zarządzania równoczesnym zmianami jest zastosowanie formy optymistycznej współbieżności. W tym modelu nie są wykonywane żadne blokady względem wierszy bazy danych. Oznacza to, że każda liczba zmian w bazie danych mogła wystąpić między pierwszym pobraniem obiektów a czasem przesłania zmian.

W związku z tym, chyba że chcesz przejść z zasadami, które ostatnia aktualizacja wygrywa, wyczyszczenie cokolwiek innego miało miejsce przed tobą, prawdopodobnie chcesz otrzymywać alerty o tym, że dane bazowe zostały zmienione przez kogoś innego.

Funkcja DataContext ma wbudowaną obsługę optymistycznej współbieżności, automatycznie wykrywając konflikty zmian. Poszczególne aktualizacje kończą się powodzeniem tylko wtedy, gdy bieżący stan bazy danych jest zgodny ze stanem, w którym dane mają znajdować się podczas pierwszego pobierania obiektów. Dzieje się tak na podstawie obiektu, tylko ostrzegając o naruszeniach, jeśli wystąpią one w obiektach, do których wprowadzono zmiany.

Podczas definiowania klas jednostek można kontrolować stopień, w którym obiekt DataContext wykrywa konflikty zmian. Każdy atrybut Kolumna ma właściwość o nazwie UpdateCheck , którą można przypisać jedną z trzech wartości: Always, Never i WhenChanged. Jeśli nie ustawiono wartości domyślnej atrybutu Kolumna to Zawsze, co oznacza, że wartości danych reprezentowane przez ten element członkowski są zawsze sprawdzane pod kątem konfliktów, to znaczy, że istnieje oczywisty tie-breaker, taki jak sygnatura wersji. Atrybut Kolumna ma właściwość IsVersion , która umożliwia określenie, czy wartość danych stanowi sygnaturę wersji obsługiwaną przez bazę danych. Jeśli istnieje wersja, wersja jest używana samodzielnie, aby określić, czy wystąpił konflikt.

Gdy wystąpi konflikt zmiany, wyjątek zostanie zgłoszony tak samo, jakby był to jakikolwiek inny błąd. Transakcja wokół przesłania zostanie przerwana, ale element DataContext pozostanie taki sam, co umożliwi rozwiązanie problemu i spróbuj ponownie.

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

Jeśli wprowadzasz zmiany w warstwie środkowej lub serwerze, najprostszą rzeczą, którą można zrobić, aby naprawić konflikt zmian, jest po prostu zacząć od nowa i spróbować ponownie, ponownie utworzyć kontekst i ponownie zastosować zmiany. Dodatkowe opcje opisano w poniższej sekcji.

Transakcje

Transakcja jest usługą dostarczaną przez bazy danych lub dowolnego innego menedżera zasobów, który może służyć do zagwarantowania, że seria poszczególnych akcji odbywa się automatycznie; oznacza, że wszyscy się udają lub nie. Jeśli tak nie jest, wszystkie są również automatycznie cofnięte, zanim wszystko inne będzie mogło się zdarzyć. Jeśli żadna transakcja nie jest już w zakresie, funkcja DataContext automatycznie uruchomi transakcję bazy danych, aby chronić aktualizacje podczas wywoływania funkcji SubmitChanges().

Możesz kontrolować typ używanej transakcji, jej poziom izolacji lub to, co faktycznie obejmuje, inicjując go samodzielnie. Izolacja transakcji używana przez element DataContext jest znana 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

Powyższy przykład inicjuje w pełni serializowaną transakcję, tworząc nowy obiekt zakresu transakcji. Wszystkie polecenia bazy danych wykonywane w zakresie transakcji będą chronione przez transakcję.

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

Ta zmodyfikowana wersja tego samego przykładu używa metody ExecuteCommand() w obiekcie DataContext do wykonania procedury składowanej w bazie danych bezpośrednio przed przesłaniem zmian. Niezależnie od tego, co procedura składowana wykonuje w bazie danych, możemy być pewni, że jej akcje są częścią tej samej transakcji.

Jeśli transakcja zakończy się pomyślnie, element DataContext wyrzuci wszystkie skumulowane informacje śledzenia i traktuje nowe stany jednostek jako niezmienione. Nie powoduje to jednak wycofania zmian w obiektach, jeśli transakcja zakończy się niepowodzeniem. Pozwala to na maksymalną elastyczność w radzeniu sobie z problemami podczas przesyłania zmian.

Istnieje również możliwość użycia lokalnej transakcji SQL zamiast nowej transakcji TransactionScope. LINQ to SQL oferuje tę funkcję, aby ułatwić integrację funkcji LINQ to SQL z istniejącymi aplikacjami ADO.NET. Jeśli jednak przejdziesz tą trasą, musisz być odpowiedzialny za znacznie więcej.

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 widać, użycie ręcznie kontrolowanej transakcji bazy danych jest nieco bardziej zaangażowane. Nie tylko musisz samodzielnie go uruchomić, musisz jawnie poinformować element DataContext , aby go użyć, przypisując go do właściwości Transaction . Następnie należy użyć bloku try-catch, aby ująć logikę przesyłania, pamiętając, aby jawnie poinformować transakcję o zatwierdzeniu i jawnie poinformować DataContext o zaakceptowaniu zmian lub przerwać transakcje, jeśli w dowolnym momencie wystąpił błąd. Ponadto nie zapomnij ustawić właściwości Transaction z powrotem na null po zakończeniu.

Procedury składowane

Gdy funkcja SubmitChanges() jest wywoływana, LINQ to SQL generuje i wykonuje polecenia SQL w celu wstawiania, aktualizowania i usuwania wierszy w bazie danych. Te akcje mogą być zastępowane przez deweloperów aplikacji, a w ich miejscu kod niestandardowy może służyć do wykonywania żądanych akcji. W ten sposób alternatywne obiekty, takie jak procedury składowane bazy danych, mogą być wywoływane automatycznie przez procesor zmian.

Rozważ procedurę składowaną aktualizowania jednostek w magazynie dla tabeli Products w przykładowej bazie danych Northwind. Deklaracja SQL procedury jest następująca.

SQL

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

Możesz użyć procedury składowanej zamiast normalnego automatycznie wygenerowanego polecenia aktualizacji, definiując metodę w silnie typizowanej metodzie DataContext. Nawet jeśli klasa DataContext jest generowana automatycznie przez narzędzie do generowania kodu LINQ to SQL, nadal można określić te metody w klasie częściowej własnej.

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 i parametru ogólnego informuje obiekt DataContext o użyciu tej metody zamiast wygenerowanej instrukcji aktualizacji. Oryginalne i bieżące parametry są używane przez LINQ to SQL do przekazywania oryginalnych i bieżących kopii obiektu określonego typu. Dwa parametry są dostępne dla optymistycznego wykrywania konfliktów współbieżności.

Uwaga Jeśli zastąpisz domyślną logikę aktualizacji, wykrywanie konfliktów jest Twoim zadaniem.

Procedura składowana UpdateProductStock jest wywoływana przy użyciu metody ExecuteCommand() obiektu DataContext. Zwraca liczbę wierszy, których dotyczy problem i ma następujący podpis:

C#

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

Visual Basic

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

Tablica obiektów służy do przekazywania parametrów wymaganych do wykonania polecenia.

Podobnie jak w przypadku metody aktualizacji, można określić metody wstawiania i usuwania. Metody wstawiania i usuwania przyjmują tylko jeden parametr typu jednostki do zaktualizowania. Na przykład metody wstawiania i usuwania wystąpienia produktu można określić w następujący sposób:

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

Klasy jednostek In-Depth

Korzystanie z atrybutów

Klasa jednostki jest taka sama jak każda normalna klasa obiektów, którą można zdefiniować jako część aplikacji, z tą różnicą, że jest oznaczona adnotacją ze specjalnymi informacjami, które kojarzą ją z określoną tabelą bazy danych. Te adnotacje są tworzone jako atrybuty niestandardowe w deklaracji klasy. Atrybuty są istotne tylko wtedy, gdy używasz klasy w połączeniu z LINQ to SQL. Są one podobne do atrybutów serializacji XML w .NET Framework. Te atrybuty "danych" zapewniają LINQ to SQL wystarczającą ilość informacji, aby przetłumaczyć zapytania dotyczące obiektów na zapytania SQL względem bazy danych i zmiany w obiektach w poleceniach wstawiania, aktualizowania i usuwania sql.

Istnieje również możliwość reprezentowania informacji o mapowaniu przy użyciu pliku mapowania XML zamiast atrybutów. Ten scenariusz został szczegółowo opisany w sekcji Mapowanie zewnętrzne.

Atrybut bazy danych

Atrybut Baza danych służy do określania domyślnej nazwy bazy danych, jeśli nie jest ona dostarczana przez połączenie. Atrybuty bazy danych można stosować do silnie typidowanych deklaracji DataContext. Ten atrybut jest opcjonalny.

Atrybut bazy danych

Właściwość Typ Opis
Nazwa Ciąg Określa nazwę bazy danych. Informacje są używane tylko wtedy, gdy samo połączenie nie określa nazwy bazy danych. Jeśli ten atrybut bazy danych nie istnieje w deklaracji kontekstu i nie jest określony przez połączenie, zakłada się, że baza danych ma taką samą nazwę jak klasa kontekstu.

C#

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

Visual Basic

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

Atrybut tabeli

Atrybut Tabela służy do wyznaczania klasy jako klasy jednostki skojarzonej z tabelą bazy danych. Klasy z atrybutem Tabela będą traktowane specjalnie przez LINQ to SQL.

Atrybut tabeli

Właściwość Typ Opis
Nazwa Ciąg Określa nazwę tabeli. Jeśli te informacje nie zostaną określone, zakłada się, że tabela ma taką samą nazwę jak klasa jednostki.

C#

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

Visual Basic

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

Atrybut kolumny

Atrybut Kolumna służy do wyznaczania składowej klasy jednostki, która reprezentuje kolumnę w tabeli bazy danych. Można go zastosować do dowolnego pola lub właściwości, publicznej, prywatnej lub wewnętrznej. Tylko członkowie zidentyfikowani jako kolumny są utrwalane, gdy LINQ to SQL zapisuje zmiany w bazie danych.

Atrybut kolumny

Właściwość Typ Opis
Nazwa Ciąg Nazwa kolumny w tabeli lub widoku. Jeśli nie określono kolumny, przyjmuje się, że ma taką samą nazwę jak składowa klasy.
Storage Ciąg Nazwa magazynu bazowego. Jeśli zostanie określony, informuje LINQ to SQL, jak pominąć metodę dostępu do właściwości publicznej dla elementu członkowskiego danych i korzystać z samej wartości pierwotnej. Jeśli nie określono LINQ to SQL pobiera i ustawia wartość przy użyciu publicznego dostępu.
Dbtype Ciąg Typ kolumny bazy danych określony przy użyciu typów baz danych i modyfikatorów. Będzie to dokładny tekst używany do definiowania kolumny w poleceniu deklaracji tabeli T-SQL. Jeśli typ kolumny bazy danych nie zostanie określony, zostanie wywnioskowany z typu elementu członkowskiego. Określony typ bazy danych jest wymagany tylko wtedy, gdy oczekuje się, że metoda CreateDatabase() zostanie użyta do utworzenia wystąpienia bazy danych.
Isprimarykey Wartość logiczna Jeśli jest ustawiona wartość true, składowa klasy reprezentuje kolumnę, która jest częścią klucza podstawowego tabeli. Jeśli więcej niż jeden element członkowski klasy jest wyznaczony jako identyfikator, klucz podstawowy jest uznawany za złożony skojarzonych kolumn.
Isdbgenerated Wartość logiczna Określa, że wartość kolumny elementu członkowskiego jest generowana automatycznie przez bazę danych. Klucze podstawowe, które są oznaczone IsDbGenerated=true , powinny również mieć typ DBType z modyfikatorem IDENTITY . Isdbgenerated elementy członkowskie są synchronizowane natychmiast po wstawieniu wiersza danych i są dostępne po zakończeniu funkcji SubmitChanges().
Isversion Wartość logiczna Określa typ kolumny elementu członkowskiego jako sygnaturę czasową bazy danych lub numer wersji. Numery wersji są zwiększane, a kolumny sygnatury czasowej są aktualizowane przez bazę danych za każdym razem, gdy skojarzony wiersz jest aktualizowany. Elementy członkowskie z parametrem IsVersion=true są synchronizowane natychmiast po zaktualizowaniu wiersza danych. Nowe wartości są widoczne po zakończeniu funkcji SubmitChanges().
Updatecheck Updatecheck Określa, jak LINQ to SQL implementuje optymistyczne wykrywanie konfliktów współbieżności. Jeśli żaden element członkowski nie zostanie wyznaczony jako IsVersion=true wykrywania, porównuje oryginalne wartości składowe z bieżącym stanem bazy danych. Możesz kontrolować, które elementy członkowskie LINQ to SQL używać podczas wykrywania konfliktów, dając każdemu członkowi wartość wyliczenia UpdateCheck.
  • Zawsze: zawsze używaj tej kolumny do wykrywania konfliktów
  • Nigdy: nigdy nie używaj tej kolumny do wykrywania konfliktów
  • WhenChanged: tej kolumny należy używać tylko wtedy, gdy element członkowski został zmieniony przez aplikację
IsDiscriminator Wartość logiczna Określa, czy składowa klasy posiada wartość dyskryminującą dla hierarchii dziedziczenia.
Wyrażenie Ciąg Nie ma wpływu na operację LINQ to SQL, ale jest używana podczas operacji .CreateDatabase() jako nieprzetworzone wyrażenie SQL reprezentujące obliczone wyrażenie kolumny.
CanBeNull Wartość logiczna Wskazuje, że wartość może zawierać wartość null. Jest to zwykle wnioskowane z typu CLR elementu członkowskiego jednostki. Użyj tego atrybutu, aby wskazać, że wartość ciągu jest reprezentowana jako niepusta kolumna w bazie danych.
Autosync Autosync Określa, czy kolumna jest automatycznie synchronizowana z wartości generowanej przez bazę danych w poleceniach wstawiania lub aktualizowania. Prawidłowe wartości tego tagu to OnInsert, Always i Never.

Typowa klasa jednostek będzie używać atrybutów kolumny we właściwościach publicznych i przechowywać rzeczywiste wartości w polach prywatnych.

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

Parametr DBType jest określony tylko tak, aby metoda CreateDatabase() mogła skonstruować tabelę z najbardziej precyzyjnym typem. W przeciwnym razie wiedza o tym, że kolumna bazowa jest ograniczona do 15 znaków, jest nieużywane.

Elementy członkowskie reprezentujące klucz podstawowy typu bazy danych często są skojarzone z wartościami generowanymi automatycznie.

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

Jeśli określisz wartość DBType, pamiętaj o dołączeniu modyfikatora IDENTITY . LINQ to SQL nie rozszerzy niestandardowego określonego typu DBType. Jeśli jednak parametr DBType nie zostanie określona LINQ to SQL będzie wnioskować, że modyfikator IDENTITY jest wymagany podczas tworzenia bazy danych za pomocą metody CreateDatabase().

Podobnie, jeśli właściwość IsVersion ma wartość true, parametr DBType musi określić poprawne modyfikatory, aby wyznaczyć numer wersji lub kolumnę sygnatury czasowej. Jeśli nie określono parametru DBType, LINQ to SQL będzie wnioskować poprawne modyfikatory.

Możesz kontrolować dostęp do elementu członkowskiego skojarzonego z automatycznie wygenerowaną kolumną, sygnaturą wersji lub dowolną kolumną, którą można ukryć, określając poziom dostępu elementu członkowskiego, a nawet ograniczając samą metodę dostępu.

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

Właściwość CustomerID zamówienia można wykonać tylko do odczytu, nie definiując zestawu dostępu. LINQ to SQL nadal może pobierać i ustawiać wartość bazową za pomocą elementu członkowskiego magazynu.

Element członkowski może być również całkowicie niedostępny w pozostałej części aplikacji, umieszczając atrybut Kolumna w prywatnym elemencie członkowskim. Dzięki temu klasa jednostki może zawierać informacje istotne dla logiki biznesowej klasy, nie ujawniając jej ogólnie. Mimo że prywatne elementy członkowskie są częścią przetłumaczonych danych, ponieważ są prywatne, nie można odwoływać się do nich w zapytaniu zintegrowanym ze językiem.

Domyślnie wszystkie elementy członkowskie są używane do wykrywania optymistycznego konfliktu współbieżności. Możesz kontrolować, czy określony element członkowski jest używany, określając jego wartość UpdateCheck .

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

W poniższej tabeli przedstawiono dopuszczalne mapowania między typami baz danych i odpowiadającym mu typem CLR. Użyj tej tabeli jako przewodnika po określeniu, którego typu CLR użyć do reprezentowania określonej kolumny bazy danych.

Typ bazy danych i odpowiednie mapowania typu CLR

Typ bazy danych Typ środowiska CLR platformy .NET Komentarze
bit, tinyint, smallint, int, bigint Bye, Int16, Uint16, Int32, Uint32, Int64, Uint64 Możliwe konwersje strat. Wartości mogą nie być zaokrąglone.
bit Wartość logiczna  
dziesiętne, liczbowe, małe, pieniądze Liczba dziesiętna Różnica w skali może spowodować utratę konwersji. Nie może być w obie strony.
real, float Single, Double Różnice w precyzji.
char, varchar, text, nchar, nvarchar, ntext Ciąg Możliwe różnice ustawień regionalnych.
datetime, smalldatetime DateTime Inna precyzja może powodować problemy z konwersją strat i przejazdami.
uniqueidentifier Guid (identyfikator GUID) Różne reguły sortowania. Sortowanie może nie działać zgodnie z oczekiwaniami.
sygnatura czasowa Bajty[] (Byte() w Visual Basic), binarne Tablica bajtów jest traktowana jako typ skalarny. Użytkownik jest odpowiedzialny za przydzielanie odpowiedniego magazynu, gdy jest wywoływany konstruktor. Jest on uznawany za niezmienny i nie jest śledzony pod kątem zmian.
binarne, varbinary Bajty[] (Byte() w Visual Basic), binarne  

Atrybut skojarzenia

Atrybut Skojarzenie służy do wyznaczania właściwości reprezentującej skojarzenie bazy danych, takie jak klucz obcy relacji klucza podstawowego.

Atrybut skojarzenia

Właściwość Typ Opis
Nazwa Ciąg Nazwa skojarzenia. Jest to często takie samo jak nazwa ograniczenia klucza obcego bazy danych. Jest on używany, gdy metoda CreateDatabase() służy do tworzenia wystąpienia bazy danych w celu wygenerowania odpowiedniego ograniczenia. Służy również do rozróżniania wielu relacji w jednej klasie jednostki odwołującej się do tej samej docelowej klasy jednostek. W takim przypadku właściwości relacji po bokach relacji (jeśli obie są zdefiniowane) muszą mieć taką samą nazwę.
Storage Ciąg Nazwa bazowego elementu członkowskiego magazynu. Jeśli zostanie określony, informuje LINQ to SQL, jak pominąć dostęp do właściwości publicznej dla elementu członkowskiego danych i wchodzić w interakcję z samą wartością nieprzetworzonej. Jeśli nie określono LINQ to SQL pobiera i ustawia wartość przy użyciu publicznego dostępu. Zaleca się, aby wszyscy członkowie skojarzenia znajdowali się we właściwościach z zidentyfikowanymi oddzielnymi członkami magazynu.
ThisKey Ciąg Rozdzielona przecinkami lista nazw co najmniej jednej składowej tej klasy jednostki reprezentującej wartości klucza po tej stronie skojarzenia. Jeśli nie zostanie określony, zakłada się, że członkowie są członkami tworzącym klucz podstawowy.
OtherKey Ciąg Rozdzielona przecinkami lista nazw co najmniej jednej składowej docelowej klasy jednostki reprezentującej wartości klucza po drugiej stronie skojarzenia. Jeśli nie zostanie określony, zakłada się, że elementy członkowskie składają się na klucz podstawowy drugiej klasy jednostek.
Isunique Wartość logiczna Prawda, jeśli istnieje ograniczenie unikatowości klucza obcego, co oznacza relację true 1:1. Ta właściwość jest rzadko używana, ponieważ relacje 1:1 są prawie niemożliwe do zarządzania w bazie danych. W większości modele jednostek są definiowane przy użyciu relacji 1:n nawet wtedy, gdy są traktowane jako 1:1 przez deweloperów aplikacji.
IsForeignKey Wartość logiczna Prawda , jeśli docelowy typ "inny" skojarzenia jest elementem nadrzędnym typu źródłowego. W przypadku relacji klucza obcego do klucza podstawowego strona trzymająca klucz obcy jest elementem podrzędnym, a strona trzymająca klucz podstawowy jest elementem nadrzędnym.
Deleterule Ciąg Służy do dodawania zachowania usuwania do tego skojarzenia. Na przykład polecenie "CASCADE" spowoduje dodanie ciągu "ON DELETE CASCADE" do relacji FK. Jeśli ustawiono wartość null, żadne zachowanie usuwania nie zostanie dodane.

Właściwości skojarzenia reprezentują pojedyncze odwołanie do innego wystąpienia klasy jednostek lub reprezentują kolekcję odwołań. Odwołania jednotonowe muszą być zakodowane w klasie jednostki przy użyciu typu wartości EntityRef T> (EntityRef<(OfT) w visual basic, aby przechowywać rzeczywiste odwołanie. Typ EntityRef polega na tym, jak LINQ to SQL umożliwia odroczone ładowanie odwołań.

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

Właściwość publiczna jest typowana jako Klient, a nie EntityRef<Customer>. Ważne jest, aby nie uwidocznić typu EntityRef w ramach publicznego interfejsu API, ponieważ odwołania do tego typu w zapytaniu nie zostaną przetłumaczone na język SQL.

Podobnie właściwość skojarzenia reprezentująca kolekcję musi używać typu kolekcji EntitySet T> (EntitySet<(OfT) w języku Visual Basic, aby przechowywać relację.

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

Jednak ponieważ zestaw EntitySet T> (EntitySet<(OfT) w Visual Basic jest kolekcją, ważne jest użycie zestawu EntitySet jako typu zwracanego. Prawidłowe jest również ukrycie prawdziwego typu kolekcji przy użyciu interfejsu ICollection T> (ICollection<(OfT) w Visual Basic.

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

Pamiętaj, aby użyć metody Assign() w zestawie EntitySet , jeśli uwidaczniasz publiczny zestaw dla właściwości. Dzięki temu klasa jednostki będzie nadal korzystać z tego samego wystąpienia kolekcji, ponieważ może być już powiązana z usługą śledzenia zmian.

Atrybut ResultType

Ten atrybut określa typ elementu sekwencji wyliczalnej, którą można zwrócić z funkcji zadeklarowanej w celu zwrócenia interfejsu IMultipleResults . Ten atrybut można określić więcej niż raz.

Atrybut ResultType

Właściwość Typ Opis
Typ Typ Typ zwróconych wyników.

Atrybut StoredProcedure

Atrybut StoredProcedure służy do deklarowania, że wywołanie metody zdefiniowanej w typie DataContext lub Schema jest tłumaczone jako wywołanie procedury składowanej bazy danych.

Atrybut StoredProcedure

Właściwość Typ Opis
Nazwa Ciąg Nazwa procedury składowanej w bazie danych. Jeśli nie określono procedury składowanej, przyjmuje się, że ma taką samą nazwę jak metoda

Atrybut funkcji

Atrybut funkcji służy do deklarowania, że wywołanie metody zdefiniowanej w obiekcie DataContext lub Schema jest tłumaczone jako wywołanie funkcji skalarnej lub tabeli zdefiniowanej przez użytkownika.

Atrybut funkcji

Właściwość Typ Opis
Nazwa Ciąg Nazwa funkcji w bazie danych. Jeśli nie określono, zakłada się, że funkcja ma taką samą nazwę jak metoda

Atrybut parametru

Atrybut Parametr służy do deklarowania mapowania między metodą a parametrami procedury składowanej bazy danych lub funkcji zdefiniowanej przez użytkownika.

Atrybut parametru

Właściwość Typ Opis
Nazwa Ciąg Nazwa parametru w bazie danych. Jeśli parametr nie zostanie określony, zostanie wywnioskowany z nazwy parametru metody.
Dbtype Ciąg Typ parametru określonego przy użyciu typów baz danych i modyfikatorów.

Atrybut DziedziczenieMapping

Atrybut InheritanceMapping służy do opisywania korespondencji między określonymi kodami dyskryminującymi a podtypem dziedziczenia. Wszystkie atrybuty DziedziczenieMapping używane do hierarchii dziedziczenia muszą być zadeklarowane w głównym typie hierarchii.

Atrybut DziedziczenieMapping

Propety Typ Opis
Kod Obiekt Dyskryminująca wartość kodu.
Typ Typ Podtyp dziedziczenia. Może to być dowolny typ nie abstrakcyjny w hierarchii dziedziczenia, w tym typ główny.
Isdefault Wartość logiczna Określa, czy określony podtyp dziedziczenia jest domyślnym typem skonstruowanym, gdy LINQ to SQL znajdzie kod dyskryminujący, który nie jest zdefiniowany przez atrybuty DziedziczenieMapping. Dokładnie jeden z atrybutów DziedziczenieMapping musi być zadeklarowany za pomocą funkcji IsDefault jako true.

Spójność grafu

Graf to ogólny termin dla struktury danych obiektów, które odwołują się do siebie nawzajem przy użyciu odwołań. Hierarchia (lub drzewo) jest zdegeneraną formą grafu. Modele obiektów specyficznych dla domeny często opisują sieć odwołań, które najlepiej opisują jako wykres obiektów. Kondycja grafu obiektu jest niezwykle ważna dla stabilności aplikacji. Dlatego ważne jest, aby upewnić się, że odwołania w obrębie grafu pozostają spójne z regułami biznesowymi i/lub ograniczeniami zdefiniowanymi w bazie danych.

LINQ to SQL nie zarządza automatycznie spójnością odwołań do relacji. Gdy relacje są dwukierunkowe, zmiana na jedną stronę relacji powinna zostać automatycznie zaktualizowana. Należy pamiętać, że zwykle normalne obiekty zachowują się w ten sposób, więc jest mało prawdopodobne, że obiekty zostałyby zaprojektowane w ten sposób w inny sposób.

LINQ to SQL udostępnia kilka mechanizmów ułatwiania tej pracy i wzorca, który należy wykonać, aby upewnić się, że zarządzasz odwołaniami poprawnie. Klasy jednostek generowane przez narzędzie generowania kodu będą automatycznie implementować poprawne wzorce.

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) w visual basic ma konstruktor, który umożliwia podanie dwóch delegatów do użycia jako wywołania zwrotnego; pierwszy po dodaniu elementu do kolekcji, drugi po jego usunięciu. Jak widać w przykładzie, kod określony dla tych delegatów może i powinien zostać zapisany w celu zaktualizowania właściwości odwrotnej relacji. W ten sposób właściwość Customer w wystąpieniu Zamówienia jest automatycznie zmieniana po dodaniu zamówienia do kolekcji Orders klienta.

Implementacja relacji na drugim końcu nie jest tak prosta. EntityRef T> (EntityRef<(OfT) w Visual Basic) to typ wartości zdefiniowany tak, aby zawierał jak najmniej dodatkowe obciążenie z rzeczywistego odwołania do obiektu, jak to możliwe. Nie ma miejsca na parę delegatów. Zamiast tego kod zarządzający spójnością grafów odwołań pojedynczych powinien być osadzony w samych metod dostępu do właściwości.

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

Przyjrzyj się ustawieniu. Gdy właściwość Customer jest zmieniana, wystąpienie zamówienia jest najpierw usuwane z kolekcji Orders bieżącego klienta, a następnie dopiero później dodane do kolekcji nowego klienta. Zwróć uwagę, że przed wywołaniem metody Remove() rzeczywiste odwołanie do jednostki jest ustawione na wartość null. Jest to wykonywane, aby uniknąć rekursji po wywołaniu metody Remove( ). Pamiętaj, że zestaw EntitySet użyje delegatów wywołania zwrotnego, aby przypisać właściwość Customer tego obiektu do wartości null. To samo dzieje się bezpośrednio przed wywołaniem metody Add(). Rzeczywiste odwołanie do jednostki jest aktualizowane do nowej wartości. To ponownie ograniczy wszelkie potencjalne rekursje i oczywiście wykona zadanie setter w pierwszej kolejności.

Definicja relacji jeden do jednego jest bardzo podobna do definicji relacji jeden do wielu po stronie odwołania pojedynczego. Zamiast wywoływanej metody Add() i Remove() jest przypisywany nowy obiekt lub przypisano wartość null do zerwania relacji.

Ponownie ważne jest, aby właściwości relacji zachowały spójność grafu obiektów. Jeśli graf obiektu w pamięci jest niespójny z danymi bazy danych, podczas wywoływanej metody SubmitChanges jest generowany wyjątek czasu wykonywania. Rozważ użycie narzędzia do generowania kodu w celu zachowania spójności.

Zmienianie powiadomień

Obiekty mogą uczestniczyć w procesie śledzenia zmian. Nie jest to wymagane, ale może znacznie zmniejszyć nakład pracy potrzebny do śledzenia potencjalnych zmian obiektów. Prawdopodobnie aplikacja pobierze o wiele więcej obiektów z zapytań niż zostanie zmodyfikowanych. Bez proaktywnej pomocy z Twoich obiektów usługa śledzenia zmian jest ograniczona w sposobie śledzenia zmian.

Ponieważ w środowisku uruchomieniowym nie ma prawdziwej usługi przechwytywania, formalne śledzenie w rzeczywistości nie występuje. Zamiast tego zduplikowane kopie obiektów są przechowywane podczas ich pierwszego pobierania. Później, po wywołaniu metody SubmitChanges(), te kopie są używane do porównywania z tymi, które zostały podane. Jeśli ich wartości są różne, obiekt został zmodyfikowany. Oznacza to, że każdy obiekt wymaga dwóch kopii w pamięci, nawet jeśli nigdy ich nie zmienisz.

Lepszym rozwiązaniem jest to, że obiekty same ogłaszają usługę śledzenia zmian, gdy rzeczywiście zostaną zmienione. Można to osiągnąć przez zaimplementowanie interfejsu implementowania interfejsu, który uwidacznia zdarzenie wywołania zwrotnego. Usługa śledzenia zmian może następnie połączyć każdy obiekt i otrzymywać powiadomienia po zmianie.

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 ułatwić ulepszone śledzenie zmian, klasy jednostek muszą zaimplementować interfejs INotifyPropertyChanging . Wymaga tylko zdefiniowania zdarzenia o nazwie PropertyChanging — usługa śledzenia zmian rejestruje się w zdarzeniu, gdy obiekty wchodzą w jego posiadanie. Wszystko, co musisz zrobić, to zgłosić to zdarzenie bezpośrednio przed zmianą wartości właściwości.

Nie zapomnij umieścić tej samej logiki podnoszenia zdarzeń w zestawach właściwości relacji. W przypadku zestawów EntitySet należy zgłaszać zdarzenia w dostarczanych delegatach.

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

Dziedziczenie

LINQ to SQL obsługuje mapowanie z jedną tabelą, w której cała hierarchia dziedziczenia jest przechowywana w pojedynczej tabeli bazy danych. Tabela zawiera spłaszczone połączenie wszystkich możliwych kolumn danych dla całej hierarchii, a każdy wiersz zawiera wartości null w kolumnach, które nie mają zastosowania do typu wystąpienia reprezentowanego przez wiersz. Strategia mapowania pojedynczej tabeli jest najprostszą reprezentacją dziedziczenia i zapewnia dobrą charakterystykę wydajności dla wielu różnych kategorii zapytań.

Mapowanie

Aby zaimplementować to mapowanie przy użyciu LINQ to SQL, należy określić następujące atrybuty i właściwości atrybutów w klasie głównej hierarchii dziedziczenia:

  • Atrybut [Table] (<Tabela> w Visual Basic).
  • Atrybut [InheritanceMapping] (<InheritanceMapping> w Visual Basic) dla każdej klasy w strukturze hierarchii. W przypadku klas nie abstrakcyjnych ten atrybut musi zdefiniować właściwość Code (wartość wyświetlaną w tabeli bazy danych w kolumnie Dyskryminator dziedziczenia, aby wskazać, która klasa lub podklasa należy do tego wiersza danych) i właściwość Type (która określa klasę lub podklasę oznacza wartość klucza).
  • Właściwość IsDefault pojedynczego atrybutu [InheritanceMapping] (<InheritanceMapping> w Visual Basic). Ta właściwość służy do wyznaczania mapowania "rezerwowego" w przypadku, gdy wartość dyskryminująca z tabeli bazy danych nie jest zgodna z żadną wartością Kodu w mapowaniach dziedziczenia.
  • Właściwość IsDiscriminator dla atrybutu [Column] (<Kolumna> w Języku Visual Basic) oznacza, że jest to kolumna zawierająca wartość Code dla mapowania dziedziczenia.

W podklasach nie są wymagane żadne atrybuty specjalne ani właściwości. Należy pamiętać, że podklasy nie mają atrybutu [Table] (<Tabela> w Visual Basic).

W poniższym przykładzie dane zawarte w podklasach Car and Truck są mapowane na pojedynczą tabelę bazy danych Vehicle. (Aby uprościć przykład, przykładowy kod używa pól, a nie właściwości mapowania kolumn).

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 klas wygląda następująco:

Rysunek 1. Diagram klas pojazdów

Po wyświetleniu wynikowego diagramu bazy danych w Eksploratorze serwera zobaczysz, że wszystkie kolumny zostały zamapowane na jedną tabelę, jak pokazano poniżej:

Rysunek 2. Kolumny mapowane na pojedynczą tabelę

Należy pamiętać, że typy kolumn reprezentujących pola w podtypach muszą mieć wartość null lub muszą mieć określoną wartość domyślną. Jest to konieczne, aby polecenia wstawiania zakończyły się pomyślnie.

Wykonywanie zapytania

Poniższy kod zawiera informacje o tym, jak można używać typów pochodnych w zapytaniach:

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

Zaawansowany

Hierarchię można rozszerzyć znacznie poza już udostępniony prosty przykład.

Przykład 1

Oto znacznie głębsza hierarchia i bardziej złożone zapytanie:

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)

Przykład 2

Poniższa hierarchia obejmuje interfejsy:

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

Możliwe zapytania obejmują następujące elementy:

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

Tematy zaawansowane

Tworzenie baz danych

Ponieważ klasy jednostek mają atrybuty opisujące strukturę tabel i kolumn relacyjnych baz danych, można użyć tych informacji do utworzenia nowych wystąpień bazy danych. Możesz wywołać metodę CreateDatabase() w obiekcie DataContext, aby LINQ to SQL utworzyć nowe wystąpienie bazy danych ze strukturą zdefiniowaną przez obiekty. Istnieje wiele powodów, dla których warto to zrobić: możesz utworzyć aplikację, która automatycznie instaluje się w systemie klienta, lub aplikację kliencką, która wymaga lokalnej bazy danych w celu zapisania stanu offline. W przypadku tych scenariuszy baza danych CreateDatabase() jest idealna — zwłaszcza jeśli jest dostępny znany dostawca danych, taki jak SQL Server Express 2005.

Jednak atrybuty danych mogą nie kodować wszystkich elementów dotyczących istniejącej struktury bazy danych. Zawartość funkcji zdefiniowanych przez użytkownika, procedur składowanych, wyzwalaczy i ograniczeń sprawdzania nie są reprezentowane przez atrybuty. Funkcja CreateDatabase() utworzy replikę bazy danych tylko przy użyciu informacji, które zna, czyli struktury bazy danych i typów kolumn w każdej tabeli. Jednak w przypadku różnych baz danych jest to wystarczające.

Poniżej przedstawiono przykład tworzenia nowej bazy danych o nazwie 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

Model obiektów może służyć do tworzenia bazy danych przy użyciu SQL Server Express 2005 w następujący sposób:

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 udostępnia również interfejs API do porzucania istniejącej bazy danych przed utworzeniem nowej bazy danych. Powyższy kod tworzenia bazy danych można zmodyfikować, aby najpierw sprawdzić istniejącą wersję bazy danych przy użyciu elementu DatabaseExists(), a następnie usunąć go przy użyciu metody DeleteDatabase().

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 wywołaniu metody CreateDatabase()nowa baza danych może akceptować zapytania i polecenia, takie jak SubmitChanges(), aby dodać obiekty do pliku MDF.

Istnieje również możliwość użycia metody CreateDatabase() z jednostkami SKU inną niż SQL Server Express przy użyciu pliku MDF lub tylko nazwy katalogu. Wszystko zależy od tego, czego używasz dla parametrów połączenia. Informacje w parametrach połączenia służą do definiowania bazy danych, która będzie istnieć, niekoniecznie taka, która już istnieje. LINQ to SQL wyłowi odpowiednie bity informacji i użyje ich do określenia, jaką bazę danych utworzyć i na jakim serwerze utworzyć. Oczywiście do tego celu potrzebne są prawa administratora bazy danych lub równoważne na serwerze.

Współdziałanie z ADO.NET

LINQ to SQL jest częścią rodziny technologii ADO.NET. Jest ona oparta na usługach udostępnianych przez model dostawcy ADO.NET, więc istnieje możliwość połączenia kodu LINQ to SQL z istniejącymi aplikacjami ADO.NET.

Podczas tworzenia LINQ to SQL DataContext można podać go przy użyciu istniejącego połączenia ADO.NET. Wszystkie operacje względem elementu DataContext — w tym zapytania — będą używać podanego połączenia. Jeśli połączenie zostało już otwarte LINQ to SQL będzie honorować twoje uprawnienia za pośrednictwem połączenia i pozostawić je tak, jak po zakończeniu z nim. Zwykle LINQ to SQL zamyka swoje połączenie zaraz po zakończeniu operacji, chyba że transakcja jest w zakresie.

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

Zawsze możesz uzyskać dostęp do połączenia używanego przez element DataContext za pośrednictwem właściwości Connection i zamknąć je samodzielnie.

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

Możesz również podać element DataContext z własną transakcją bazy danych, na wypadek, gdy aplikacja zainicjowała ją i chcesz, aby obiekt DataContext był odtwarzany wraz z nim.

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

Za każdym razem, gdy transakcja jest ustawiona, funkcja DataContext będzie używać jej za każdym razem, gdy wysyła zapytanie lub wykonuje polecenie. Nie zapomnij przypisać właściwości z powrotem do wartości null po zakończeniu pracy.

Jednak preferowaną metodą wykonywania transakcji z .NET Framework jest użycie obiektu TransactionScope. Umożliwia ona wykonywanie transakcji rozproszonych, które działają między bazami danych i innymi menedżerami zasobów rezydenta pamięci. Chodzi o to, że zakresy transakcji zaczynają się tanie, tylko promowanie się do pełnej transakcji rozproszonej, gdy rzeczywiście odwołują się do wielu baz danych lub wielu połączeń w zakresie transakcji.

C#

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

Visual Basic

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

Bezpośrednie wykonywanie instrukcji SQL

Połączenia i transakcje nie są jedynym sposobem współdziałania z ADO.NET. W niektórych przypadkach zapytanie lub przesłanie obiektu zmiany obiektu DataContext jest niewystarczające dla wyspecjalizowanego zadania, które warto wykonać. W takich okolicznościach można użyć elementu DataContext do wydawania nieprzetworzonych poleceń SQL bezpośrednio do bazy danych.

Metoda ExecuteQuery() umożliwia wykonywanie nieprzetworzonego zapytania SQL i konwertowanie wyniku zapytania bezpośrednio na obiekty. Na przykład przy założeniu, że dane klasy Customer są rozłożone na dwie tabele customer1 i customer2, następujące zapytanie zwraca sekwencję obiektów Klient .

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

Tak długo, jak nazwy kolumn w wynikach tabelarycznych pasują do właściwości kolumny klasy jednostki, LINQ to SQL zmaterializuje obiekty z dowolnego zapytania SQL.

Metoda ExecuteQuery() zezwala również na parametry. W poniższym kodzie wykonywane jest zapytanie sparametryzowane:

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 są wyrażane w tekście zapytania przy użyciu tej samej notacji curly używanej przez Console.WriteLine() i String.Format(). W rzeczywistości funkcja String.Format() jest faktycznie wywoływana dla podanego ciągu zapytania, podstawiając parametry nawiasów klamrowych z wygenerowanymi nazwami parametrów, takimi jak p0, @p1 ..., p(n).

Zmienianie rozwiązywania konfliktów

Opis

Konflikt zmian występuje, gdy klient próbuje przesłać zmiany do obiektu i co najmniej jedna wartość używana w sprawdzaniu aktualizacji została zaktualizowana w bazie danych od czasu ostatniego odczytu klienta.

Uwaga Tylko członkowie mapowani jako UpdateCheck.Always lub UpdateCheck.WhenChanged uczestniczą w optymistycznych kontrolach współbieżności. Nie jest wykonywane sprawdzanie dla członków oznaczonych updateCheck.Never.

Rozwiązanie tego konfliktu obejmuje wykrycie, które elementy członkowskie obiektu są w konflikcie, a następnie podjęcie decyzji, co z tym zrobić. Należy pamiętać, że optymistyczna współbieżność może nie być najlepszą strategią w danej sytuacji. Czasami jest to całkowicie uzasadnione, aby "niech ostatnia aktualizacja wygrać".

Wykrywanie, raportowanie i rozwiązywanie konfliktów w LINQ to SQL

Rozwiązywanie konfliktów to proces odświeżania elementu powodującego konflikt przez ponowne wykonywanie zapytań względem bazy danych i uzgadnianie wszelkich różnic. Po odświeżeniu obiektu monitor zmian ma stare oryginalne wartości i nowe wartości bazy danych. LINQ to SQL następnie określa, czy obiekt jest w konflikcie, czy nie. Jeśli tak jest, LINQ to SQL określa, którzy członkowie są zaangażowani. Jeśli nowa wartość bazy danych dla elementu członkowskiego różni się od starego oryginału (który był używany do sprawdzania aktualizacji, który zakończył się niepowodzeniem), jest to konflikt. Wszystkie konflikty elementów członkowskich są dodawane do listy konfliktów.

Na przykład w poniższym scenariuszu użytkownik User1 rozpoczyna przygotowywanie aktualizacji przez wykonanie zapytania względem bazy danych dla wiersza. Zanim użytkownik User1 będzie mógł przesłać zmiany, użytkownik User2 zmienił bazę danych. Przesyłanie użytkownika User1 kończy się niepowodzeniem, ponieważ wartości oczekiwane dla kolumn Col B i Col C uległy zmianie.

Konflikt aktualizacji bazy danych

Użytkownik Kolumna A Kolumna B Kolumna C
Stan oryginalny Alfreds Maria Sales
Użytkownik 1 Alfred   Marketing
Użytkownik 2   Mary Usługa

W LINQ to SQL zgłaszane są obiekty, których nie można zaktualizować z powodu konfliktów optymistycznej współbieżności, powodują wyjątek (ChangeConflictException). Można określić, czy wyjątek powinien zostać zgłoszony przy pierwszym niepowodzeniu, czy też wszystkie aktualizacje powinny być podejmowane z powodu gromadzenia i zgłaszania wszystkich błędów w wyjątku.

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

Podczas zgłaszania wyjątek zapewnia dostęp do kolekcji ObjectChangeConflict . Szczegóły są dostępne dla każdego konfliktu (zamapowanego na jedną nieudaną próbę aktualizacji), w tym dostęp do listy MemberConflicts . Każdy konflikt elementów członkowskich jest mapowany na jeden element członkowski w aktualizacji, która nie powiodła się podczas sprawdzania współbieżności.

Obsługa konfliktów

W poprzednim scenariuszu użytkownik User1 ma opcje RefreshMode opisane poniżej w celu uzgadniania różnic przed podjęciem próby ponownego przesłanie. We wszystkich przypadkach rekord na kliencie jest najpierw "odświeżony", ściągając zaktualizowane dane z bazy danych. Ta akcja gwarantuje, że kolejna próba aktualizacji nie zakończy się niepowodzeniem podczas tych samych testów współbieżności.

W tym miejscu użytkownik User1 wybiera scalanie wartości bazy danych z bieżącymi wartościami klienta, aby wartości bazy danych zostały zastąpione tylko wtedy, gdy bieżący zestaw zmian również zmodyfikował wartość. (Zobacz przykład 1 w dalszej części tej sekcji).

W powyższym scenariuszu po rozwiązaniu konfliktu wynik w bazie danych jest następujący:

Zachowaj zmiany

  Kolumna A Kolumna B Kolumna C
Zachowaj zmiany Alfred (użytkownik 1) Mary (użytkownik 2) Marketing (użytkownik 1)
  • Kolumna A: Zostanie wyświetlona zmiana użytkownika User1 (Alfred).
  • Col B: Zostanie wyświetlona zmiana użytkownika User2 (Mary). Ta wartość została scalona, ponieważ użytkownik User1 nie zmienił tej wartości.
  • Kolumna C: Zostanie wyświetlona zmiana użytkownika User1 (Marketing). Zmiana użytkownika User2 (usługa) nie jest scalona, ponieważ użytkownik User1 również zmienił ten element.

Poniżej użytkownik User1 decyduje się zastąpić wszystkie wartości bazy danych bieżącymi wartościami. (Zobacz przykład 2 w dalszej części tej sekcji).

Po odświeżeniu zostaną przesłane zmiany użytkownika User1. Wynik w bazie danych jest następujący:

KeepCurrentValues

  Kolumna A Kolumna B Kolumna C
KeepCurrentValues Alfred (użytkownik 1) Maria (oryginalna) Marketing (użytkownik 1)
  • Kolumna A: Zostanie wyświetlona zmiana użytkownika User1 (Alfred).
  • Płk B: Oryginalna Maria pozostaje; Zmiana użytkownika User2 jest odrzucana.
  • Kolumna C: Zostanie wyświetlona zmiana użytkownika User1 (Marketing). Zmiana użytkownika User2 (usługa) jest odrzucana.

W następnym scenariuszu użytkownik User1 decyduje się zezwolić wartościom bazy danych na zastąpienie bieżących wartości w kliencie. (Zobacz przykład 3 w dalszej części tej sekcji).

W powyższym scenariuszu po rozwiązaniu konfliktu wynik w bazie danych jest następujący:

OverwriteCurrentValues

  Kolumna A Kolumna B Kolumna C
OverwriteCurrentValues Alfreds (oryginalny) Mary (użytkownik 2) Usługa (użytkownik 2)
  • Col A: Oryginalna wartość (Alfreds) pozostaje; Wartość user1 (Alfred) jest odrzucana.
  • Col B: Zostanie wyświetlona zmiana użytkownika User2 (Mary).
  • Kolumna C: Zostanie wyświetlona zmiana użytkownika (usługa). Zmiana użytkownika User1 (Marketing) została odrzucona.

Po rozwiązaniu konfliktów możesz spróbować przesłać ponownie. Ponieważ ta druga aktualizacja może również zakończyć się niepowodzeniem, rozważ użycie pętli dla prób aktualizacji.

Przykłady

W poniższych fragmentach kodu przedstawiono różne informacyjne elementy członkowskie i techniki dostępne do odnajdywania i rozwiązywania konfliktów składowych.

Przykład 1

W tym przykładzie konflikty są rozwiązywane "automatycznie". Oznacza to, że wartości bazy danych są scalane z bieżącymi wartościami klienta, chyba że klient zmienił również wartość (KeepChanges). Nie odbywa się inspekcja ani niestandardowa obsługa konfliktów poszczególnych elementów członkowskich.

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)

Przykład 2

W tym przykładzie konflikty są rozwiązywane ponownie bez żadnej obsługi niestandardowej. Jednak tym razem wartości bazy danych nie są scalane z bieżącymi wartościami 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

Przykład 3

Tutaj ponownie nie odbywa się żadna niestandardowa obsługa. Jednak w tym przypadku wszystkie wartości klienta są aktualizowane przy użyciu bieżących wartości bazy danych.

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

Przykład 4

W tym przykładzie pokazano sposób uzyskiwania dostępu do informacji o jednostce w konflikcie.

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

Przykład 5

W tym przykładzie jest dodawany pętla przez poszczególne elementy członkowskie. W tym miejscu można zapewnić niestandardową obsługę dowolnego elementu członkowskiego.

Uwaga Dodaj przy użyciu elementu System.Reflection; w celu podania informacji o elemencie MemberInfo.

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

Wywołanie procedur składowanych

LINQ to SQL obsługuje procedury składowane i funkcje zdefiniowane przez użytkownika. LINQ to SQL mapuje te abstrakcje zdefiniowane przez bazę danych na obiekty klienta wygenerowane przy użyciu kodu, dzięki czemu można uzyskiwać do nich dostęp w sposób silnie typizowany z poziomu kodu klienta. Te metody można łatwo odnaleźć przy użyciu funkcji IntelliSense, a sygnatury metody są jak najbardziej zbliżone do podpisów procedur i funkcji zdefiniowanych w bazie danych. Zestaw wyników zwracany przez wywołanie procedury mapowanej jest silnie typizowaną kolekcją. LINQ to SQL może automatycznie generować zamapowane metody, ale obsługuje również ręczne mapowanie w sytuacjach, w których nie chcesz używać generowania kodu.

LINQ to SQL mapuje procedury składowane i funkcje na metody za pomocą atrybutów. Atrybuty StoredProcedure, Parameter i Function obsługują właściwość Name, a atrybut Parameter obsługuje również właściwość DBType. Poniżej przedstawiono dwa przykłady:

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

W poniższych przykładach pokazano mapowania dla różnych rodzajów procedur składowanych.

Przykład 1

Poniższa procedura składowana przyjmuje pojedynczy parametr wejściowy i zwraca liczbę całkowitą:

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

Zamapowana metoda będzie następująca:

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

Przykład 2

Gdy procedura składowana może zwracać wiele kształtów wyników, zwracany typ nie może być silnie typowany do pojedynczego kształtu projekcji. W poniższym przykładzie kształt wyniku zależy od danych wejściowych:

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

Metoda mapowana jest następująca:

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

Tej procedury składowanej można użyć w następujący sposób:

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 
         
      }           

W tym miejscu musisz użyć wzorca GetResult , aby uzyskać moduł wyliczający poprawnego typu na podstawie wiedzy na temat procedury składowanej. LINQ to SQL może wygenerować wszystkie możliwe typy projekcji, ale nie ma możliwości poznania, w jakiej kolejności zostaną zwrócone. Jedynym sposobem, w jaki można wiedzieć, które wygenerowane typy projekcji odpowiadają metodzie mapowanej, jest użycie wygenerowanych komentarzy kodu do metod.

Przykład 3

Oto język T-SQL procedury składowanej, która zwraca sekwencyjnie wiele kształtów wyników:

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

LINQ to SQL zamapować tę procedurę tak jak w przykładzie 2 powyżej. W tym przypadku istnieją jednak dwa sekwencyjne zestawy wyników.

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

Tej procedury składowanej można użyć w następujący sposób:

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

Przykład 4

LINQ to SQL mapuje out parametry na parametry odwołania (słowo kluczowe ref), a dla typów wartości deklaruje parametr jako dopuszczalny wartość null (na przykład int?). Procedura w poniższym przykładzie przyjmuje pojedynczy parametr wejściowy i zwraca out parametr.

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

Metoda mapowana jest następująca:

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

W tym przypadku metoda nie ma jawnej wartości zwracanej, ale domyślna wartość zwracana jest mimo to mapowana. Dla parametru wyjściowego odpowiedni parametr wyjściowy jest używany zgodnie z oczekiwaniami.

Wywołasz powyższą procedurę składowaną w następujący sposób:

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)

Funkcje zdefiniowane przez użytkownika

LINQ to SQL obsługuje zarówno funkcje skalarne, jak i tabelowe oraz obsługują wbudowany odpowiednik obu tych funkcji.

LINQ to SQL obsługuje śródliniowe wywołania skalarne podobnie do sposobu wywoływanego przez system funkcji. Rozpatrzmy następujące zapytanie:

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)

Tutaj wywołanie metody Math.Floor jest tłumaczone na wywołanie funkcji systemowej "FLOOR". W ten sam sposób wywołanie funkcji mapowanej na funkcję zdefiniowaną przez użytkownika jest tłumaczone na wywołanie funkcji zdefiniowanej przez użytkownika w języku SQL.

Przykład 1

Oto funkcja zdefiniowana przez użytkownika (UDF) ReverseCustName(). W SQL Server funkcja może być zdefiniowana w następujący sposób:

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

Metodę kliencką zdefiniowaną w klasie schematu można mapować na tę funkcję zdefiniowaną przez użytkownika, korzystając z poniższego kodu. Zwróć uwagę, że treść metody tworzy wyrażenie, które przechwytuje intencję wywołania metody i przekazuje to wyrażenie do obiektu DataContext na potrzeby tłumaczenia i wykonywania. (To bezpośrednie wykonanie odbywa się tylko wtedy, gdy wywoływana jest funkcja).

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)

Przykład 2

W poniższym zapytaniu widać śródliniowe wywołanie metody ReverseCustName wygenerowanej metody UDF. W takim przypadku funkcja nie jest wykonywana natychmiast. Język SQL utworzony dla tego zapytania przekłada się na wywołanie funkcji zdefiniowanej w bazie danych (zobacz kod SQL po zapytaniu).

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]

Po wywołaniu tej samej funkcji poza zapytaniem LINQ to SQL tworzy proste zapytanie na podstawie wyrażenia wywołania metody przy użyciu następującej składni SQL (gdzie parametr @p0 jest powiązany ze stałą przekazaną):

W LINQ to SQL:

C#

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

Visual Basic

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

Konwertuje na:

SELECT dbo.ReverseCustName(@p0)

Przykład 3

Funkcja z wartością tabeli (TVF) zwraca pojedynczy zestaw wyników (w przeciwieństwie do procedur składowanych, które mogą zwracać wiele kształtów wyników). Ponieważ zwracany typ TVF jest tabelą, można użyć funkcji TVF w dowolnym miejscu w języku SQL, w której można użyć tabeli, i można traktować funkcję TVF w taki sam sposób, jak w przypadku tabeli.

Rozważmy następującą definicję SQL Server funkcji z wartością tabeli:

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

Ta funkcja jawnie stwierdza, że zwraca tabelę, więc zwracana struktura zestawu wyników jest niejawnie zdefiniowana. LINQ to SQL mapuje funkcję w następujący sposób:

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)

Poniższy kod SQL pokazuje, że można połączyć się z tabelą zwróconą przez funkcję i w inny sposób traktować ją tak, jak w przypadku każdej innej tabeli:

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

W LINQ to SQL zapytanie będzie renderowane w następujący sposób (przy użyciu nowej składni sprzężenia):

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 ograniczenia dotyczące procedur składowanych

LINQ to SQL obsługuje generowanie kodu dla procedur składowanych, które zwracają statycznie określone zestawy wyników. W związku z tym generator kodu LINQ to SQL nie obsługuje następujących elementów:

  • Procedury składowane, które używają dynamicznej bazy danych SQL do zwracania zestawów wyników. Jeśli procedura składowana zawiera logikę warunkową do utworzenia dynamicznej instrukcji SQL, LINQ to SQL nie może uzyskać metadanych dla zestawu wyników, ponieważ zapytanie używane do generowania zestawu wyników jest nieznane do czasu wykonywania.
  • Procedury składowane, które generują wyniki na podstawie tabeli tymczasowej.

Narzędzie Generator klas jednostek

Jeśli masz istniejącą bazę danych, nie trzeba ręcznie tworzyć kompletnego modelu obiektów, aby go przedstawić. Dystrybucja LINQ to SQL jest dostarczana z narzędziem o nazwie SQLMetal. Jest to narzędzie wiersza polecenia, które automatyzuje zadanie tworzenia klas jednostek przez wnioskowanie odpowiednich klas na podstawie metadanych bazy danych.

Za pomocą programu SQLMetal można wyodrębnić metadane SQL z bazy danych i wygenerować plik źródłowy zawierający deklaracje klasy jednostek. Alternatywnie można podzielić proces na dwa kroki, najpierw generując plik XML reprezentujący metadane SQL, a następnie tłumacząc ten plik XML na plik źródłowy zawierający deklaracje klas. Ten proces podziału umożliwia zachowanie metadanych jako pliku, dzięki czemu można je edytować. Proces wyodrębniania tworzący plik wykonuje kilka wnioskowań w drodze o odpowiednich nazwach klas i właściwości, biorąc pod uwagę nazwy tabel i kolumn bazy danych. Może okazać się konieczne edytowanie pliku XML, aby generator wygenerował więcej przyjemnych wyników lub ukryć aspekty bazy danych, których nie chcesz prezentować w obiektach.

Najprostszym scenariuszem użycia programu SQLMetal jest bezpośrednie generowanie klas z istniejącej bazy danych. Oto jak wywołać narzędzie:

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

Wykonanie narzędzia powoduje utworzenie pliku Northwind.cs lub vb zawierającego model obiektów wygenerowany przez odczytanie metadanych bazy danych. To użycie sprawdza się, jeśli nazwy tabel w bazie danych są podobne do nazw obiektów, które chcesz wygenerować. Jeśli nie, należy podjąć podejście dwuetapowe.

Aby poinstruować program SQLMetal, aby wygenerował plik DBML, użyj narzędzia w następujący sposób:

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

Po wygenerowaniu pliku dbml możesz dodać do niego adnotacje z atrybutem klasy i właściwości , aby opisać sposób mapowania tabel i kolumn na klasy i właściwości. Po zakończeniu dodawania adnotacji do pliku dbml można wygenerować model obiektów, uruchamiając następujące polecenie:

C#

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

Visual Basic

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

Sygnatura użycia programu SQLMetal jest następująca:

SqlMetal [options] [filename]

Poniżej znajduje się tabela przedstawiająca dostępne opcje wiersza polecenia dla programu SQLMetal.

Opcje wiersza polecenia dla programu SQLMetal

Opcja Opis
/server:<name> Wskazuje serwer do nawiązania połączenia w celu uzyskania dostępu do bazy danych.
/database:<name> Wskazuje nazwę bazy danych do odczytu metadanych.
/user:<name> Identyfikator użytkownika logowania dla serwera.
/password:<name> Hasło logowania dla serwera.
/Widoki Wyodrębnianie widoków bazy danych.
/Funkcje Wyodrębnianie funkcji bazy danych.
/sprocs Wyodrębnianie procedur składowanych.
/code[:<nazwa pliku>] Wskazuje, że dane wyjściowe narzędzia są plikiem źródłowym deklaracji klas jednostek.
/language:<language> Użyj języka Visual Basic lub C# (ustawienie domyślne).
/xml[:<nazwa pliku>] Wskazuje, że dane wyjściowe narzędzi to plik DBML opisujący metadane bazy danych oraz pierwsze przybliżenie nazw klas i właściwości.
/map[:<nazwa pliku>] Wskazuje, że plik mapowania zewnętrznego powinien być używany zamiast atrybutów.
/pluralize Wskazuje, że narzędzie powinno wykonywać mnogi język angielski/ de-pluralizowanie heurystyki do nazw tabel w celu utworzenia odpowiednich nazw klas i właściwości.
/namespace:<name> Wskazuje przestrzeń nazw, w których będą generowane klasy jednostek.
/timeout:<s> Wartość limitu czasu w sekundach do użycia dla poleceń bazy danych.

Uwaga Aby wyodrębnić metadane z pliku MDF, należy podać nazwę pliku MDF po wszystkich innych opcjach. Jeśli nie określono /server jest określony localhost jest zakładany.

Dokumentacja dbML narzędzia generatora

Plik DBML (Database Mapping Language) jest przede wszystkim opisem metadanych SQL dla danej bazy danych. Jest wyodrębniany przez program SQLMetal, przeglądając metadane bazy danych. Ten sam plik jest również używany przez program SQLMetal do generowania domyślnego modelu obiektów do reprezentowania bazy danych.

Oto prototypowy przykład składni 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>

Elementy i ich atrybuty są opisane w następujący sposób.

baza danych

Jest to najbardziej zewnętrzny element w formacie XML. Ten element jest luźno mapowany na atrybut Database na wygenerowany element DataContext.

Atrybuty bazy danych

Atrybut Typ Domyślny Opis
Nazwa Ciąg Brak Nazwa bazy danych. W przypadku obecności i wygenerowania elementu DataContext dołączy do niego atrybut Database o tej nazwie. Jest również używana jako nazwa klasy DataContext , jeśli atrybut klasy nie jest obecny.
EntityNamespace Silna Brak Domyślna przestrzeń nazw dla klas wygenerowanych na podstawie elementów typu w ramach elementów tabeli. Jeśli w tym miejscu nie określono żadnej przestrzeni nazw, klasy jednostek są generowane w głównej przestrzeni nazw.
ContextNamespace Ciąg Brak Domyślna przestrzeń nazw dla wygenerowanej klasy DataContext . Jeśli w tym miejscu nie określono żadnej przestrzeni nazw, klasa DataContext jest generowana w głównej przestrzeni nazw.
Klasa Ciąg Database.Name Nazwa wygenerowanej klasy DataContext . Jeśli nie ma, użyj atrybutu Name elementu Database.
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu wygenerowanej klasy DataContext . Prawidłowe wartości to publiczne, chronione, wewnętrzne i prywatne.
BaseType Ciąg "System.Data.Linq.DataContext" Podstawowy typ klasy DataContext .
Dostawca Ciąg "System.Data.Linq.SqlClient.Sql2005Provider" Dostawca elementu DataContext użyj dostawcy sql2005 jako domyślnego
Mapowanie zewnętrzne Wartość logiczna Fałsz Określ, czy kod DBML jest używany do generowania pliku mapowania zewnętrznego.
Serializacja SerializacjaMode SerializationMode.None Określ, czy wygenerowane klasy danych DataContext i jednostki można serializować.

Atrybuty Sub-Element bazy danych

Sub-Element Typ elementu Zakres wystąpień Opis
<Tabela> Tabela 0-unbounded Reprezentuje tabelę SQL Server lub widok, który zostanie zamapowany na pojedynczy typ lub do hierarchii dziedziczenia.
<Funkcja> Funkcja 0-unbounded Reprezentuje procedurę składowaną SQL Server lub funkcję db, która zostanie zamapowana na metodę w wygenerowanej klasie DataContext.
<Połączenie> Połączenie 0-1 Reprezentuje połączenie z bazą danych, które będzie używane przez ten element DataContext .

Tabela

Ten element reprezentuje tabelę bazy danych (lub widok), która zostanie zamapowana na pojedynczy typ lub do hierarchii dziedziczenia. Ten element jest luźno mapowany na atrybut Table w wygenerowanej klasie jednostki.

Atrybuty tabeli

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa tabeli w bazie danych. W razie potrzeby służy jako podstawa nazwy domyślnej karty tabeli.
Członek Ciąg Table.Name Nazwa pola składowego wygenerowanego dla tej tabeli w klasie DataContext .
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu odwołania do tabeli<T> w obiekcie DataContext. Prawidłowe wartości to publiczne, chronione, wewnętrzne i prywatne.

Atrybuty Sub-Element tabeli

Sub-Element Typ elementu Zakres wystąpień Opis
<Typ> Typ 1-1 Reprezentuje typ lub hierarchię dziedziczenia zamapowana na tę tabelę.
<InsertFunction> TableFunction 0-1 Metoda wstawiania. Gdy jest obecny, jest generowana metoda InsertT .
<UpdateFunction> TableFunction 0-1 Metoda aktualizowania. Gdy jest obecny, jest generowana metoda UpdateT .
<DeleteFunction> TableFunction 0-1 Metoda usuwania. Gdy jest obecny, jest generowana metoda DeleteT .

Typ

Ten element reprezentuje definicję typu dla kształtu wyniku tabeli lub procedury składowanej. Spowoduje to utworzenie kodu gen w nowym typie CLR z określonymi kolumnami i skojarzeniami.

Typ może również reprezentować składnik hierarchii dziedziczenia z wieloma typami mapowania na tę samą tabelę. W takim przypadku elementy typu są zagnieżdżone, aby reprezentować relacje dziedziczenia nadrzędnego-podrzędnego i są rozróżniane w bazie danych przez określony kod dziedziczenia .

Atrybuty typu

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa typu CLR do wygenerowania.
Kod dziedziczenia Ciąg Brak Jeśli ten typ uczestniczy w dziedziczeniu, może mieć skojarzony kod dziedziczenia, aby odróżnić typy CLR podczas ładowania wierszy z tabeli. Typ, którego kod dziedziczenia odpowiada wartości kolumny IsDiscriminator, jest używany do tworzenia wystąpienia załadowanego obiektu. Jeśli kod dziedziczenia nie jest obecny, wygenerowana klasa jednostki jest abstrakcyjna.
IsInheritanceDefault Wartość logiczna Fałsz Jeśli dotyczy to typu w hierarchii dziedziczenia, ten typ będzie używany podczas ładowania wierszy, które nie są zgodne z żadnymi zdefiniowanymi kodami dziedziczenia.
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu tworzonego typu CLR. Prawidłowe wartości to: publiczne, chronione, wewnętrzne i prywatne.
Id Ciąg Brak Typ może mieć unikatowy identyfikator. Identyfikator typu może być używany przez inne tabele lub funkcje. Identyfikator jest wyświetlany tylko w pliku DBML, a nie w modelu obiektów.
Idref Ciąg Brak Identyfikator idRef służy do odwoływania się do identyfikatora innego typu. Jeśli element IdRef jest obecny w elemecie type, element type musi zawierać tylko informacje IdRef . Element IdRef jest wyświetlany tylko w pliku DBML, a nie w modelu obiektów.

Typ atrybutów Sub-Element

Sub-Element Typ elementu Zakres wystąpień Opis
<Kolumna> Kolumna 0-unbounded Reprezentuje właściwość tego typu, która będzie powiązana z polem w tabeli tego typu.
<Stowarzyszenia> Stowarzyszenia 0-unbounded Reprezentuje właściwość tego typu, która będzie powiązana z jedną końcem relacji klucza obcego między tabelami.
<Typ> Podtyp 0-unbounded Reprezentuje podtypy tego typu w hierarchii dziedziczenia.

Podtyp

Ten element reprezentuje typ pochodny w hierarchii dziedziczenia. Zostanie to wygenerowane w nowym typie CLR z kolumnami i skojarzeniami określonymi w tym typie. Nie są generowane atrybuty dziedziczenia dla podtypów.

W porównaniu z typem elementy SubType nie mają modułu AccessModifier , ponieważ wszystkie typy pochodne muszą być publiczne. Podtypy nie mogą być ponownie używane przez inne tabele i funkcje, więc w nich nie ma identyfikatora ani identyfikatoraIdRef .

Atrybuty podtypu

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa typu CLR do wygenerowania.
Kod dziedziczenia Ciąg Brak Jeśli ten typ uczestniczy w dziedziczeniu, może mieć skojarzony kod dziedziczenia, aby odróżnić typy CLR podczas ładowania wierszy z tabeli. Typ, którego kod dziedziczenia odpowiada wartości kolumny IsDiscriminator , jest używany do tworzenia wystąpienia załadowanego obiektu. Jeśli kod dziedziczenia nie jest obecny, wygenerowana klasa jednostki jest abstrakcyjna.
IsInheritanceDefault Wartość logiczna Fałsz Jeśli dotyczy to typu w hierarchii dziedziczenia, ten typ będzie używany podczas ładowania wierszy, które nie są zgodne z żadnymi zdefiniowanymi kodami dziedziczenia.

Atrybuty Sub-Element podtypu

Sub-Element Typ elementu Zakres wystąpień Opis
<Kolumna> Kolumna 0-unbounded Reprezentuje właściwość tego typu, która będzie powiązana z polem w tabeli tego typu.
<Stowarzyszenia> Stowarzyszenia 0-unbounded Reprezentuje właściwość tego typu, która będzie powiązana z jedną końcem relacji klucza obcego między tabelami.
<Typ> Podtyp 0-unbounded Reprezentuje podtypy tego typu w hierarchii dziedziczenia.

Kolumna

Ten element reprezentuje kolumnę w tabeli mapowanej na właściwość (i pole kopii zapasowej) w klasie. Dla żadnego końca relacji klucza obcego nie będzie obecny żaden element Kolumna , ponieważ jest on całkowicie reprezentowany (na obu końcach) przez elementy skojarzenia.

Atrybuty kolumny

Atrybuty Typ Domyślny Opis
Nazwa Ciąg Brak Nazwa pola bazy danych, do których będzie mapować ta kolumna.
Członek Ciąg Nazwa Nazwa właściwości CLR, która ma zostać wygenerowana w typie zawierającym.
Storage Ciąg _Członkowskich Nazwa prywatnego pola tworzenia kopii zapasowej CLR, które będzie przechowywać wartość tej kolumny. Nie usuwaj magazynu podczas serializacji, nawet jeśli jest to ustawienie domyślne.
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu tworzonej właściwości CLR. Prawidłowe wartości to: publiczne, chronione, wewnętrzne i prywatne.
Typ Ciąg (wymagane) Nazwa typu zarówno właściwości CLR, jak i tworzonego pola tworzenia kopii zapasowej. Może to być dowolny element od w pełni kwalifikowanej nazwy tylko bezpośredniej nazwy klasy, o ile nazwa ostatecznie będzie w zakresie po skompilowaniu wygenerowanego kodu.
Dbtype Ciąg Brak Pełny SQL Server typ (w tym adnotacja, taka jak NOT NULL) dla tej kolumny. Używane przez LINQ to SQL, jeśli podasz je w celu zoptymalizowania wygenerowanych zapytań i bardziej szczegółowego podczas tworzenia bazy danych CreateDatabase(). Zawsze serializuj dbType.
IsReadOnly Wartość logiczna Fałsz Jeśli parametr IsReadOnly jest ustawiony, zestaw właściwości nie zostanie utworzony, co oznacza, że osoby nie mogą zmienić wartości tej kolumny przy użyciu tego obiektu.
Isprimarykey Wartość logiczna Fałsz Wskazuje, że ta kolumna uczestniczy w kluczu podstawowym tabeli. Te informacje są wymagane do prawidłowego działania LINQ to SQL.
Isdbgenerated Wartość logiczna Fałsz Wskazuje, że dane tego pola są generowane przez bazę danych. Jest to przypadek głównie w przypadku pól Autonumerowanie i dla pól obliczeniowych. Nie ma znaczenia, aby przypisywać wartości do tych pól, dlatego są automatycznie isReadOnly.
CanBeNull Wartość logiczna Brak Wskazuje, że wartość może zawierać wartość null. Jeśli chcesz faktycznie używać wartości null w clR, nadal musisz określić parametr ClrType jako nullable<T>.
Updatecheck Updatecheck Zawsze (chyba że co najmniej jeden inny element członkowski ma ustawioną wartość IsVersion , a następnie Nigdy) Wskazuje, czy LINQ to SQL używać tej kolumny podczas optymistycznego wykrywania konfliktów współbieżności. Zwykle wszystkie kolumny są domyślnie współuczesne, chyba że istnieje kolumna IsVersion , która następnie uczestniczy samodzielnie. Może to być: Zawsze, Nigdy lub WhenChanged (co oznacza, że kolumna uczestniczy, jeśli jego wartość uległa zmianie).
IsDiscriminator Wartość logiczna Fałsz Wskazuje, czy to pole zawiera kod dyskryminujący używany do wybierania typów w hierarchii dziedziczenia.
Wyrażenie Ciąg Brak Nie ma wpływu na operację LINQ to SQL, ale jest używana podczas .CreateDatabase() jako nieprzetworzone wyrażenie SQL reprezentujące wyrażenie kolumny obliczeniowej.
Isversion Wartość logiczna Fałsz Wskazuje, że to pole reprezentuje pole TIMESTAMP w SQL Server, które jest automatycznie aktualizowane za każdym razem, gdy wiersz zostanie zmieniony. To pole może następnie służyć do umożliwienia bardziej wydajnego optymistycznego wykrywania konfliktów współbieżności.
IsDelayLoaded Wartość logiczna Fałsz Wskazuje, że ta kolumna nie powinna być ładowana natychmiast po materializacji obiektu, ale tylko wtedy, gdy dostępna jest odpowiednia właściwość. Jest to przydatne w przypadku dużych pól noty lub danych binarnych w wierszu, które nie zawsze są potrzebne.
Autosync Autosync If (IsDbGenerated && IsPrimaryKey) OnInsert;

Inaczej, jeśli (IsDbGenerated) zawsze

Else Never

Określa, czy kolumna jest automatycznie synchronizowana z wartości wygenerowanej przez bazę danych. Prawidłowe wartości tego tagu to : OnInsert, Always i Never.

Stowarzyszenia

Ten element reprezentuje dowolny koniec relacji klucza obcego. W przypadku relacji jeden-do-wielu będzie to element EntitySet<T> po jednej stronie, a obiekt EntityRef<T> po stronie wielu. W przypadku relacji jeden-do-jednego będzie to jednostka EntityRef<T> po obu stronach.

Należy pamiętać, że nie jest wymagane wpis skojarzenia po obu stronach skojarzenia. W takim przypadku właściwość zostanie wygenerowana tylko po stronie, która zawiera wpis (tworząc relację jednokierunkową).

Atrybuty skojarzenia

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa relacji (zwykle nazwa ograniczenia klucza obcego). Może to być technicznie opcjonalne, ale zawsze powinno być generowane przez kod, aby uniknąć niejednoznaczności, gdy istnieje wiele relacji między tymi samymi dwiema tabelami.
Członek Ciąg Nazwa Nazwa właściwości CLR, która ma być generowana po tej stronie skojarzenia.
Storage Ciąg Jeśli OneToMany i Not IsForeignKey:

_OtherTable

Innego:

_TypeName(OtherTable)

Nazwa prywatnego pola tworzenia kopii zapasowej CLR, które będzie przechowywać wartość tej kolumny.
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu tworzonej właściwości CLR. Prawidłowe wartości to Publiczne, Chronione, Wewnętrzne i Prywatne.
ThisKey Ciąg Właściwość IsIdentity w obrębie klasy zawierającej Rozdzielona przecinkami lista kluczy po tej stronie skojarzenia.
OtherTable Ciąg Patrz opis. Tabela na drugim końcu relacji. Zwykle może to być określane przez środowisko uruchomieniowe LINQ to SQL przez dopasowanie nazw relacji, ale nie jest to możliwe w przypadku skojarzeń jednokierunkowych ani skojarzeń anonimowych.
OtherKey Ciąg Klucze podstawowe w klasie obcej Rozdzielona przecinkami lista kluczy po drugiej stronie skojarzenia.
IsForeignKey Wartość logiczna Fałsz Wskazuje, czy jest to strona "podrzędna" relacji, wiele stron jednego do wielu.
Relationshiptype Relationshiptype OneToMany Wskazuje, czy użytkownik potwierdza, że dane związane z tym skojarzeniem spełniają kryteria danych jeden do jednego, czy spełniają bardziej ogólny przypadek jeden do wielu. W przypadku jednego do jednego użytkownik potwierdza, że dla każdego wiersza po stronie klucza podstawowego ("jeden") znajduje się tylko jeden wiersz po stronie klucza obcego ("wiele"). Spowoduje to wygenerowanie elementu EntityRef<T> po stronie "jeden" zamiast elementu EntitySet<T>. Prawidłowe wartości to OneToOne i OneToMany.
Deleterule Ciąg Brak Służy do dodawania zachowania usuwania do tego skojarzenia. Na przykład wyrażenie "CASCADE" spowoduje dodanie ciągu "ONDELETECASCADE" do relacji FK. W przypadku ustawienia wartości null żadne zachowanie usuwania nie zostanie dodane.

Funkcja

Ten element reprezentuje procedurę składowaną lub funkcję bazy danych. Dla każdego węzła funkcji metoda jest generowana w klasie DataContext .

Atrybuty funkcji

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa procedury składowanej w bazie danych.
Metoda Ciąg Metoda Nazwa metody CLR do wygenerowania, która umożliwia wywołanie procedury składowanej. Domyślna nazwa metody zawiera takie elementy jak [dbo]. Usunięte z pola Nazwa.
AccessModifier AccessModifier Publiczne Poziom ułatwień dostępu metody procedury składowanej. Prawidłowe wartości to Publiczne, Chronione, Wewnętrzne i Prywatne.
HasMultipleResults Wartość logiczna Liczba typów > 1 Określa, czy procedura składowana reprezentowana przez ten węzeł funkcji zwraca wiele zestawów wyników. Każdy zestaw wyników jest kształtem tabelarycznym, może być istniejącym typem lub zestawem kolumn. W tym drugim przypadku zostanie utworzony węzeł Typ dla zestawu kolumn.
IsComposable Wartość logiczna Fałsz Określa, czy można skomponować funkcję/procedurę składowaną w LINQ to SQL zapytaniach. Można tworzyć tylko funkcje bazy danych, które nie zwracają wartości void.

Atrybuty Sub-Element funkcji

Sub-Element Typy elementów Zakres wystąpień Opis
<Parametr> Parametr 0-unbounded Reprezentuje parametry in i out tej procedury składowanej.
<Elementtype> Typ 0-unbounded Reprezentuje kształty tabelaryczne, które może zwrócić odpowiednia procedura składowana.
< Zwrot > Zwrot 0-1 Zwrócony typ skalarny tej funkcji bazy danych lub procedury składowanej. Jeśli wartość Return ma wartość null, funkcja zwraca wartość void. Funkcja nie może mieć wartości Return i ElementType.

TableFunction

Ten element reprezentuje funkcje zastępowania CUD dla tabel. Projektant LINQ to SQL umożliwia tworzenie metod wstawiania, aktualizowania i usuwania zastąpień dla języka LINQ TO SQL oraz umożliwia mapowanie nazw właściwości jednostki na nazwy parametrów procedury składowanej.

Nazwa metody dla funkcji CUD jest stała, więc nie ma atrybutu Method w dbML dla elementów TableFunction . Na przykład w przypadku tabeli Customer metody CUD mają nazwę InsertCustomer, UpdateCustomer i DeleteCustomer.

Funkcja tabeli nie może zwrócić kształtu tabelarycznego, więc w elemecie TableFunction nie ma atrybutu ElementType.

Atrybuty TableFunction

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa procedury składowanej w bazie danych.
AccessModifier AccessModifier Prywatne Poziom ułatwień dostępu metody procedury składowanej. Prawidłowe wartości to Publiczne, Chronione, Wewnętrzne i Prywatne.
HasMultipleResults Wartość logiczna Liczba typów > 1 Określa, czy procedura składowana reprezentowana przez ten węzeł funkcji zwraca wiele zestawów wyników. Każdy zestaw wyników jest kształtem tabelarycznym, może być istniejącym typem lub zestawem kolumn. W tym drugim przypadku zostanie utworzony węzeł Typ dla zestawu kolumn.
IsComposable Wartość logiczna Fałsz Określa, czy można skomponować funkcję/procedurę składowaną w LINQ to SQL zapytaniach. Można tworzyć tylko funkcje bazy danych, które nie zwracają wartości void.

Atrybuty funkcji tableFunction Sub-Element

Sub-Elements Typ elementu Zakres wystąpień Opis
<Parametr> TableFunctionParameter 0-unbounded Reprezentuje parametry in i out tej funkcji tabeli.
< Zwrot > TableFunctionReturn 0-1 Zwrócony typ skalarny tej funkcji tabeli. Jeśli wartość Return ma wartość null, funkcja zwraca wartość void.

Parametr

Ten element reprezentuje parametr procedury składowanej/funkcji. Parametry mogą przekazywać i wyprowadzać dane.

Atrybuty parametrów

Atrybut Typ Domyślny Opisy
Nazwa Ciąg (wymagane) Nazwa bazy danych przechowywanego parametru proc/function.
Parametr Ciąg Nazwa Nazwa CLR parametru metody.
  Ciąg (wymagane) Nazwa CLR parametru metody.
Dbtype Ciąg Brak Typ bazy danych przechowywanego parametru proc/function.
Kierunek ParametrDirection W Kierunek przepływu parametru. Może być jednym z elementów In, Out i InOut.

Zwrot

Ten element reprezentuje zwracany typ procedury składowanej/funkcji.

Atrybuty zwracane

Atrybut Typ Domyślny Opis
Typ Ciąg (wymagane) Typ CLR wyniku przechowywanej funkcji proc/function.
Dbtype Ciąg Brak Typ bazy danych wyniku przechowywanej funkcji proc/function.

TableFunctionParameter

Ten element reprezentuje parametr funkcji CUD. Parametry mogą przekazywać i wychodzące dane. Każdy parametr jest mapowany na kolumnę Tabela , do którego należy ta funkcja CUD. W tym elemecie nie ma atrybutów Typu ani DbType , ponieważ informacje o typie można uzyskać z kolumny, do której mapuje parametr.

Atrybuty TableFunctionParameter

Atrybut Typ Domyślny Opis
Nazwa Ciąg (wymagane) Nazwa bazy danych parametru funkcji CUD.
Parametr Ciąg Nazwa Nazwa CLR parametru metody.
Kolumna Ciąg Nazwa Nazwa kolumny, do których jest mapowanie tego parametru.
Kierunek ParametrDirection W Kierunek przepływu parametru. Może być jednym z elementów In, Out lub InOut.
Wersja Wersja Current Niezależnie od tego, czy właściwość PropertyName odnosi się do bieżącej, czy oryginalnej wersji danej kolumny. Dotyczy tylko przesłonięcia aktualizacji . Może być bieżący lub oryginalny.

TableFunctionReturn

Ten element reprezentuje zwracany typ funkcji CUD. W rzeczywistości zawiera tylko nazwę kolumny mapowanej na wynik funkcji CUD. Informacje o typie zwrotu można uzyskać z kolumny.

TableFunctionReturn, atrybut

Attrobite Typ Domyślny Opis
Kolumna Ciąg Brak Nazwa kolumny, na którą zwracany jest mapowanie.

Połączenie

Ten element reprezentuje domyślne parametry połączenia z bazą danych. Umożliwia to utworzenie domyślnego konstruktora dla typu DataContext , który już wie, jak nawiązać połączenie z bazą danych.

Istnieją dwa możliwe typy połączeń domyślnych, jeden z bezpośrednim ciągiem ConnectionString i jeden, który odczytuje z obszaru App.Settings.

Atrybuty połączenia

Atrybut Typ Domyślny Opis
UseApplicationSettings Wartość logiczna Fałsz Określa, czy należy użyć pliku App.Settings, czy pobrać ustawieniaaplikacji z bezpośredniego połączeniaString.
Connectionstring Ciąg Brak Parametry połączenia wysyłane do dostawcy danych SQL.
SettingsObjectName Ciąg Ustawienia Obiekt App.Settings do pobrania właściwości.
SettingsPropertyName Ciąg Connectionstring Właściwość App.Settings zawierająca właściwość ConnectionString.

Jednostki wielowarstwowe

W aplikacjach dwuwarstwowych pojedynczy element DataContext obsługuje zapytania i aktualizacje. Jednak w przypadku aplikacji z dodatkowymi warstwami często konieczne jest użycie oddzielnych wystąpień danychContext na potrzeby zapytań i aktualizacji. Na przykład w przypadku aplikacji ASP.NET zapytania i aktualizacji są wykonywane dla oddzielnych żądań na serwerze sieci Web. W związku z tym niepraktyczne jest użycie tego samego wystąpienia elementu DataContext w wielu żądaniach. W takich przypadkach wystąpienie obiektu DataContext musi być w stanie zaktualizować obiekty, które nie zostały pobrane. Obsługa jednostek wielowarstwowych w LINQ to SQL zapewnia taką możliwość za pośrednictwem metody Attach().

Oto przykład sposobu zmiany obiektu Klienta przy użyciu innego wystąpienia obiektu 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()

W aplikacjach wielowarstwowych cała jednostka nie jest często wysyłana między warstwami, aby zapewnić prostotę, współdziałanie lub prywatność. Na przykład dostawca może zdefiniować kontrakt danych dla usługi sieci Web, która różni się od jednostki Order używanej w warstwie środkowej. Podobnie strona sieci Web może wyświetlać tylko podzestaw członków jednostki Employee. W związku z tym obsługa wielowarstwowa jest przeznaczona do obsługi takich przypadków. Przed wywołaniem funkcji Attach()należy przetransportować tylko członków należących do co najmniej jednej z następujących kategorii.

  1. Członkowie będący częścią tożsamości jednostki.
  2. Członkowie, którzy zostali zmienieni.
  3. Członkowie, którzy uczestniczą w optymistycznym sprawdzaniu współbieżności.

Jeśli sygnatura czasowa lub kolumna numeru wersji jest używana do sprawdzania optymistycznej współbieżności, należy ustawić odpowiedni element członkowski przed wywołaniem funkcji Attach(). Wartości innych elementów członkowskich nie muszą być ustawiane przed wywołaniem metody Attach(). LINQ to SQL używa minimalnych aktualizacji z optymistycznymi kontrolami współbieżności. Oznacza to, że element członkowski, który nie jest ustawiony lub sprawdzany pod kątem optymistycznej współbieżności, jest ignorowany.

Oryginalne wartości wymagane do optymistycznych testów współbieżności mogą być zachowywane przy użyciu różnych mechanizmów spoza zakresu interfejsów API LINQ to SQL. Aplikacja ASP.NET może używać stanu widoku (lub kontrolki używającej stanu widoku). Usługa sieci Web może używać elementu DataContract dla metody aktualizacji, aby upewnić się, że oryginalne wartości są dostępne do przetwarzania aktualizacji. W interesie współdziałania i uogólnienia LINQ to SQL nie dyktuje kształtu danych wymienianych między warstwami lub mechanizmami używanymi do zaokrąglania oryginalnych wartości.

Jednostki do wstawiania i usuwania nie wymagają metody Attach(). Metody używane dla aplikacji dwuwarstwowych — Table.Add() i Table.Remove() mogą służyć do wstawiania i usuwania. Podobnie jak w przypadku aktualizacji dwuwarstwowych, użytkownik jest odpowiedzialny za obsługę ograniczeń klucza obcego. Klient z zamówieniami nie może zostać usunięty po prostu bez obsługi zamówień, jeśli istnieje ograniczenie klucza obcego w bazie danych uniemożliwiające usunięcie klienta z zamówieniami.

LINQ to SQL obsługuje również dołączanie jednostek do aktualizacji przechodnio. Użytkownik zasadniczo tworzy graf obiektu przed aktualizacją zgodnie z potrzebami i wywołuje funkcję Attach(). Wszystkie zmiany można następnie "odtworzyć" na dołączonym grafie w celu wykonania niezbędnych aktualizacji, jak pokazano poniżej:

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

Mapowanie zewnętrzne

Oprócz mapowania opartego na atrybutach LINQ to SQL obsługuje również mapowanie zewnętrzne. Najczęstszą formą mapowania zewnętrznego jest plik XML. Pliki mapowania umożliwiają korzystanie z dodatkowych scenariuszy, w których pożądane jest oddzielenie mapowania z kodu.

Element DataContext udostępnia dodatkowy konstruktor do dostarczania elementu MappingSource. Jedną z form mapowaniaSource jest element XmlMappingSource , który można skonstruować z pliku mapowania XML.

Oto przykład użycia pliku mapowania:

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   )

Oto odpowiedni fragment kodu z pliku mapowania przedstawiający mapowanie dla klasy Product . Przedstawia klasę Product w mapowaniu przestrzeni nazw mapowanej na tabelę Products w bazie danych Northwind . Elementy i atrybuty są spójne z nazwami atrybutów i parametrami.

<?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>

Obsługa i uwagi dotyczące funkcji programu NET Framework

Poniższe akapity zawierają podstawowe informacje dotyczące obsługi LINQ to SQL typów i różnic w .NET Framework.

Typy pierwotne

Zaimplementowana

  • Operatory arytmetyczne i porównawcze
  • Operatory shift: << i >>
  • Konwersja między znakiem a cyfrą odbywa się za pomocą formatu UNICODE/NCHAR. W przeciwnym razie jest używana funkcja KONWERTUJ SQL.

Nie zaimplementowano

  • <Wpisz>. Przeanalizować
  • Wyliczenia mogą być używane i mapowane na liczby całkowite i ciągi w tabeli. W przypadku tych ostatnich używane są metody Parse i ToString( ).

Różnica między platformą .NET

  • Dane wyjściowe funkcji ToString do podwójnego użycia funkcji CONVERT(NVARCHAR(30), @x, 2) w języku SQL, które zawsze używają 16 cyfr i "Notacja naukowa". Na przykład: "0.000000000000000e+000" dla 0, więc nie daje tego samego ciągu co . Funkcja Convert.ToString()platformy NET.

System.string

Zaimplementowana

  • Metody niestatyczne:

    • Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. Wszystkie podpisy są obsługiwane, z wyjątkiem przypadków, gdy przyjmują parametr StringComparison itd., jak opisano poniżej.
  • Metody statyczne:

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

        String(Char, Int32)
    
  • Operatorów:

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

Nie zaimplementowano

  • Metody, które przyjmują lub tworzą tablicę znaków.

  • Metody, które przyjmują parametr CultureInfo/StringComparison/IFormatProvider.

  • Statyczny (udostępniony w 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
    
  • Wystąpienie:

       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)
    

Ograniczenia/różnica z platformy .NET

Język SQL używa sortowania do określania równości i kolejności ciągów. Można je określić w wystąpieniu SQL Server, bazie danych, kolumnie tabeli lub wyrażeniu.

Tłumaczenia funkcji zaimplementowanych do tej pory nie zmieniają sortowania ani nie określają innego sortowania w przetłumaczonych wyrażeniach. Dlatego jeśli sortowanie domyślne jest bez uwzględniania wielkości liter, funkcje takie jak CompareTo lub IndexOf mogą dać wyniki, które różnią się od tego, jakie funkcje platformy .NET (uwzględnia wielkość liter).

Metody StartsWith(str)EndsWith(str)/ zakładają, że str argumentu jest stałą lub wyrażeniem obliczanym na kliencie. Oznacza to, że obecnie nie można użyć kolumny dla parametru str.

System.math

Zaimplementowano metody statyczne

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

Nie zaimplementowano

  • IEEERemainder.
  • Element DivRem ma parametr out, więc nie można go użyć w wyrażeniu. Stałe Math.PI i Math.E są oceniane na kliencie, więc nie potrzebują tłumaczenia.

Różnica między platformą .NET

Tłumaczenie funkcji Math.Round dla platformy .NET jest funkcją SQL ROUND. Tłumaczenie jest obsługiwane tylko wtedy, gdy określono przeciążenie wskazujące wartość wyliczeniową MidpointRounding . MidpointRounding.AwayFromZero jest zachowaniem SQL i MidpointRounding.ToEven wskazuje zachowanie CLR.

System.Convert

Zaimplementowana

  • Metody formularza Do<Type1(<Type2> x), gdzie Type1>, Type2 jest jednym z:
    • bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 lub string.
  • Zachowanie jest takie samo jak rzutowanie:
    • W przypadku funkcji ToString(Double) istnieje specjalny kod umożliwiający uzyskanie pełnej precyzji.
    • W przypadku konwersji Int32/Char LINQ to SQL używa funkcji UNICODE/NCHAR języka SQL.
    • W przeciwnym razie tłumaczenie jest konwersją.

Nie zaimplementowano

  • ToSByte, UInt16, 32, 64: Te typy nie istnieją w języku SQL.

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

  • Metody obejmujące tablicę (Do/zBase64CharArray, do/zBase64String).

System.timespan

Zaimplementowana

  • Konstruktorów:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • Operatorów:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Metody statyczne (udostępnione w Visual Basic):

       Compare(t1,t2)
    
  • Metody niestatyczne (wystąpienie) / właściwości:

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

Nie zaimplementowano

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

System.datetime

Zaimplementowana

  • Konstruktorów:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • Operatorów:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • Metody statyczne (udostępnione):

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • Metody niestatyczne (wystąpienie) / właściwości:

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

Różnica w porównaniu z platformą .NET

Wartości daty/godziny bazy danych SQL są zaokrąglane do .000, .003 lub .007 sekund, więc jest mniej precyzyjne niż wartości platformy .NET.

Zakres daty/godziny sql rozpoczyna się od 1 stycznia 1753 roku.

Język SQL nie ma wbudowanego typu TimeSpan. Używa ona różnych metod DATEDIFF , które zwracają 32-bitowe liczby całkowite. Jeden to DATEDIFF(DAY,...), który daje liczbę dni; inny to DATEDIFF(MILISEKUND,...), który daje liczbę milisekund. Wynik błędu, jeśli data/godzina jest większa niż 24 dni od siebie. Natomiast platforma .NET używa 64-bitowych liczb całkowitych i mierzy przedziały czasu w znacznikach.

Aby uzyskać jak najbliżej semantyki platformy .NET w języku SQL, LINQ to SQL tłumaczy przedziały czasu na 64-bitowe liczby całkowite i używają dwóch metod DATEDIFF wymienionych powyżej w celu obliczenia liczby kleszczy między dwiema datami.

Datetime Funkcja UtcNow jest obliczana na kliencie, gdy zapytanie jest tłumaczone (na przykład każde wyrażenie, które nie obejmuje danych bazy danych).

Nie zaimplementowano

   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

Obsługa debugowania

Obiekt DataContext udostępnia metody i właściwości umożliwiające uzyskanie kodu SQL wygenerowanego na potrzeby zapytań i przetwarzania zmian. Te metody mogą być przydatne do zrozumienia funkcji LINQ to SQL i debugowania określonych problemów.

Metody DataContext do pobierania wygenerowanego kodu SQL

Członek Przeznaczenie
Dziennik Drukuje kod SQL przed jego wykonaniem. Obejmuje polecenia wykonywania zapytań, wstawiania, aktualizowania i usuwania. Użycie:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(query) Zwraca tekst zapytania zapytania bez jego wykonywania. Użycie:

C#

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

Visual Basic

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

GetChangeText() Zwraca tekst poleceń SQL do wstawiania/aktualizowania/usuwania bez ich wykonywania. Użycie:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())