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 :
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.
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.
|
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.
- Członkowie będący częścią tożsamości jednostki.
- Członkowie, którzy zostali zmienieni.
- 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#
Visual Basic
|
GetQueryText(query) | Zwraca tekst zapytania zapytania bez jego wykonywania. Użycie: C# Console.WriteLine(db.GetQueryText(db.Customers));
Visual Basic
|
GetChangeText() | Zwraca tekst poleceń SQL do wstawiania/aktualizowania/usuwania bez ich wykonywania. Użycie: C# Console.WriteLine(db.GetChangeText());
Visual Basic
|