LINQ to SQL: .NET Language-Integrated Query für relationale Daten

 

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

März 2007

Gilt für:
   Visual Studio Code-Name "Orcas"
   .NET Framework 3.5

Zusammenfassung: LINQ to SQL stellt eine Laufzeitinfrastruktur zum Verwalten relationaler Daten als Objekte bereit, ohne die Abfragefähigkeit zu verlieren. Ihre Anwendung kann die Objekte bearbeiten, während LINQ to SQL ihre Änderungen automatisch im Hintergrund nachverfolgt. (119 gedruckte Seiten)

Inhalte

Einführung
Eine kurze Tour
   Erstellen von Entitätsklassen
   The DataContext
   Definieren von Beziehungen
   Beziehungsübergreifendes Abfragen
   Ändern und Speichern von Entitäten
Abfragen In-Depth
   Abfrageausführung
   Objektidentität
   Beziehungen
   Joins
   Projektionen
   Kompilierte Abfragen
   SQL-Übersetzung
Entitätslebenszyklus
   Nachverfolgen von Änderungen
   Übermitteln von Änderungen
   Gleichzeitige Änderungen
   Transaktionen
   Gespeicherte Prozeduren
Entitätsklassen In-Depth
   Verwenden von Attributen
   Graphkonsistenz
   Benachrichtigungen ändern
   Vererbung
Weiterführende Themen
   Erstellen von Datenbanken
   Interoperieren mit ADO.NET
   Konfliktlösung ändern
   Aufruf gespeicherter Prozeduren
   Das Tool zum Generator für Entitätsklassen
   DBML-Referenz zum Generatortool
   Entitäten mit mehreren Ebenen
   Externe Zuordnung
   Unterstützung von NET Framework-Funktionen und Hinweise
   Debugunterstützung

Einführung

Die meisten heute geschriebenen Programme bearbeiten Daten auf die eine oder andere Weise, und häufig werden diese Daten in einer relationalen Datenbank gespeichert. Dennoch gibt es eine große Kluft zwischen modernen Programmiersprachen und Datenbanken in der Darstellung und Bearbeitung von Informationen. Diese Impedanzabweichung ist auf mehrere Arten sichtbar. Am bemerkenswertesten ist, dass Programmiersprachen über APIs auf Informationen in Datenbanken zugreifen, für die Abfragen als Textzeichenfolgen angegeben werden müssen. Diese Abfragen sind wichtige Teile der Programmlogik. Dennoch sind sie für die Sprache undurchsichtig und können nicht von der Kompilierzeitüberprüfung und Entwurfszeitfeatures wie IntelliSense profitieren.

Natürlich gehen die Unterschiede viel tiefer. Die Darstellung von Informationen – das Datenmodell – unterscheidet sich zwischen den beiden sehr. Moderne Programmiersprachen definieren Informationen in Form von Objekten. Relationale Datenbanken verwenden Zeilen. Objekte haben eine eindeutige Identität, da sich jedes instance physisch von einem anderen unterscheidet. Zeilen werden durch Primärschlüsselwerte identifiziert. Objekte verfügen über Verweise, die Instanzen identifizieren und miteinander verknüpfen. Zeilen werden absichtlich getrennt, sodass verwandte Zeilen mithilfe von Fremdschlüsseln lose miteinander verknüpft werden müssen. Objekte sind eigenständig vorhanden, solange noch von einem anderen Objekt auf sie verwiesen wird. Zeilen sind als Elemente von Tabellen vorhanden und verschwinden, sobald sie entfernt werden.

Es ist kein Wunder, dass Anwendungen, von denen erwartet wird, dass sie diese Lücke schließen, schwierig zu erstellen und zu warten sind. Es würde sicherlich die Gleichung vereinfachen, die eine oder andere Seite loszuwerden. Relationale Datenbanken bieten jedoch eine kritische Infrastruktur für langfristige Speicher- und Abfrageverarbeitung, und moderne Programmiersprachen sind für agile Entwicklung und umfangreiche Berechnungen unerlässlich.

Bisher war es die Aufgabe des Anwendungsentwicklers, diesen Konflikt in jeder Anwendung separat zu beheben. Die besten Lösungen waren bisher aufwendige Abstraktionsebenen für Datenbanken, die die Informationen zwischen den domänenspezifischen Objektmodellen und der tabellarischen Darstellung der Datenbank zwischen den Anwendungen überlagern und die Daten jeweils neu formatieren und neu formatierten. Durch die Verschleierung der wahren Datenquelle verwerfen diese Lösungen jedoch am Ende das überzeugendste Feature relationaler Datenbanken. die Möglichkeit, die Daten abzufragen.

LINQ to SQL, eine Komponente des Visual Studio Code-Namens "Orcas", stellt eine Laufzeitinfrastruktur zum Verwalten relationaler Daten als Objekte bereit, ohne dass die Abfragefähigkeit verloren geht. Dazu werden sprachintegrierte Abfragen für die Ausführung durch die Datenbank in SQL übersetzt und dann die tabellarischen Ergebnisse wieder in von Ihnen definierte Objekte übersetzt. Ihre Anwendung kann dann die Objekte bearbeiten, während LINQ to SQL ihre Änderungen automatisch im Hintergrund nachverfolgt.

  • LINQ to SQL ist so konzipiert, dass ihre Anwendung nicht aufdringlich ist.
    • Es ist möglich, aktuelle ADO.NET Lösungen stückweise zu LINQ to SQL zu migrieren (gemeinsam mit denselben Verbindungen und Transaktionen), da LINQ to SQL einfach eine weitere Komponente in der ADO.NET-Familie ist. LINQ to SQL bietet auch umfassende Unterstützung für gespeicherte Prozeduren, die die Wiederverwendung der vorhandenen Unternehmensressourcen ermöglichen.
  • LINQ to SQL Anwendungen sind einfach zu beginnen.
    • Objekte, die mit relationalen Daten verknüpft sind, können genau wie normale Objekte definiert werden und nur mit Attributen versehen werden, um zu bestimmen, wie Eigenschaften Spalten entsprechen. Natürlich ist es nicht einmal notwendig, dies von Hand zu tun. Ein Entwurfszeittool wird bereitgestellt, um die Übersetzung bereits vorhandener relationaler Datenbankschemas in Objektdefinitionen für Sie zu automatisieren.

Zusammen reduzieren die LINQ to SQL Laufzeitinfrastruktur und Entwurfszeittools die Workload für den Datenbankanwendungsentwickler erheblich. Die folgenden Kapitel bieten eine Übersicht darüber, wie LINQ to SQL zum Ausführen allgemeiner datenbankbezogener Aufgaben verwendet werden können. Es wird davon ausgegangen, dass der Leser mit Language-Integrated Query und den Standardabfrageoperatoren vertraut ist.

LINQ to SQL ist sprachunabhängig. Jede Sprache, die für die Bereitstellung Language-Integrated Abfrage entwickelt wurde, kann sie verwenden, um den Zugriff auf informationen zu ermöglichen, die in relationalen Datenbanken gespeichert sind. Die Beispiele in diesem Dokument werden sowohl in C# als auch in Visual Basic gezeigt. LINQ to SQL können auch mit der LINQ-fähigen Version des Visual Basic-Compilers verwendet werden.

Eine kurze Tour

Der erste Schritt beim Erstellen einer LINQ to SQL-Anwendung besteht darin, die Objektklassen zu deklarieren, die Sie zur Darstellung Ihrer Anwendungsdaten verwenden. Sieh dir folgendes Beispiel an.

Erstellen von Entitätsklassen

Wir beginnen mit einer einfachen Klasse Customer und ordnen sie der Tabelle kunden in der Beispieldatenbank Northwind zu. Dazu müssen wir nur ein benutzerdefiniertes Attribut auf den Anfang der Klassendeklaration anwenden. LINQ to SQL definiert zu diesem Zweck das Table-Attribut.

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

Das Table-Attribut verfügt über eine Name-Eigenschaft , die Sie verwenden können, um den genauen Namen der Datenbanktabelle anzugeben. Wenn keine Name-Eigenschaft angegeben wird, wird LINQ to SQL davon ausgegangen, dass die Datenbanktabelle denselben Namen wie die -Klasse hat. Nur Instanzen von Klassen, die als Tabellen deklariert sind, werden in der Datenbank gespeichert. Instanzen dieser Klassentypen werden als Entitäten bezeichnet. Die Klassen selbst werden als Entitätsklassen bezeichnet.

Zusätzlich zum Zuordnen von Klassen zu Tabellen müssen Sie jedes Feld oder jede Eigenschaft bezeichnen, die Sie einer Datenbankspalte zuordnen möchten. Dazu definiert LINQ to SQL das Column-Attribut.

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

Das Column-Attribut verfügt über eine Vielzahl von Eigenschaften, mit denen Sie die genaue Zuordnung zwischen Ihren Feldern und den Datenbankspalten anpassen können. Eine wichtige Eigenschaft ist die Id-Eigenschaft . Sie teilt LINQ to SQL mit, dass die Datenbankspalte Teil des Primärschlüssels in der Tabelle ist.

Wie beim Table-Attribut müssen Sie nur Informationen im Column-Attribut angeben, wenn sie sich von dem unterscheiden, was aus Ihrer Feld- oder Eigenschaftsdeklaration abgeleitet werden kann. In diesem Beispiel müssen Sie LINQ to SQL mitteilen, dass das Feld CustomerID Teil des Primärschlüssels in der Tabelle ist. Sie müssen jedoch nicht den genauen Namen oder Typ angeben.

Nur Felder und Eigenschaften, die als Spalten deklariert wurden, werden in der Datenbank beibehalten oder aus der Datenbank abgerufen. Andere werden als vorübergehende Teile Ihrer Anwendungslogik betrachtet.

The DataContext

DataContext ist der Standard Conduit, mit dem Sie Objekte aus der Datenbank abrufen und Änderungen erneut übermitteln. Sie verwenden es auf die gleiche Weise wie eine ADO.NET-Verbindung. Tatsächlich wird der DataContext mit einer von Ihnen bereitgestellten Verbindungs- oder Verbindungszeichenfolge initialisiert. Der Zweck von DataContext besteht darin, Ihre Anforderungen für Objekte in SQL-Abfragen für die Datenbank zu übersetzen und dann Objekte aus den Ergebnissen zusammenzustellen. DataContext ermöglicht sprachintegriert abfragen, indem dasselbe Operatormuster wie die Standardabfrageoperatoren wie Where und Select implementiert wird.

Sie können beispielsweise den DataContext verwenden, um Kundenobjekte abzurufen, deren Stadt London ist:

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

Jede Datenbanktabelle wird als Table-Auflistung dargestellt, auf die über die GetTable() -Methode mithilfe ihrer Entitätsklasse zugegriffen werden kann, um sie zu identifizieren. Es wird empfohlen, einen stark typisierten DataContext zu deklarieren, anstatt sich auf die grundlegende DataContext-Klasse und die GetTable()- Methode zu verlassen. Ein stark typisierter DataContext deklariert alle Table-Auflistungen als Member des Kontexts.

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

Die Abfrage für Kunden aus London kann dann einfacher ausgedrückt werden wie folgt:

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

Wir verwenden weiterhin die stark typisierte Northwind-Klasse für den Rest des Übersichtsdokuments.

Definieren von Beziehungen

Beziehungen in relationalen Datenbanken werden in der Regel als Fremdschlüsselwerte modelliert, die sich auf Primärschlüssel in anderen Tabellen beziehen. Um zwischen ihnen zu navigieren, müssen Sie die beiden Tabellen mithilfe eines relationalen Verknüpfungsvorgangs explizit zusammenführen. Objekte hingegen verweisen auf einander mithilfe von Eigenschaftsverweisen oder Auflistungen von Verweisen, die mithilfe der "Punkt"-Notation navigiert werden. Natürlich ist das Punktieren einfacher als das Verknüpfen, da Sie sich nicht jedes Mal an die explizite Joinbedingung erinnern müssen, wenn Sie navigieren.

Für solche Datenbeziehungen, die immer gleich sind, ist es sehr praktisch, sie als Eigenschaftenverweise in Ihrer Entitätsklasse zu codieren. LINQ to SQL definiert ein Zuordnungsattribut, das Sie auf einen Member anwenden können, der zum Darstellen einer Beziehung verwendet wird. Eine Zuordnungsbeziehung ist wie eine Fremdschlüssel-Zu-Primärschlüssel-Beziehung, die durch den Abgleich von Spaltenwerten zwischen Tabellen hergestellt wird.

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

Die Customer-Klasse verfügt nun über eine Eigenschaft, die die Beziehung zwischen Kunden und ihren Bestellungen deklariert. Die Orders-Eigenschaft ist vom Typ EntitySet , da die Beziehung 1:n ist. Wir verwenden die OtherKey-Eigenschaft im Association-Attribut , um zu beschreiben, wie diese Zuordnung erfolgt. Es gibt die Namen der Eigenschaften in der zugehörigen Klasse an, die mit dieser verglichen werden sollen. Es gab auch eine ThisKey-Eigenschaft , die wir nicht angegeben haben. Normalerweise würden wir es verwenden, um die Mitglieder auf dieser Seite der Beziehung aufzulisten. Durch Weglassen erlauben wir LINQ to SQL jedoch, sie von den Elementen abzuleiten, aus denen der Primärschlüssel besteht.

Beachten Sie, wie dies in der Definition für die Order-Klasse umgekehrt wird.

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

Die Order-Klasse verwendet den EntityRef-Typ , um die Beziehung zurück zum Kunden zu beschreiben. Die Verwendung der EntityRef-Klasse ist erforderlich, um verzögertes Laden zu unterstützen (weiter unten erläutert). Das Association-Attribut für die Customer-Eigenschaft gibt die ThisKey-Eigenschaft an, da sich die nicht rückschlussbaren Member jetzt auf dieser Seite der Beziehung befinden.

Sehen Sie sich auch die Storage-Eigenschaft an. Sie teilt LINQ to SQL mit, welches private Element verwendet wird, um den Wert der Eigenschaft zu halten. Dadurch können LINQ to SQL Ihre öffentlichen Eigenschaftszugriffsoren umgehen, wenn sie ihren Wert speichern und abrufen. Dies ist wichtig, wenn Sie LINQ to SQL vermeiden möchten, dass benutzerdefinierte Geschäftslogik in Ihre Accessors geschrieben wird. Wenn die Speichereigenschaft nicht angegeben wird, werden stattdessen die öffentlichen Accessors verwendet. Sie können die Storage-Eigenschaft auch mit Column-Attributen verwenden.

Sobald Sie Beziehungen in Ihre Entitätsklassen eingeführt haben, wächst die Menge an Code, die Sie schreiben müssen, mit der Unterstützung für Benachrichtigungen und Graphkonsistenz. Glücklicherweise gibt es ein (später beschriebenes) Tool, mit dem alle erforderlichen Definitionen als Teilklassen generiert werden können, sodass Sie eine Mischung aus generiertem Code und benutzerdefinierter Geschäftslogik verwenden können.

Für den Rest dieses Dokuments wird davon ausgegangen, dass das Tool verwendet wurde, um einen vollständigen Northwind-Datenkontext und alle Entitätsklassen zu generieren.

Beziehungsübergreifendes Abfragen

Da Sie nun über Beziehungen verfügen, können Sie diese beim Schreiben von Abfragen verwenden, indem Sie einfach auf die in Ihrer Klasse definierten Beziehungseigenschaften verweisen.

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

Die obige Abfrage verwendet die Orders-Eigenschaft, um das übergreifende Produkt zwischen Kunden und Bestellungen zu bilden und eine neue Sequenz von Kunden- und Order-Paaren zu erzeugen.

Es ist auch möglich, das Gegenteil zu tun.

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

In diesem Beispiel werden die Bestellungen abgefragt, und die Kundenbeziehung wird verwendet, um auf Informationen zum zugeordneten Customer-Objekt zuzugreifen.

Ändern und Speichern von Entitäten

Nur wenige Anwendungen werden nur mit Abfrage erstellt. Auch Daten müssen erstellt und geändert werden. LINQ to SQL wurde entwickelt, um maximale Flexibilität beim Bearbeiten und Beibehalten von Änderungen an Ihren Objekten zu bieten. Sobald Entitätsobjekte verfügbar sind – entweder durch Abrufen über eine Abfrage oder neu erstellen –, können Sie sie als normale Objekte in Ihrer Anwendung bearbeiten, ihre Werte ändern oder sie nach Bedarf hinzufügen und aus Sammlungen entfernen. LINQ to SQL verfolgt alle Ihre Änderungen nach und ist bereit, sie zurück an die Datenbank zu übertragen, sobald Sie fertig sind.

Im folgenden Beispiel werden die Klassen Customer und Order verwendet, die von einem Tool aus den Metadaten der gesamten Northwind-Beispieldatenbank generiert werden. Die Klassendefinitionen wurden aus Gründen der Kürze nicht angezeigt.

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

Wenn SubmitChanges() aufgerufen wird, generiert und führt LINQ to SQL automatisch SQL-Befehle aus, um die Änderungen zurück an die Datenbank zu übertragen. Es ist auch möglich, dieses Verhalten mit benutzerdefinierter Logik zu überschreiben. Die benutzerdefinierte Logik kann eine gespeicherte Datenbankprozedur aufrufen.

Abfragen In-Depth

LINQ to SQL stellt eine Implementierung der Standardabfrageoperatoren für Objekte bereit, die Tabellen in einer relationalen Datenbank zugeordnet sind. In diesem Kapitel werden die LINQ to SQL spezifischen Aspekte von Abfragen beschrieben.

Abfrageausführung

Unabhängig davon, ob Sie eine Abfrage als allgemeinen Abfrageausdruck schreiben oder einen der einzelnen Operatoren erstellen, ist die von Ihnen geschriebene Abfrage keine sofort ausgeführte imperative Anweisung. Dies ist eine Beschreibung. Beispielsweise bezieht sich die lokale Variable q in der Deklaration unten auf die Beschreibung der Abfrage, nicht auf das Ergebnis der Ausführung.

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

Der tatsächliche Typ von q in diesem instance ist IQueryable<Customer>. Erst wenn die Anwendung versucht, den Inhalt der Abfrage aufzulisten, wird sie tatsächlich ausgeführt. In diesem Beispiel bewirkt die foreach-Anweisung die Ausführung.

Ein IQueryable-Objekt ähnelt einem ADO.NET Befehlsobjekt. Eine Abfrage in der Hand zu haben bedeutet nicht, dass eine Abfrage ausgeführt wurde. Ein Befehlsobjekt enthält eine Zeichenfolge, die eine Abfrage beschreibt. Ebenso enthält ein IQueryable-Objekt eine Beschreibung einer Abfrage, die als Datenstruktur codiert ist, die als Ausdruck bezeichnet wird. Ein Befehlsobjekt verfügt über eine ExecuteReader() -Methode, die die Ausführung verursacht und Ergebnisse als DataReader zurückgibt. Ein IQueryable-Objekt verfügt über eine GetEnumerator() -Methode, die die Ausführung verursacht und Ergebnisse als IEnumerator<Customer> zurückgibt.

Aus diesem Grund folgt, dass eine Abfrage, wenn sie zweimal aufgelistet wird, zweimal ausgeführt wird.

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

Dieses Verhalten wird als verzögerte Ausführung bezeichnet. Genau wie bei einem ADO.NET Befehlsobjekt ist es möglich, eine Abfrage festzuhalten und erneut auszuführen.

Natürlich müssen Anwendungsautoren häufig sehr explizit angeben, wo und wann eine Abfrage ausgeführt wird. Es wäre unerwartet, wenn eine Anwendung eine Abfrage mehrmals ausführen würde, einfach weil sie die Ergebnisse mehrmals untersuchen musste. Beispielsweise können Sie die Ergebnisse einer Abfrage an ein DataGrid binden. Das Steuerelement kann die Ergebnisse jedes Mal auf dem Bildschirm aufzählen.

Um die mehrfache Ausführung zu vermeiden, konvertieren Sie die Ergebnisse in eine beliebige Anzahl von Standardauflistungsklassen. Es ist einfach, die Ergebnisse mithilfe der Standardabfrageoperatoren ToList() oder ToArray() in eine Liste oder ein Array zu konvertieren.

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 

Ein Vorteil der verzögerten Ausführung besteht darin, dass Abfragen möglicherweise stückweise erstellt werden, wobei die Ausführung erst erfolgt, wenn die Konstruktion abgeschlossen ist. Sie können mit dem Komponieren eines Teils einer Abfrage beginnen, ihn einer lokalen Variablen zuweisen und dann einige Zeit später weitere Operatoren darauf anwenden.

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 

In diesem Beispiel beginnt q als Abfrage für alle Kunden in London. Später wird sie je nach Anwendungsstatus in eine geordnete Abfrage umgewandelt. Durch Verzögern der Ausführung kann die Abfrage so erstellt werden, dass sie genau den Anforderungen der Anwendung entspricht, ohne dass eine riskante Zeichenfolgenbearbeitung erforderlich ist.

Objektidentität

Objekte in der Runtime verfügen über eine eindeutige Identität. Wenn zwei Variablen auf dasselbe Objekt verweisen, verweisen sie tatsächlich auf dasselbe Objekt instance. Aus diesem Gründen sind Änderungen, die über einen Pfad durch eine Variable vorgenommen werden, sofort über die andere sichtbar. Zeilen in einer relationalen Datenbanktabelle weisen keine eindeutige Identität auf. Sie verfügen jedoch über einen Primärschlüssel, und dieser Primärschlüssel kann eindeutig sein, was bedeutet, dass keine zwei Zeilen denselben Schlüssel gemeinsam haben. Dies schränkt jedoch nur den Inhalt der Datenbanktabelle ein. Solange wir also nur über Remotebefehle mit den Daten interagieren, ist dies ungefähr dasselbe.

Dies ist jedoch selten der Fall. Am häufigsten werden Daten aus der Datenbank und in eine andere Ebene gebracht, in der eine Anwendung sie bearbeitet. Natürlich ist dies das Modell, das LINQ to SQL unterstützen soll. Wenn die Daten als Zeilen aus der Datenbank geholt werden, wird nicht erwartet, dass zwei Zeilen, die dieselben Daten darstellen, tatsächlich denselben Zeileninstanzen entsprechen. Wenn Sie einen bestimmten Kunden zweimal abfragen, erhalten Sie zwei Datenzeilen, die jeweils dieselben Informationen enthalten.

Bei Objekten erwarten Sie jedoch etwas ganz anderes. Wenn Sie den DataContext erneut nach denselben Informationen fragen, erhalten Sie tatsächlich dasselbe Objekt instance. Sie erwarten dies, da Objekte eine besondere Bedeutung für Ihre Anwendung haben und sich wie normale Objekte verhalten. Sie haben sie als Hierarchien oder Diagramme entworfen, und Sie erwarten sicherlich, sie als solche abzurufen, ohne Horden von replizierten Instanzen, nur weil Sie zweimal nach demselben Ding gefragt haben.

Aus diesem Gründen verwaltet DataContext die Objektidentität. Wenn eine neue Zeile aus der Datenbank abgerufen wird, wird sie mit ihrem Primärschlüssel in einer Identitätstabelle protokolliert, und ein neues Objekt wird erstellt. Wenn dieselbe Zeile erneut abgerufen wird, wird das ursprüngliche Objekt instance an die Anwendung zurückgegeben. Auf diese Weise übersetzt der DataContext das Datenbankkonzept der Identität (Schlüssel) in das Sprachenkonzept (Instanzen). Die Anwendung sieht das Objekt immer nur in dem Zustand, in dem es zuerst abgerufen wurde. Die neuen Daten werden, sofern sie unterschiedlich sind, weggeworfen.

Dies könnte Sie irritieren, denn warum würde eine Anwendung Daten wegwerfen? Wie sich herausstellt, verwaltet LINQ to SQL die Integrität der lokalen Objekte und kann optimistische Updates unterstützen. Da die einzigen Änderungen, die nach dem anfänglichen Erstellen des Objekts von der Anwendung vorgenommen werden, sind, ist die Absicht der Anwendung klar. Wenn änderungen durch eine externe Partei in der Zwischenzeit aufgetreten sind, werden sie zum Zeitpunkt des Aufrufs von SubmitChanges() identifiziert. Weitere Informationen hierzu werden im Abschnitt Gleichzeitige Änderungen erläutert.

Beachten Sie, dass für den Fall, dass die Datenbank eine Tabelle ohne Primärschlüssel enthält, LINQ to SQL das Übermitteln von Abfragen über die Tabelle zulässt, aber keine Updates zulässt. Dies liegt daran, dass das Framework aufgrund des Fehlens eines eindeutigen Schlüssels nicht identifizieren kann, welche Zeile aktualisiert werden soll.

Wenn das von der Abfrage angeforderte Objekt durch den Primärschlüssel leicht identifizierbar ist, da bereits abgerufen wurde, wird überhaupt keine Abfrage ausgeführt. Die Identitätstabelle fungiert als Cache zum Speichern aller zuvor abgerufenen Objekte.

Beziehungen

Wie wir in der Schnellübersicht gesehen haben, entsprechen Verweise auf andere Objekte oder Sammlungen anderer Objekte in Ihren Klassendefinitionen direkt den Fremdschlüsselbeziehungen in der Datenbank. Sie können diese Beziehungen verwenden, wenn Sie abfragen, indem Sie einfach die Punktnotation verwenden, um auf die Beziehungseigenschaften zuzugreifen und von einem Objekt zum anderen zu navigieren. Diese Zugriffsvorgänge werden in komplexere Joins oder korrelierte Unterabfragen in der entsprechenden SQL-Instanz übersetzt, sodass Sie ihr Objektdiagramm während einer Abfrage durchlaufen können. Die folgende Abfrage navigiert beispielsweise von Bestellungen zu Kunden und bietet somit die Möglichkeit, die Ergebnisse auf Bestellungen von Kunden aus London einzugrenzen.

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"

Wenn Beziehungseigenschaften nicht vorhanden wären, müssten Sie sie wie in einer SQL-Abfrage manuell als Joins ausschreiben.

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

Mit der Beziehungseigenschaft können Sie diese bestimmte Beziehung definieren, sobald Sie die Verwendung der bequemeren Punktsyntax aktivieren. Dies ist jedoch nicht der Grund, warum Beziehungseigenschaften vorhanden sind. Sie sind vorhanden, weil wir unsere domänenspezifischen Objektmodelle als Hierarchien oder Diagramme definieren. Die Objekte, für die wir programmieren möchten, weisen Verweise auf andere Objekte auf. Es ist nur ein glücklicher Zufall, dass, da Objekt-zu-Objekt-Beziehungen fremdschlüsselartigen Beziehungen in Datenbanken entsprechen, der Eigenschaftenzugriff zu einer bequemen Möglichkeit zum Schreiben von Joins führt.

Daher ist das Vorhandensein von Beziehungseigenschaften auf der Ergebnisseite einer Abfrage wichtiger als als als Teil der Abfrage selbst. Sobald Sie einen bestimmten Kunden in den Händen haben, sagt Ihnen die Klassendefinition, dass Kunden Bestellungen haben. Wenn Sie sich also die Orders-Eigenschaft eines bestimmten Kunden ansehen, erwarten Sie, dass die Sammlung mit allen Bestellungen des Kunden aufgefüllt wird, da dies tatsächlich der Vertrag ist, den Sie durch Definieren der Klassen auf diese Weise deklariert haben. Sie erwarten, dass die Bestellungen dort angezeigt werden, auch wenn Sie nicht im Voraus nach Aufträgen gefragt haben. Sie erwarten, dass Ihr Objektmodell die Illusion behält, dass es sich um eine In-Memory-Erweiterung der Datenbank handelt, mit unmittelbar verfügbaren zugehörigen Objekten.

LINQ to SQL implementiert eine Technik namens verzögertes Laden, um diese Illusion aufrechtzuerhalten. Wenn Sie ein Objekt abfragen, rufen Sie tatsächlich nur die Objekte ab, die Sie angefordert haben. Die verwandten Objekte werden nicht automatisch zum gleichen Zeitpunkt abgerufen. Die Tatsache, dass die zugehörigen Objekte nicht bereits geladen sind, ist jedoch nicht zu beobachten, da sobald Sie versuchen, darauf zuzugreifen, eine Anforderung zum Abrufen dieser Objekte ausgeht.

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

Beispielsweise können Sie einen bestimmten Satz von Bestellungen abfragen und dann nur gelegentlich eine E-Mail-Benachrichtigung an bestimmte Kunden senden. Sie müssen nicht alle Kundendaten im Voraus bei jeder Bestellung abrufen. Mit verzögertem Laden können Sie die Kosten für das Abrufen zusätzlicher Informationen zurückstellen, bis Sie dies unbedingt müssen.

Natürlich kann auch das Gegenteil der Fall sein. Möglicherweise verfügen Sie über eine Anwendung, die Kunden- und Bestelldaten gleichzeitig betrachten muss. Sie wissen, dass Sie beide Datensätze benötigen. Sie wissen, dass Ihre Anwendung einen Drilldown durch die Bestellungen jedes Kunden durchführen wird, sobald Sie sie erhalten. Es wäre bedauerlich, einzelne Abfragen für Bestellungen für jeden Kunden abzufeuern. Was Sie wirklich wollen, ist, dass die Bestelldaten zusammen mit den Kunden abgerufen werden.

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

Sicherlich können Sie immer eine Möglichkeit finden, Kunden und Bestellungen in einer Abfrage zusammenzuführen, indem Sie das übergreifende Produkt bilden und alle relativen Datenbits als eine große Projektion abrufen. Dann wären die Ergebnisse jedoch keine Entitäten. Entitäten sind Objekte mit Identität, die Sie ändern können, während die Ergebnisse Projektionen sind, die nicht geändert und beibehalten werden können. Schlimmer noch: Sie würden eine große Menge redundanter Daten abrufen, wenn jeder Kunde für jede Bestellung in der geflachten Joinausgabe wiederholt.

Was Sie wirklich benötigen, ist eine Möglichkeit, einen Satz verwandter Objekte zur gleichen Zeit abzurufen – einen abgegrenzten Teil eines Graphen, sodass Sie nie mehr oder weniger abrufen würden, als für die beabsichtigte Verwendung erforderlich war.

LINQ to SQL können Sie aus diesem Grund das sofortige Laden einer Region Ihres Objektmodells anfordern. Dies geschieht, indem die Spezifikation eines DataShape-Objekts für einen DataContext zugelassen wird. Die DataShape-Klasse wird verwendet, um das Framework anzuweisen, welche Objekte beim Abrufen eines bestimmten Typs abgerufen werden sollen. Dies wird mithilfe der LoadWith-Methode wie folgt erreicht:

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

In der vorherigen Abfrage werden alle Bestellungen für alle Kunden , die in London leben, abgerufen, wenn die Abfrage ausgeführt wird, sodass der nachfolgende Zugriff auf die Orders-Eigenschaft für ein Customer-Objekt keine Datenbankabfrage auslöst.

Die DataShape-Klasse kann auch verwendet werden, um Unterabfragen anzugeben, die auf eine Beziehungsnavigation angewendet werden. Wenn Sie beispielsweise nur die Bestellungen abrufen möchten, die heute versendet wurden, können Sie die AssociateWith-Methode für die DataShape wie folgt verwenden:

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

Im vorherigen Code durchläuft die innere foreach-Anweisung gerade die Bestellungen , die heute versendet wurden, da gerade solche Bestellungen aus der Datenbank abgerufen wurden.

Es ist wichtig, zwei Fakten zur DataShape-Klasse zu beachten:

  1. Nach dem Zuweisen einer DataShape zu einem DataContext kann die DataShape nicht mehr geändert werden. Jeder LoadWith - oder AssociateWith-Methodenaufruf für eine solche DataShape gibt zur Laufzeit einen Fehler zurück.

  2. Es ist unmöglich, Zyklen mit LoadWith oder AssociateWith zu erstellen. So generiert beispielsweise zur Laufzeit ein Fehler:

    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)
    

Joins

Die meisten Abfragen für Objektmodelle basieren stark auf der Navigation von Objektverweisen im Objektmodell. Es gibt jedoch interessante "Beziehungen" zwischen Entitäten, die im Objektmodell möglicherweise nicht als Verweise erfasst werden. Beispielsweise ist Customer.Orders eine nützliche Beziehung, die auf Fremdschlüsselbeziehungen in der Northwind-Datenbank basiert. Lieferanten und Kunden in demselben Ort oder Land sind jedoch eine Ad-hoc-Beziehung , die nicht auf einer Fremdschlüsselbeziehung basiert und möglicherweise nicht im Objektmodell erfasst wird. Joins bieten einen zusätzlichen Mechanismus zum Behandeln solcher Beziehungen. LINQ to SQL unterstützt die neuen Joinoperatoren, die in LINQ eingeführt wurden.

Stellen Sie sich das folgende Problem vor: Suchen Sie Lieferanten und Kunden in derselben Stadt. Die folgende Abfrage gibt die Namen von Lieferanten- und Kundenunternehmen und die gemeinsame Stadt als vereinfachtes Ergebnis zurück. Dies entspricht dem inneren Equi-Join in relationalen Datenbanken:

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

Die obige Abfrage eliminiert Lieferanten, die sich nicht in derselben Stadt wie ein bestimmter Kunde befinden. Es gibt jedoch Situationen, in denen wir eine der Entitäten in einer Ad-hoc-Beziehung nicht entfernen möchten. Die folgende Abfrage listet alle Lieferanten mit Kundengruppen für jeden Lieferanten auf. Wenn ein bestimmter Lieferant keinen Kunden in derselben Stadt hat, ist das Ergebnis eine leere Sammlung von Kunden, die diesem Lieferanten entsprechen. Beachten Sie, dass die Ergebnisse nicht flach sind– jeder Lieferant verfügt über eine zugeordnete Sammlung. Dadurch wird eine Gruppenbeitrittsmenge bereitgestellt. Dadurch werden zwei Sequenzen verknüpft und Elemente der zweiten Sequenz durch die Elemente der ersten Sequenz gruppiert.

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

Gruppenbeitritt kann auch auf mehrere Sammlungen erweitert werden. Die folgende Abfrage erweitert die obige Abfrage, indem Mitarbeiter aufgelistet werden, die sich in derselben Stadt wie der Lieferant befinden. Hier zeigt das Ergebnis einen Lieferanten mit (möglicherweise leeren) Sammlungen von Kunden und Mitarbeitern.

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

Die Ergebnisse einer Gruppeneinbindung können auch vereinfacht werden. Die Ergebnisse der Vereinfachung des Gruppenverbunds zwischen Lieferanten und Kunden sind mehrere Einträge für Lieferanten mit mehreren Kunden in ihrer Stadt – einer pro Kunde. Leere Auflistungen werden durch NULL-Werte ersetzt. Dies entspricht einem linken äußeren Equi-Join in relationalen Datenbanken.

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

Die Signaturen für die zugrunde liegenden Joinoperatoren sind im Dokument standardabfrageoperator.the signatures for underlying join operators are defined in the standard query operators document. Nur Equi-Joins werden unterstützt, und die beiden Operanden von Gleichheitszeichen müssen denselben Typ aufweisen.

Projektionen

Bisher haben wir uns nur Abfragen zum Abrufen von Entitäten angesehen, d. h. Objekte, die direkt Datenbanktabellen zugeordnet sind. Wir müssen uns nicht darauf beschränken. Das Schöne an einer Abfragesprache ist, dass Sie Informationen in beliebiger Form abrufen können. Wenn Sie dies tun, können Sie die automatische Änderungsnachverfolgung oder Identitätsverwaltung nicht nutzen. Sie können jedoch nur die gewünschten Daten abrufen.

Beispielsweise müssen Sie möglicherweise einfach die Firmennamen aller Kunden in London kennen. Wenn dies der Fall ist, gibt es keinen bestimmten Grund, ganze Kundenobjekte abzurufen, nur um Namen zu markieren. Sie können die Namen im Rahmen der Abfrage projizieren.

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

In diesem Fall wird q zu einer Abfrage, die eine Sequenz von Zeichenfolgen abruft.

Wenn Sie mehr als nur einen einzelnen Namen zurückerhalten möchten, aber nicht genug, um das Abrufen des gesamten Kundenobjekts zu rechtfertigen, können Sie eine beliebige Teilmenge angeben, indem Sie die Ergebnisse als Teil Ihrer Abfrage erstellen.

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

In diesem Beispiel wird ein anonymer Objektinitialisierer verwendet, um eine Struktur zu erstellen, die sowohl den Firmennamen als auch die Telefonnummer enthält. Möglicherweise wissen Sie nicht, was Sie den Typ aufrufen sollen, aber mit implizit typisierter lokaler Variablendeklaration in der Sprache, die Sie nicht unbedingt benötigen.

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

Wenn Sie die Daten sofort nutzen, sind anonyme Typen eine gute Alternative zum expliziten Definieren von Klassen, die Ihre Abfrageergebnisse enthalten.

Sie können auch Kreuzprodukte von ganzen Objekten bilden, obwohl Sie möglicherweise selten einen Grund dafür haben.

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

Diese Abfrage erstellt eine Sequenz von Paaren von Kunden- und Auftragsobjekten.

Es ist auch möglich, Projektionen in jeder Phase der Abfrage zu erstellen. Sie können Daten in neu erstellte Objekte projizieren und dann in nachfolgenden Abfragevorgängen auf die Member dieser Objekte verweisen.

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

Seien Sie jedoch vorsichtig bei der Verwendung parametrisierter Konstruktoren in dieser Phase. Dies ist technisch gültig, aber es ist für LINQ to SQL unmöglich, nachzuverfolgen, wie sich die Konstruktorverwendung auf den Memberzustand auswirkt, ohne den tatsächlichen Code im Konstruktor zu verstehen.

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

Da LINQ to SQL Versuche, die Abfrage in rein relationale sql lokal definierte Objekttypen zu übersetzen, auf dem Server nicht verfügbar sind, um tatsächlich zu erstellen. Alle Objektkonstruktionen werden tatsächlich verschoben, bis die Daten aus der Datenbank abgerufen wurden. Anstelle der tatsächlichen Konstruktoren verwendet die generierte SQL-Instanz die normale SQL-Spaltenprojektion. Da der Abfrageübersetzung nicht verstehen kann, was während eines Konstruktoraufrufs geschieht, kann er keine Bedeutung für das Feld Name von MyType festlegen.

Stattdessen besteht die bewährte Methode darin, Immer Objektinitialisierer zum Codieren von Projektionen zu verwenden.

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

Der einzige sichere Ort für die Verwendung eines parametrisierten Konstruktors ist die endgültige Projektion einer Abfrage.

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>

Sie können bei Bedarf sogar eine aufwendige Schachtelung von Objektkonstruktoren verwenden, wie in diesem Beispiel, das XML direkt aus dem Ergebnis einer Abfrage erstellt. Es funktioniert, solange es sich um die letzte Projektion der Abfrage handelt.

Selbst wenn Konstruktoraufrufe verstanden werden, sind Aufrufe lokaler Methoden möglicherweise nicht vorhanden. Wenn Ihre endgültige Projektion den Aufruf lokaler Methoden erfordert, ist es unwahrscheinlich, dass LINQ to SQL dazu in der Lage sein wird, dies zu verpflichten. Methodenaufrufe, die keine bekannte Übersetzung in SQL aufweisen, können nicht als Teil der Abfrage verwendet werden. Eine Ausnahme von dieser Regel sind Methodenaufrufe, die keine Argumente haben, die von Abfragevariablen abhängig sind. Diese werden nicht als Teil der übersetzten Abfrage betrachtet und stattdessen als Parameter behandelt.

Für immer noch aufwendige Projektionen (Transformationen) ist möglicherweise eine lokale prozedurale Logik erforderlich. Damit Sie Ihre eigenen lokalen Methoden in einer endgültigen Projektion verwenden können, müssen Sie zweimal projizieren. Die erste Projektion extrahiert alle Datenwerte, auf die Sie verweisen müssen, und die zweite Projektion führt die Transformation aus. Zwischen diesen beiden Projektionen befindet sich ein Aufruf des AsEnumerable()-Operators, der die Verarbeitung an diesem Punkt von einer LINQ to SQL Abfrage in eine lokal ausgeführte abfrage verschiebt.

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

Hinweis Der AsEnumerable()- Operator verursacht im Gegensatz zu ToList() und ToArray()keine Ausführung der Abfrage. Es wird noch zurückgestellt. Der AsEnumerable() -Operator ändert lediglich die statische Eingabe der Abfrage und wandelt ein IQueryable<T> (IQueryable (ofT) in Visual Basic in ein IEnumerable<T> (IEnumerable (ofT) in Visual Basic um und verleiten den Compiler, den Rest der Abfrage als lokal ausgeführt zu behandeln.

Kompilierte Abfragen

In vielen Anwendungen ist es üblich, strukturell ähnliche Abfragen mehrmals auszuführen. In solchen Fällen ist es möglich, die Leistung zu erhöhen, indem die Abfrage einmal kompiliert und mehrmals in der Anwendung mit verschiedenen Parametern ausgeführt wird. Dieses Ergebnis wird in LINQ to SQL mithilfe der CompiledQuery-Klasse abgerufen. Der folgende Code zeigt, wie eine kompilierte Abfrage definiert wird:

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

Die Compile-Methode gibt einen Delegaten zurück, der mehrmals zwischengespeichert und anschließend ausgeführt werden kann, indem einfach die Eingabeparameter geändert werden. Im folgenden Code wird ein Beispiel hierfür dargestellt:

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

SQL-Übersetzung

LINQ to SQL führt keine Abfragen aus, die relationale Datenbank jedoch. LINQ to SQL übersetzt die von Ihnen geschriebenen Abfragen in entsprechende SQL-Abfragen und sendet sie zur Verarbeitung an den Server. Da die Ausführung verzögert wird, kann LINQ to SQL Ihre gesamte Abfrage untersuchen, auch wenn sie aus mehreren Teilen zusammengesetzt ist.

Da der relationale Datenbankserver IL nicht tatsächlich ausführt (abgesehen von der CLR-Integration in SQL Server 2005), werden die Abfragen nicht als IL an den Server übertragen. Sie werden tatsächlich als parametrisierte SQL-Abfragen in Textform übertragen.

Natürlich ist SQL – selbst T-SQL mit CLR-Integration – nicht in der Lage, die Vielzahl von Methoden auszuführen, die lokal für Ihr Programm verfügbar sind. Daher müssen die von Ihnen geschriebenen Abfragen in gleichwertige Vorgänge und Funktionen übersetzt werden, die in der SQL-Umgebung verfügbar sind.

Die meisten Methoden und Operatoren in integrierten .NET Framework-Typen verfügen über direkte Übersetzungen in SQL. Einige können aus den verfügbaren Funktionen erstellt werden. Diejenigen, die nicht übersetzt werden können, sind nicht zulässig, sodass Laufzeitausnahmen generiert werden, wenn Sie versuchen, sie zu verwenden. Es gibt einen Abschnitt weiter unten im Dokument, in dem die Frameworkmethoden erläutert werden, die für die Übersetzung in SQL implementiert werden.

Der Entitätslebenszyklus

LINQ to SQL ist mehr als nur eine Implementierung der Standardabfrageoperatoren für relationale Datenbanken. Neben der Übersetzung von Abfragen handelt es sich um einen Dienst, der Ihre Objekte während ihrer gesamten Lebensdauer verwaltet und Ihnen dabei hilft, die Integrität Ihrer Daten aufrechtzuerhalten und den Prozess der Rückübersetzung Ihrer Änderungen in den Speicher zu automatisieren.

In einem typischen Szenario werden Objekte über eine oder mehrere Abfragen abgerufen und dann in irgendeiner Weise bearbeitet, bis die Anwendung bereit ist, die Änderungen zurück an den Server zu senden. Dieser Vorgang kann sich mehrmals wiederholen, bis die Anwendung diese Informationen nicht mehr verwendet. An diesem Punkt werden die Objekte von der Laufzeit wie normale Objekte wieder freigegeben. Die Daten verbleiben jedoch in der Datenbank. Auch nach dem Löschen aus der Laufzeit können Objekte, die die gleichen Daten darstellen, weiterhin abgerufen werden. In diesem Sinne besteht die wahre Lebensdauer des Objekts über jede einzelne Laufzeitmanifestation hinaus.

Im Mittelpunkt dieses Kapitels steht der Entitätslebenszyklus , bei dem sich ein Zyklus auf die Zeitspanne einer einzelnen Manifestation eines Entitätsobjekts in einem bestimmten Laufzeitkontext bezieht. Der Zyklus beginnt, wenn dataContext eine neue instance erkennt, und endet, wenn das Objekt oder DataContext nicht mehr benötigt wird.

Nachverfolgen von Änderungen

Nachdem Entitäten aus der Datenbank abgerufen wurden, können Sie sie beliebig bearbeiten. Sie sind Ihre Objekte; verwenden Sie sie wie Sie möchten. In diesem Fall verfolgt LINQ to SQL Änderungen nach, damit sie beim Aufruf von SubmitChanges() in der Datenbank beibehalten werden können.

LINQ to SQL beginnt, Ihre Entitäten zu verfolgen, sobald sie aus der Datenbank abgerufen werden, bevor Sie sie jemals in die Hand nehmen. Tatsächlich ist auch der zuvor besprochene Identitätsverwaltungsdienst bereits aktiviert. Änderungsnachverfolgungskosten nur sehr wenig zusätzlichen Mehraufwand, bis Sie tatsächlich mit dem Vornehmen von Änderungen beginnen.

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"

Sobald der CompanyName im obigen Beispiel zugewiesen ist, wird LINQ to SQL von der Änderung informiert und kann sie aufzeichnen. Die ursprünglichen Werte aller Datenmember werden vom Änderungsnachverfolgungsdienst beibehalten.

Der Änderungsnachverfolgungsdienst zeichnet auch alle Bearbeitungen von Beziehungseigenschaften auf. Sie verwenden Beziehungseigenschaften, um die Verknüpfungen zwischen Ihren Entitäten herzustellen, auch wenn diese möglicherweise durch Schlüsselwerte in der Datenbank verknüpft sind. Es ist nicht erforderlich, die Member, die den Schlüsselspalten zugeordnet sind, direkt zu ändern. LINQ to SQL synchronisiert sie automatisch für Sie, bevor die Änderungen übermittelt werden.

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

Sie können Bestellungen von einem Kunden zu einem anderen verschieben, indem Sie einfach seiner Kundeneigenschaft eine Zuweisung vornehmen. Da die Beziehung zwischen dem Kunden und der Bestellung besteht, können Sie die Beziehung ändern, indem Sie beide Seiten ändern. Sie hätten sie genauso einfach aus der Sammlung Orders von cust2 entfernen und der Orders-Auflistung von cust1 hinzufügen können, wie unten gezeigt.

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)

Wenn Sie einer Beziehung den Wert null zuweisen, werden Sie die Beziehung natürlich vollständig los. Durch Zuweisen einer Customer-Eigenschaft eines Auftrags zu NULL wird die Bestellung tatsächlich aus der Liste des Kunden entfernt.

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

Die automatische Aktualisierung beider Seiten einer Beziehung ist wichtig, um die Konsistenz Ihres Objektgraphen zu gewährleisten. Im Gegensatz zu normalen Objekten sind Beziehungen zwischen Daten häufig bidirektional. mit LINQ to SQL können Sie Mithilfe von Eigenschaften Beziehungen darstellen. Es bietet jedoch keinen Dienst, um diese bidirektionalen Eigenschaften automatisch synchron zu halten. Dies ist eine Dienstebene, die direkt in Ihre Klassendefinitionen eingefügt werden muss. Entitätsklassen, die mit dem Codegenerierungstool generiert werden, verfügen über diese Funktion. Im nächsten Kapitel zeigen wir Ihnen, wie Sie dies für Ihre eigenen handschriftlichen Klassen tun.

Beachten Sie jedoch, dass das Entfernen einer Beziehung nicht bedeutet, dass ein Objekt aus der Datenbank gelöscht wurde. Denken Sie daran, dass die Lebensdauer der zugrunde liegenden Daten in der Datenbank beibehalten wird, bis die Zeile aus der Tabelle gelöscht wurde. Die einzige Möglichkeit, ein Objekt tatsächlich zu löschen, besteht darin, es aus seiner Table-Auflistung zu entfernen.

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)

Wie bei allen anderen Änderungen wurde die Reihenfolge nicht gelöscht. Es sieht uns einfach so aus, da es entfernt und vom Rest unserer Objekte getrennt wurde. Wenn das Order-Objekt aus der Tabelle Orders entfernt wurde, wurde es vom Änderungsnachverfolgungsdienst zum Löschen markiert. Die tatsächliche Löschung aus der Datenbank erfolgt, wenn die Änderungen bei einem Aufruf von SubmitChanges() übermittelt werden. Beachten Sie, dass das Objekt selbst nie gelöscht wird. Die Laufzeit verwaltet die Lebensdauer von Objektinstanzen, sodass sie so lange bleibt, wie Sie noch einen Verweis darauf enthalten. Nachdem ein Objekt jedoch aus seiner Tabelle entfernt wurde und Änderungen übermittelt wurden, wird es nicht mehr vom Änderungsnachverfolgungsdienst nachverfolgt.

Die einzige andere Zeit, dass eine Entität nicht nachverfolgt wird, ist, wenn sie vorhanden ist, bevor der DataContext sie erkennt. Dies geschieht immer dann, wenn Sie neue Objekte in Ihrem Code erstellen. Sie können Instanzen von Entitätsklassen in Ihrer Anwendung verwenden, ohne sie jemals aus einer Datenbank abzurufen. Änderungs tacking und Identitätsverwaltung gelten nur für die Objekte, die dem DataContext bekannt sind. Daher ist keiner der Dienste für neu erstellte Instanzen aktiviert, bis Sie sie dem DataContext hinzufügen.

Dies kann auf eine von zwei Arten auftreten. Sie können die Add() -Methode für die zugehörige Table-Auflistung manuell aufrufen.

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)

Alternativ können Sie ein neues instance an ein Objekt anfügen, das dem DataContext bereits bekannt ist.

C#

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

Visual Basic

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

DataContext ermittelt Ihre neuen Objektinstanzen auch dann, wenn sie an andere neue Instanzen angefügt sind.

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

Im Grunde erkennt dataContext jede Entität in Ihrem Objektgraphen, die derzeit nicht als neue instance nachverfolgt wird, unabhängig davon, ob Sie die Add()-Methode aufgerufen haben oder nicht.

Verwenden eines schreibgeschützten DataContext

In vielen Szenarien ist es nicht erforderlich, die aus der Datenbank abgerufenen Entitäten zu aktualisieren. Eine Tabelle mit Kunden auf einer Webseite ist ein offensichtliches Beispiel. In all diesen Fällen ist es möglich, die Leistung zu verbessern, indem DataContext angewiesen wird, die Änderungen an den Entitäten nicht nachzuverfolgen. Dies wird erreicht, indem die ObjectTracking-Eigenschaft im DataContext wie im folgenden Code als false angegeben wird:

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

Übermitteln von Änderungen

Unabhängig davon, wie viele Änderungen Sie an Ihren Objekten vornehmen, wurden diese Änderungen nur an Speicherreplikaten vorgenommen. Mit den tatsächlichen Daten in der Datenbank ist noch nichts passiert. Die Übertragung dieser Informationen an den Server erfolgt erst, wenn Sie sie explizit anfordern, indem Sie SubmitChanges() im DataContext aufrufen.

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

Wenn Sie SubmitChanges() aufrufen, versucht DataContext , alle Ihre Änderungen in entsprechende SQL-Befehle zu übersetzen und Zeilen in entsprechenden Tabellen einzufügen, zu aktualisieren oder zu löschen. Diese Aktionen können auf Wunsch von Ihrer eigenen benutzerdefinierten Logik überschrieben werden, die Reihenfolge der Übermittlung wird jedoch von einem Dienst des DataContext orchestriert, der als Änderungsprozessor bekannt ist.

Das erste, was beim Aufrufen von SubmitChanges() geschieht, ist, dass der Satz bekannter Objekte untersucht wird, um festzustellen, ob neue Instanzen an sie angefügt wurden. Diese neuen Instanzen werden der Gruppe der nachverfolgten Objekte hinzugefügt. Als Nächstes werden alle Objekte mit ausstehenden Änderungen basierend auf Abhängigkeiten zwischen ihnen in eine Sequenz von Objekten sortiert. Objekte, deren Änderungen von anderen Objekten abhängen, werden nach ihren Abhängigkeiten sequenziert. Fremdschlüsseleinschränkungen und Eindeutigkeitseinschränkungen in der Datenbank spielen eine große Rolle bei der Bestimmung der richtigen Reihenfolge von Änderungen. Dann, kurz bevor tatsächliche Änderungen übertragen werden, wird eine Transaktion gestartet, um die Reihe einzelner Befehle zu kapseln, es sei denn, einer befindet sich bereits im Bereich. Schließlich werden die Änderungen an den Objekten nacheinander in SQL-Befehle übersetzt und an den Server gesendet.

Zu diesem Zeitpunkt führen alle von der Datenbank erkannten Fehler dazu, dass der Übermittlungsprozess abgebrochen wird, und eine Ausnahme wird ausgelöst. Alle Änderungen an der Datenbank werden so zurückgesetzt, als ob keine der Übermittlungen jemals stattgefunden hätte. Der DataContext verfügt weiterhin über eine vollständige Aufzeichnung aller Änderungen, sodass es möglich ist, zu versuchen, das Problem zu beheben und sie erneut zu übermitteln, indem SubmitChanges() erneut aufgerufen wird.

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

Wenn die Transaktion um die Übermittlung erfolgreich abgeschlossen wurde, akzeptiert DataContext die Änderungen an den Objekten, indem die Änderungsnachverfolgungsinformationen einfach vergessen werden.

Gleichzeitige Änderungen

Es gibt eine Vielzahl von Gründen, warum ein Aufruf von SubmitChanges() fehlschlägt. Möglicherweise haben Sie ein Objekt mit einem ungültigen Primärschlüssel erstellt. eine, die bereits verwendet wird, oder mit einem Wert, der gegen eine Überprüfungseinschränkung der Datenbank verstößt. Diese Art von Überprüfungen ist schwer in die Geschäftslogik zu integrieren, da sie häufig absolute Kenntnisse des gesamten Datenbankzustands erfordern. Der wahrscheinlichste Grund für einen Fehler besteht jedoch darin, dass eine andere Person Änderungen an den Objekten vor Ihnen vorgenommen hat.

Dies wäre sicherlich nicht möglich, wenn Sie jedes Objekt in der Datenbank sperren und eine vollständig serialisierte Transaktion verwenden würden. Diese Art der Programmierung (pessimistische Parallelität) wird jedoch selten verwendet, da sie teuer ist und echte Konflikte selten auftreten. Die beliebteste Form der Verwaltung gleichzeitiger Änderungen ist die Verwendung einer Form von optimistischer Parallelität. In diesem Modell werden überhaupt keine Sperren für die Datenbankzeilen vorgenommen. Das bedeutet, dass eine beliebige Anzahl von Änderungen an der Datenbank zwischen dem Zeitpunkt, an dem Sie Ihre Objekte zum ersten Mal abgerufen haben, und dem Zeitpunkt, zu dem Sie Ihre Änderungen übermittelt haben, aufgetreten sein können.

Wenn Sie also nicht mit einer Richtlinie fortfahren möchten, die das letzte Update gewinnt und alles, was vor Ihnen aufgetreten ist, löschen möchten, möchten Sie wahrscheinlich darauf hingewiesen werden, dass die zugrunde liegenden Daten von einer anderen Person geändert wurden.

DataContext bietet integrierte Unterstützung für optimistische Parallelität durch automatische Erkennung von Änderungskonflikten. Einzelne Updates sind nur erfolgreich, wenn der aktuelle Zustand der Datenbank mit dem Zustand übereinstimmt, den Sie beim ersten Abrufen ihrer Objekte verstanden haben. Dies geschieht auf Objektbasis, wobei Sie nur auf Verstöße hingewiesen werden, wenn diese bei Objekten auftreten, an denen Sie Änderungen vorgenommen haben.

Sie können den Grad steuern, in dem DataContext Änderungskonflikte erkennt, wenn Sie Ihre Entitätsklassen definieren. Jedes Column-Attribut verfügt über eine Eigenschaft namens UpdateCheck , die einem von drei Werten zugewiesen werden kann: Always, Never und WhenChanged. Wenn nicht der Standardwert für ein Column-Attributauf Always festgelegt ist, werden die von diesem Element dargestellten Datenwerte immer auf Konflikte überprüft, es sei denn, es gibt einen offensichtlichen Tie-Breaker wie einen Versionsstempel. Ein Column-Attribut verfügt über eine IsVersion-Eigenschaft , mit der Sie angeben können, ob der Datenwert einen versionsstempel darstellt, der von der Datenbank verwaltet wird. Wenn eine Version vorhanden ist, wird die Version allein verwendet, um zu ermitteln, ob ein Konflikt aufgetreten ist.

Wenn ein Änderungskonflikt auftritt, wird eine Ausnahme wie ein anderer Fehler ausgelöst. Die Transaktion um die Übermittlung wird abgebrochen, aber der DataContext bleibt unverändert, sodass Sie das Problem beheben und es erneut versuchen können.

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

Wenn Sie Änderungen auf einer mittleren Ebene oder einem Server vornehmen, können Sie einen Änderungskonflikt am einfachsten beheben, indem Sie einfach von vorne beginnen und es erneut versuchen, den Kontext neu zu erstellen und die Änderungen erneut zu verwenden. Weitere Optionen werden im folgenden Abschnitt beschrieben.

Transaktionen

Eine Transaktion ist ein Von Datenbanken oder einem anderen Ressourcen-Manager bereitgestellter Dienst, der verwendet werden kann, um zu gewährleisten, dass eine Reihe einzelner Aktionen automatisch ausgeführt wird. bedeutet, dass entweder sie alle erfolgreich sind oder nicht. Wenn dies nicht der Fall ist, werden sie auch alle automatisch rückgängig machen, bevor etwas anderes passieren darf. Wenn sich bereits keine Transaktion im Bereich befindet, startet DataContext automatisch eine Datenbanktransaktion, um Updates zu schützen, wenn Sie SubmitChanges()aufrufen.

Sie können den Typ der verwendeten Transaktion, ihre Isolationsstufe oder den tatsächlichen Umfang der Transaktion steuern, indem Sie sie selbst initiieren. Die Transaktionsisolation, die dataContext verwendet, wird als ReadCommitted bezeichnet.

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

Im obigen Beispiel wird eine vollständig serialisierte Transaktion initiiert, indem ein neues Transaktionsbereichsobjekt erstellt wird. Alle Datenbankbefehle, die innerhalb des Bereichs der Transaktion ausgeführt werden, werden von der Transaktion geschützt.

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

Diese geänderte Version desselben Beispiels verwendet die ExecuteCommand()- Methode für dataContext , um eine gespeicherte Prozedur in der Datenbank auszuführen, bevor die Änderungen übermittelt werden. Unabhängig davon, was die gespeicherte Prozedur mit der Datenbank ausführt, können wir sicher sein, dass ihre Aktionen Teil derselben Transaktion sind.

Wenn die Transaktion erfolgreich abgeschlossen wurde, löst dataContext alle akkumulierten Nachverfolgungsinformationen aus und behandelt die neuen Zustände der Entitäten als unverändert. Es wird jedoch kein Rollback der Änderungen an Ihren Objekten durchgeführt, wenn die Transaktion fehlschlägt. Dies ermöglicht Ihnen maximale Flexibilität beim Umgang mit Problemen bei der Änderungsübermittlung.

Es ist auch möglich, anstelle des neuen TransactionScope eine lokale SQL-Transaktion zu verwenden. LINQ to SQL bietet diese Funktion, um LINQ to SQL Features in bereits vorhandene ADO.NET-Anwendungen zu integrieren. Wenn Sie diese Route gehen, müssen Sie jedoch für viel mehr verantwortlich sein.

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

Wie Sie sehen, ist die Verwendung einer manuell gesteuerten Datenbanktransaktion etwas komplexer. Sie müssen es nicht nur selbst starten, sie müssen auch den DataContext explizit anweisen, ihn zu verwenden, indem Sie es der Transaction-Eigenschaft zuweisen. Anschließend müssen Sie einen Try-Catch-Block verwenden, um Ihre Übermittlungslogik zu hüllen. Denken Sie daran, die Transaktion explizit zum Commit anzuweisen und den DataContext explizit anzuweisen, Änderungen zu akzeptieren oder die Transaktionen abzubrechen, wenn ein Fehler zu irgendeinem Zeitpunkt auftritt. Vergessen Sie außerdem nicht, die Transaction-Eigenschaft wieder auf NULL festzulegen, wenn Sie fertig sind.

Gespeicherte Prozeduren

Wenn SubmitChanges() aufgerufen wird, generiert und führt LINQ to SQL SQL-Befehle aus, um Zeilen in die Datenbank einzufügen, zu aktualisieren und zu löschen. Diese Aktionen können von Anwendungsentwicklern überschrieben werden, und an ihrer Stelle kann benutzerdefinierter Code verwendet werden, um die gewünschten Aktionen auszuführen. Auf diese Weise können alternative Einrichtungen wie datenbankgespeicherte Prozeduren vom Änderungsprozessor automatisch aufgerufen werden.

Betrachten Sie eine gespeicherte Prozedur zum Aktualisieren der vorrätig verfügbaren Einheiten für die Tabelle Products in der Beispieldatenbank Northwind. Die SQL-Deklaration der Prozedur lautet wie folgt.

SQL

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

Sie können die gespeicherte Prozedur anstelle des normalen automatisch generierten Updatebefehls verwenden, indem Sie eine Methode für Ihren stark typisierten DataContext definieren. Auch wenn die DataContext-Klasse vom codegenerierungstool LINQ to SQL automatisch generiert wird, können Sie diese Methoden dennoch in einer eigenen partiellen Klasse angeben.

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

Die Signatur der Methode und des generischen Parameters weist dataContext an, diese Methode anstelle einer generierten update-Anweisung zu verwenden. Die ursprünglichen und aktuellen Parameter werden von LINQ to SQL verwendet, um die originalen und aktuellen Kopien des Objekts des angegebenen Typs zu übergeben. Die beiden Parameter sind für die Erkennung optimistischer Parallelitätskonflikte verfügbar.

Hinweis Wenn Sie die Standardaktualisierungslogik überschreiben, liegt die Konflikterkennung in Ihrer Verantwortung.

Die gespeicherte Prozedur UpdateProductStock wird mithilfe der ExecuteCommand() -Methode des DataContext aufgerufen. Es gibt die Anzahl der betroffenen Zeilen zurück und weist die folgende Signatur auf:

C#

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

Visual Basic

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

Das Objektarray wird zum Übergeben von Parametern verwendet, die für die Ausführung des Befehls erforderlich sind.

Ähnlich wie bei der Updatemethode können einfüge- und löschmethoden angegeben werden. Einfüge- und Löschmethoden verwenden nur einen Parameter des Entitätstyps, der aktualisiert werden soll. Beispielsweise können Methoden zum Einfügen und Löschen eines Product-instance wie folgt angegeben werden:

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

Entitätsklassen In-Depth

Verwenden von Attributen

Eine Entitätsklasse ähnelt jeder normalen Objektklasse, die Sie möglicherweise als Teil Ihrer Anwendung definieren, mit dem Unterschied, dass sie mit speziellen Informationen versehen ist, die sie einer bestimmten Datenbanktabelle zuordnen. Diese Anmerkungen werden als benutzerdefinierte Attribute für Ihre Klassendeklaration erstellt. Die Attribute sind nur dann sinnvoll, wenn Sie die -Klasse in Verbindung mit LINQ to SQL verwenden. Sie ähneln den XML-Serialisierungsattributen im .NET Framework. Diese "Daten"-Attribute bieten LINQ to SQL genügend Informationen, um Abfragen für Ihre Objekte in SQL-Abfragen für die Datenbank und Änderungen an Ihren Objekten in SQL-Befehle zum Einfügen, Aktualisieren und Löschen von SQL-Befehlen zu übersetzen.

Es ist auch möglich, die Zuordnungsinformationen mithilfe einer XML-Zuordnungsdatei anstelle von Attributen darzustellen. Dieses Szenario wird im Abschnitt Externe Zuordnung ausführlicher beschrieben.

Database-Attribut

Das Database-Attribut wird verwendet, um den Standardnamen der Datenbank anzugeben, wenn er nicht von der Verbindung bereitgestellt wird. Datenbankattribute können auf stark typisierte DataContext-Deklarationen angewendet werden. Dieses Attribut ist optional.

Database-Attribut

Eigenschaft type BESCHREIBUNG
Name String Gibt den Namen der Datenbank an. Die Informationen werden nur verwendet, wenn die Verbindung selbst den Datenbanknamen nicht angibt. Wenn dieses Database-Attribut in der Kontextdeklaration nicht vorhanden ist und nicht von der Verbindung angegeben wird, wird davon ausgegangen, dass die Datenbank denselben Namen wie die Kontextklasse hat.

C#

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

Visual Basic

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

Tabellenattribute

Das Table-Attribut wird verwendet, um eine Klasse als Entitätsklasse festzulegen, die einer Datenbanktabelle zugeordnet ist. Klassen mit dem Table-Attribut werden speziell von LINQ to SQL behandelt.

Tabellenattribute

Eigenschaft type BESCHREIBUNG
Name String Gibt den Namen der Tabelle an. Wenn diese Informationen nicht angegeben werden, wird davon ausgegangen, dass die Tabelle denselben Namen wie die Entitätsklasse hat.

C#

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

Visual Basic

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

Spaltenattribut

Das Column-Attribut wird verwendet, um ein Element einer Entitätsklasse anzugeben, das eine Spalte in einer Datenbanktabelle darstellt. Es kann auf jedes Feld oder jede Eigenschaft angewendet werden, ob öffentlich, privat oder intern. Nur elemente, die als Spalten identifiziert werden, werden beibehalten, wenn LINQ to SQL Änderungen an der Datenbank speichert.

Spaltenattribute

Eigenschaft type BESCHREIBUNG
Name String Der Name der Spalte in der Tabelle oder Sicht. Wenn nicht angegeben, wird davon ausgegangen, dass die Spalte denselben Namen wie der Klassenmember hat.
Storage String Der Name des zugrunde liegenden Speichers. Wenn angegeben, wird LINQ to SQL darüber informiert, wie der öffentliche Eigenschaftszugriffsor für den Datenmember umgangen und mit dem Rohwert selbst interagiert wird. Wenn nicht angegeben, ruft LINQ to SQL den Wert mithilfe des öffentlichen Accessors ab und legt den Wert fest.
Dbtype String Der Typ der Datenbankspalte, der mithilfe von Datenbanktypen und -modifizierern angegeben wird. Dies ist der genaue Text, der zum Definieren der Spalte in einem T-SQL-Tabellendeklarationsbefehl verwendet wird. Wenn nicht angegeben, wird der Datenbankspaltentyp vom Membertyp abgeleitet. Der spezifische Datenbanktyp ist nur erforderlich, wenn erwartet wird, dass die CreateDatabase()-Methode zum Erstellen einer instance der Datenbank verwendet wird.
IsPrimaryKey Bool Wenn dieser Wert auf true festgelegt ist, stellt das Klassenmember eine Spalte dar, die Teil des Primärschlüssels der Tabelle ist. Wenn mehr als ein Member der Klasse als ID festgelegt ist, wird der Primärschlüssel als zusammengesetzt aus den zugeordneten Spalten bezeichnet.
Isdbgenerated Boolean Gibt an, dass der Spaltenwert des Elements automatisch von der Datenbank generiert wird. Primärschlüssel, die als IsDbGenerated=true bezeichnet werden, sollten ebenfalls über einen DBType mit dem IDENTITY-Modifizierer verfügen. Isdbgenerated Member werden unmittelbar nach dem Einfügen der Datenzeile synchronisiert und stehen nach Abschluss von SubmitChanges() zur Verfügung.
Isversion Boolean Gibt den Spaltentyp des Elements als Datenbankzeitstempel oder Versionsnummer an. Versionsnummern werden erhöht, und Zeitstempelspalten werden von der Datenbank jedes Mal aktualisiert, wenn die zugeordnete Zeile aktualisiert wird. Member mit IsVersion=true werden sofort synchronisiert, nachdem die Datenzeile aktualisiert wurde. Die neuen Werte werden nach Abschluss von SubmitChanges() angezeigt.
UpdateCheck UpdateCheck Bestimmt, wie LINQ to SQL eine optimistische Parallelitätskonflikterkennung implementiert. Wenn kein Member als IsVersion=true festgelegt wird, erfolgt die Erkennung durch Vergleich der ursprünglichen Memberwerte mit dem aktuellen Datenbankstatus. Sie können steuern, welche Member LINQ to SQL während der Konflikterkennung verwenden, indem Sie jedem Member einen UpdateCheck-Enumerationswert zugeben.
  • Immer: Verwenden Sie diese Spalte immer für die Konflikterkennung.
  • Nie: Verwenden Sie diese Spalte niemals für die Konflikterkennung
  • WhenChanged: Verwenden Sie diese Spalte nur, wenn das Element von der Anwendung geändert wurde.
IsDiscriminator Boolean Bestimmt, ob das Klassenmember den Diskriminatorwert für eine Vererbungshierarchie enthält.
Ausdruck String Wirkt sich nicht auf den Vorgang von LINQ to SQL aus, sondern wird während verwendet.CreateDatabase() als unformatierter SQL-Ausdruck, der den berechneten Spaltenausdruck darstellt.
CanBeNull Boolean Gibt an, dass der Wert den NULL-Wert enthalten kann. Dies wird in der Regel vom CLR-Typ des Entitätselements abgeleitet. Verwenden Sie dieses Attribut, um anzugeben, dass ein Zeichenfolgenwert in der Datenbank als spalte ohne NULL-Wert dargestellt wird.
AutoSync AutoSync Gibt an, ob die Spalte automatisch anhand des Werts synchronisiert wird, der von der Datenbank bei Einfüge- oder Aktualisierungsbefehlen generiert wird. Gültige Werte für dieses Tag sind OnInsert, Always und Never.

Eine typische Entitätsklasse verwendet Column-Attribute für öffentliche Eigenschaften und speichert tatsächliche Werte in privaten Feldern.

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

Der DBType wird nur angegeben, damit die CreateDatabase() -Methode die Tabelle mit dem präzisesten Typ erstellen kann. Andernfalls wird das Wissen, dass die zugrunde liegende Spalte auf 15 Zeichen beschränkt ist, nicht verwendet.

Member, die den Primärschlüssel eines Datenbanktyps darstellen, werden häufig automatisch generierten Werten zugeordnet.

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

Wenn Sie den DBType angeben, stellen Sie sicher, dass Sie den IDENTITY-Modifizierer einschließen. LINQ to SQL erweitert keinen benutzerdefinierten angegebenen DBType. Wenn der DBType jedoch nicht angegeben ist, leitet LINQ to SQL ab, dass der IDENTITY-Modifizierer erforderlich ist, wenn die Datenbank über die CreateDatabase()-Methode erstellt wird.

Wenn die IsVersion-Eigenschaft true ist, muss der DBType die richtigen Modifizierer angeben, um eine Versionsnummer oder Zeitstempelspalte festzulegen. Wenn kein DBType angegeben ist, leitet LINQ to SQL die richtigen Modifizierer ab.

Sie können den Zugriff auf ein Element steuern, das einer automatisch generierten Spalte, einem Versionsstempel oder einer beliebigen Spalte zugeordnet ist, die Sie möglicherweise ausblenden möchten, indem Sie die Zugriffsebene des Members festlegen oder den Accessor selbst einschränken.

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

Die CustomerID-Eigenschaft des Auftrags kann schreibgeschützt werden, indem kein festgelegter Accessor definiert wird. LINQ to SQL können weiterhin den zugrunde liegenden Wert über das Speicherelement abrufen und festlegen.

Sie können ein Element auch für den Rest der Anwendung vollständig unzugänglich machen, indem Sie ein Column-Attribut auf einem privaten Member platzieren. Dadurch kann die Entitätsklasse Informationen enthalten, die für die Geschäftslogik der Klasse relevant sind, ohne sie im Allgemeinen verfügbar zu machen. Obwohl private Member Teil der übersetzten Daten sind, können Sie nicht in einer sprachintegrierten Abfrage darauf verweisen, da sie privat sind.

Standardmäßig werden alle Member verwendet, um die Erkennung optimistischer Parallelitätskonflikte durchzuführen. Sie können steuern, ob ein bestimmter Member verwendet wird, indem Sie dessen UpdateCheck-Wert angeben.

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

Die folgende Tabelle zeigt die zulässigen Zuordnungen zwischen Datenbanktypen und dem entsprechenden CLR-Typ. Verwenden Sie diese Tabelle als Leitfaden, wenn Sie bestimmen, welcher CLR-Typ verwendet werden soll, um eine bestimmte Datenbankspalte darzustellen.

Zulässige Zuordnungen des Datenbanktyps und des entsprechenden CLR-Typs

Datenbanktyp .NET CLR-Typ Kommentare
bit, tinyint, smallint, int, bigint Bye, Int16, Uint16, Int32, Uint32, Int64, Uint64 Verlustbehaftete Konvertierungen möglich. Werte dürfen keinen Roundtrip durchführen.
bit Boolean  
dezimal, numerisch, smallmoney, money Decimal Skalierungsunterschiede können zu einer verlustbehafteten Konvertierung führen. Darf keinen Roundtrip durchführen.
real, float Single, Double Genauigkeitsunterschiede.
char, varchar, text, nchar, nvarchar, ntext String Gebietsschemaunterschiede möglich.
datetime, smalldatetime Datetime Unterschiedliche Genauigkeit kann zu Verlustkonvertierungs- und Roundtripproblemen führen.
UNIQUEIDENTIFIER Guid Verschiedene Sortierungsregeln. Das Sortieren funktioniert möglicherweise nicht wie erwartet.
timestamp Byte[] (Byte() in Visual Basic), Binary Bytearray wird als Skalartyp behandelt. Der Benutzer ist für die Zuweisung eines ausreichenden Speichers verantwortlich, wenn der Konstruktor aufgerufen wird. Sie gilt als unveränderlich und wird nicht auf Änderungen nachverfolgt.
binary, varbinary Byte[] (Byte() in Visual Basic), Binary  

Zuordnungsattribut

Das Association-Attribut wird verwendet, um eine Eigenschaft zu bestimmen, die eine Datenbankzuordnung wie eine Fremdschlüssel-zu-Primärschlüssel-Beziehung darstellt.

Zuordnungsattribut

Eigenschaft type BESCHREIBUNG
Name String Der Name der Zuordnung. Dies entspricht häufig dem Fremdschlüsseleinschränkungsnamen der Datenbank. Es wird verwendet, wenn CreateDatabase() verwendet wird, um eine instance der Datenbank zu erstellen, um die relevante Einschränkung zu generieren. Es wird auch verwendet, um zwischen mehreren Beziehungen in einer einzelnen Entitätsklasse zu unterscheiden, die auf dieselbe Zielentitätsklasse verweist. In diesem Fall müssen Beziehungseigenschaften auf seiten der Beziehung (wenn beide definiert sind) denselben Namen aufweisen.
Storage String Der Name des zugrunde liegenden Speicherelements. Wenn angegeben, wird LINQ to SQL mitgeteilt, wie der Accessor für öffentliche Eigenschaften für den Datenmember umgangen und mit dem Rohwert selbst interagiert wird. Wenn nicht angegeben LINQ to SQL ruft den Wert mithilfe des öffentlichen Accessors ab und legt den Wert fest. Es wird empfohlen, dass alle Zuordnungsmitglieder Eigenschaften mit separaten Speichermitgliedern sind.
Thiskey String Eine durch Trennzeichen getrennte Liste von Namen eines oder mehrerer Member dieser Entitätsklasse, die die Schlüsselwerte auf dieser Seite der Zuordnung darstellen. Wenn nicht angegeben, wird angenommen, dass es sich bei den Membern um die Member handelt, aus denen der Primärschlüssel besteht.
OtherKey String Eine durch Trennzeichen getrennte Liste von Namen eines oder mehrerer Member der Zielentitätsklasse, die die Schlüsselwerte auf der anderen Seite der Zuordnung darstellen. Wenn nicht angegeben, wird davon ausgegangen, dass es sich bei den Membern um die Member handelt, die den Primärschlüssel der anderen Entitätsklasse bilden.
IsUnique Boolean True , wenn es eine Eindeutigkeitseinschränkung für den Fremdschlüssel gibt, die eine true 1:1-Beziehung angibt. Diese Eigenschaft wird selten verwendet, da 1:1-Beziehungen in der Datenbank kaum verwaltet werden können. In den meisten Fällen werden Entitätsmodelle mithilfe von 1:n-Beziehungen definiert, auch wenn sie von Anwendungsentwicklern als 1:1 behandelt werden.
IsForeignKey Boolean True , wenn der Zieltyp "other" der Zuordnung das übergeordnete Element des Quelltyps ist. Bei Beziehungen zwischen Fremdschlüsseln und Primärschlüsseln ist die Seite, die den Fremdschlüssel enthält, das untergeordnete Element, und die Seite, die den Primärschlüssel enthält, ist das übergeordnete Element.
Deleterule String Wird verwendet, um dieser Zuordnung Löschverhalten hinzuzufügen. Beispielsweise würde "CASCADE" der FK-Beziehung "ON DELETE CASCADE" hinzufügen. Wenn auf NULL festgelegt ist, wird kein Löschverhalten hinzugefügt.

Zuordnungseigenschaften stellen entweder einen einzelnen Verweis auf eine andere Entitätsklasse instance oder eine Auflistung von Verweisen dar. Singleton-Verweise müssen in der Entitätsklasse mithilfe des Werttyps EntityRef<T> (EntityRef (OfT) in Visual Basic codiert werden, um den tatsächlichen Verweis zu speichern. Der EntityRef-Typ ermöglicht LINQ to SQL das verzögerte Laden von Verweisen.

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

Die öffentliche Eigenschaft wird als Customer und nicht als EntityRef<Customer> eingegeben. Es ist wichtig, den EntityRef-Typ nicht als Teil der öffentlichen API verfügbar zu machen, da Verweise auf diesen Typ in einer Abfrage nicht in SQL übersetzt werden.

Ebenso muss eine Zuordnungseigenschaft, die eine Sammlung darstellt, den Auflistungstyp EntitySet<T> (EntitySet(OfT) in Visual Basic verwenden, um die Beziehung zu speichern.

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

Da es sich bei einem EntitySet<T> (EntitySet(OfT) in Visual Basic jedoch um eine Auflistung handelt, ist es gültig, das EntitySet als Rückgabetyp zu verwenden. Es ist auch gültig, den wahren Typ der Auflistung zu verschleiern, indem stattdessen die ICollection<T-Schnittstelle> (ICollection(OfT) in Visual Basic verwendet wird.

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

Stellen Sie sicher, dass Sie die Assign() -Methode für das EntitySet verwenden, wenn Sie einen öffentlichen Setter für die Eigenschaft verfügbar machen. Dadurch kann die Entitätsklasse weiterhin dieselbe Auflistung instance verwenden, da sie möglicherweise bereits an den Änderungsnachverfolgungsdienst gebunden ist.

ResultType-Attribut

Dieses Attribut gibt einen Elementtyp einer aufzählbaren Sequenz an, der von einer Funktion zurückgegeben werden kann, die deklariert wurde, um die IMultipleResults-Schnittstelle zurückzugeben. Dieses Attribut kann mehrmals angegeben werden.

ResultType-Attribut

Eigenschaft type BESCHREIBUNG
type Typ Typ der zurückgegebenen Ergebnisse.

StoredProcedure-Attribut

Das StoredProcedure-Attribut wird verwendet, um zu deklarieren, dass ein Aufruf einer Methode, die für den DataContext - oder Schema-Typ definiert ist, als Aufruf einer gespeicherten Datenbankprozedur übersetzt wird.

StoredProcedure-Attribut

Eigenschaft type BESCHREIBUNG
Name String Der Name der gespeicherten Prozedur in der Datenbank. Wenn nicht angegeben, wird davon ausgegangen, dass die gespeicherte Prozedur denselben Namen wie die Methode hat.

Funktionsattribute

Das Function-Attribut wird verwendet, um zu deklarieren, dass ein Aufruf einer Methode, die für einen DataContext oder Schema definiert ist, als Aufruf einer benutzerdefinierten skalaren oder tabellenwertbasierten Datenbankfunktion übersetzt wird.

Funktionsattribute

Eigenschaft type BESCHREIBUNG
Name String Der Name der Funktion in der Datenbank. Wenn nicht angegeben, wird davon ausgegangen, dass die Funktion denselben Namen wie die -Methode hat.

Parameterattribut

Das Parameter-Attribut wird verwendet, um eine Zuordnung zwischen einer Methode und den Parametern einer gespeicherten Datenbankprozedur oder benutzerdefinierten Funktion zu deklarieren.

Parameterattribute

Eigenschaft type BESCHREIBUNG
Name String Der Name des Parameters in der Datenbank. Wenn nicht angegeben, wird der Parameter aus dem Methodenparameternamen abgeleitet.
Dbtype String Der Typ des Parameters, der mithilfe von Datenbanktypen und Modifizierern angegeben wird.

InheritanceMapping-Attribut

Das InheritanceMapping-Attribut wird verwendet, um die Korrespondenz zwischen einem bestimmten Diskriminatorcode und einem Vererbungsuntertyp zu beschreiben. Alle InheritanceMapping-Attribute, die für eine Vererbungshierarchie verwendet werden, müssen für den Stammtyp der Hierarchie deklariert werden.

InheritanceMapping-Attribut

Propety type BESCHREIBUNG
Code Object Der Diskriminatorcodewert.
type Typ Der Untertyp Vererbung. Dies kann ein beliebiger nicht abstrakter Typ in der Vererbungshierarchie sein, einschließlich des Stammtyps.
IsDefault Boolean Bestimmt, ob der angegebene Vererbungsuntertyp der Standardtyp ist, der erstellt wird, wenn LINQ to SQL einen Diskriminatorcode findet, der nicht durch die InheritanceMapping-Attribute definiert ist. Genau eines der InheritanceMapping-Attribute muss mit IsDefault als true deklariert werden.

Graphkonsistenz

Ein Graph ist ein allgemeiner Begriff für eine Datenstruktur von Objekten, die alle durch Verweise aufeinander verweisen. Eine Hierarchie (oder Struktur) ist eine degenerate Form von Graph. Domänenspezifische Objektmodelle beschreiben häufig ein Netzwerk von Verweisen, die am besten als Graph von Objekten beschrieben werden. Die Integrität Ihres Objektdiagramms ist von entscheidender Bedeutung für die Stabilität Ihrer Anwendung. Aus diesem Grund ist es wichtig, sicherzustellen, dass Verweise innerhalb des Diagramms mit Ihren in der Datenbank definierten Geschäftsregeln und/oder -einschränkungen konsistent bleiben.

LINQ to SQL verwaltet nicht automatisch die Konsistenz von Beziehungsverweisen für Sie. Wenn Beziehungen bidirektional sind, sollte die andere Seite automatisch aktualisiert werden. Beachten Sie, dass es ungewöhnlich ist, dass sich normale Objekte so verhalten, sodass es unwahrscheinlich ist, dass Sie Ihre Objekte andernfalls auf diese Weise entworfen hätten.

LINQ to SQL bietet einige Mechanismen, um dies zu vereinfachen, und ein Muster, das Sie befolgen können, um sicherzustellen, dass Sie Ihre Verweise ordnungsgemäß verwalten. Entitätsklassen, die vom Codegenerierungstool generiert werden, implementieren automatisch die richtigen Muster.

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

Der EntitySet-T-Typ<> (EntitySet(OfT) in Visual Basic verfügt über einen Konstruktor, mit dem Sie zwei Delegaten bereitstellen können, die als Rückrufe verwendet werden können. Der erste, wenn der Auflistung ein Element hinzugefügt wird, der zweite, wenn es entfernt wird. Wie Sie im Beispiel sehen können, kann und sollte der Code, den Sie für diese Delegatten angeben, geschrieben werden, um die Eigenschaft für die umgekehrte Beziehung zu aktualisieren. So wird die Customer-Eigenschaft für eine Order-instance automatisch geändert, wenn eine Bestellung der Bestellsammlung eines Kunden hinzugefügt wird.

Das Implementieren der Beziehung am anderen Ende ist nicht so einfach. EntityRef<T> (EntityRef(OfT) in Visual Basic) ist ein Werttyp, der definiert ist, um so wenig zusätzlichen Mehraufwand wie möglich aus dem tatsächlichen Objektverweis zu enthalten. Es hat keinen Platz für ein Paar von Delegaten. Stattdessen sollte der Code, der die Graphkonsistenz von Singleton-Verweisen verwaltet, in die Eigenschaftenaccessoren selbst eingebettet werden.

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

Sehen Sie sich den Setter an. Wenn die Customer-Eigenschaft geändert wird, wird die Bestellung instance zuerst aus der Orders-Sammlung des aktuellen Kunden entfernt und dann erst später der Sammlung des neuen Kunden hinzugefügt. Beachten Sie, dass vor dem Aufruf von Remove() der tatsächliche Entitätsverweis auf NULL festgelegt ist. Dies erfolgt, um eine Rekursion beim Aufruf der Remove() -Methode zu vermeiden. Denken Sie daran, dass entitySet Rückrufdelegaten verwendet, um die Customer-Eigenschaft dieses Objekts NULL zuzuweisen. Dasselbe geschieht direkt vor dem Aufruf von Add(). Der tatsächliche Entitätsverweis wird auf den neuen Wert aktualisiert. Dies schränkt jede mögliche Rekursion wieder ein und erfüllt natürlich die Aufgabe des Setters überhaupt.

Die Definition einer 1:1-Beziehung ähnelt der Definition einer 1:n-Beziehung auf der Seite des Singleton-Verweises. Anstelle von Add() und Remove() wird ein neues Objekt zugewiesen, oder es wird ein NULL-Objekt zugewiesen, um die Beziehung zu severn.

Auch hier ist es wichtig, dass Beziehungseigenschaften die Konsistenz des Objektgraphen beibehalten. Wenn das In-Memory-Objektdiagramm mit den Datenbankdaten inkonsistent ist, wird beim Aufrufen der SubmitChanges-Methode eine Laufzeit-Ausnahme generiert. Erwägen Sie die Verwendung des Codegenerierungstools, um die Konsistenzarbeit für Sie zu gewährleisten.

Änderungsbenachrichtigungen

Ihre Objekte können am Änderungsnachverfolgungsprozess teilnehmen. Es ist nicht erforderlich, dass sie dies tun, aber sie können den Aufwand erheblich reduzieren, der erforderlich ist, um potenzielle Objektänderungen nachzuverfolgen. Es ist wahrscheinlich, dass Ihre Anwendung viel mehr Objekte aus Abfragen abruft, als am Ende geändert werden. Ohne proaktive Hilfe durch Ihre Objekte ist der Änderungsnachverfolgungsdienst in der Tatsächlichen Nachverfolgung von Änderungen eingeschränkt.

Da es keinen echten Abfangdienst in der Laufzeit gibt, findet die formale Nachverfolgung nicht tatsächlich statt. Stattdessen werden doppelte Kopien der Objekte gespeichert, wenn sie zum ersten Mal abgerufen werden. Später, wenn Sie SubmitChanges() aufrufen, werden diese Kopien verwendet, um mit denen zu vergleichen, die Sie erhalten haben. Wenn sich die Werte unterscheiden, wurde das Objekt geändert. Dies bedeutet, dass jedes Objekt zwei Kopien im Arbeitsspeicher benötigt, auch wenn Sie sie nie ändern.

Eine bessere Lösung besteht darin, die Objekte selbst an den Änderungsnachverfolgungsdienst zu melden, wenn sie tatsächlich geändert werden. Dies kann erreicht werden, indem das -Objekt eine Schnittstelle implementiert, die ein Rückrufereignis verfügbar macht. Der Änderungsnachverfolgungsdienst kann dann jedes Objekt verknüpfen und Benachrichtigungen empfangen, wenn sie sich ändern.

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

Um eine verbesserte Änderungsnachverfolgung zu unterstützen, müssen Ihre Entitätsklassen die INotifyPropertyChanging-Schnittstelle implementieren. Sie müssen nur ein Ereignis namens PropertyChanging definieren. Der Änderungsnachverfolgungsdienst registriert sich dann bei Ihrem Ereignis, wenn Ihre Objekte in den Besitz gelangen. Sie müssen dieses Ereignis nur unmittelbar auslösen, bevor Sie den Wert einer Eigenschaft ändern.

Vergessen Sie nicht, die gleiche Ereignisauslösungslogik auch in Ihren Beziehungseigenschaftensettern einzufügen. Lösen Sie für EntitySets die Ereignisse in den von Ihnen bereitgestellten Delegaten aus.

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

Vererbung

LINQ to SQL unterstützt die Zuordnung mit einer einzelnen Tabelle, wobei eine gesamte Vererbungshierarchie in einer einzelnen Datenbanktabelle gespeichert wird. Die Tabelle enthält die vereinfachte Vereinigung aller möglichen Datenspalten für die gesamte Hierarchie, und jede Zeile weist NULL-Werte in den Spalten auf, die nicht auf den Typ der durch die Zeile dargestellten instance anwendbar sind. Die Strategie der Zuordnung zu einer einzelnen Tabelle ist die einfachste Darstellung der Vererbung und bietet gute Leistungsmerkmale für viele verschiedene Abfragekategorien.

Zuordnung

Um diese Zuordnung mit LINQ to SQL zu implementieren, müssen Sie die folgenden Attribute und Attributeigenschaften für die Stammklasse der Vererbungshierarchie angeben:

  • Das Attribut [Table] (<Table> in Visual Basic).
  • Ein [InheritanceMapping]- Attribut (<InheritanceMapping> in Visual Basic) für jede Klasse in der Hierarchiestruktur. Für nicht abstrakte Klassen muss dieses Attribut eine Code-Eigenschaft (ein Wert, der in der Datenbanktabelle in der Spalte Vererbungsdiskriminator angezeigt wird, um anzugeben, zu welcher Klasse oder Unterklasse diese Datenzeile gehört) und eine Type-Eigenschaft (die angibt, welche Klasse oder Unterklasse der Schlüsselwert kennzeichnet).
  • Eine IsDefault-Eigenschaft für ein einzelnes [InheritanceMapping] -Attribut (<InheritanceMapping> in Visual Basic). Diese Eigenschaft dient zum Festlegen einer Fallbackzuordnung für den Fall, dass der Diskriminatorwert aus der Datenbanktabelle keinem der Codewerte in den Vererbungszuordnungen entspricht.
  • Eine IsDiscriminator-Eigenschaft für ein [Column] -Attribut (<Column> in Visual Basic), um anzugeben, dass dies die Spalte ist, die den Codewert für die Vererbungszuordnung enthält.

Für Unterklassen sind keine besonderen Attribute oder Eigenschaften erforderlich. Beachten Sie insbesondere, dass Unterklassen nicht über das Attribut [Table] (<Table> in Visual Basic) verfügen.

Im folgenden Beispiel werden die in den Unterklassen Car und Truck enthaltenen Daten der Einzeldatenbanktabelle Vehicle zugeordnet. (Um das Beispiel zu vereinfachen, verwendet der Beispielcode Felder anstelle von Eigenschaften für die Spaltenzuordnung.)

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

Das Klassendiagramm wird wie folgt angezeigt:

Abbildung 1. Fahrzeugklassendiagramm

Wenn Sie das resultierende Datenbankdiagramm in Server Explorer anzeigen, sehen Sie, dass alle Spalten einer einzelnen Tabelle zugeordnet wurden, wie hier gezeigt:

Abbildung 2. Spalten, die einer einzelnen Tabelle zugeordnet sind

Beachten Sie, dass für die Spaltentypen, die Felder in den Untertypen darstellen, NULL-Werte zulässig sein müssen, oder es muss eine Standardeinstellung angegeben sein. Dies ist erforderlich, damit die Einfügebefehle erfolgreich sind.

Abfragen

Der folgende Code bietet eine Variante der Verwendung abgeleiteter Typen in Ihren Abfragen:

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

Fortgeschrittene

Sie können eine Hierarchie weit über das bereits bereitgestellte einfache Beispiel hinaus erweitern.

Beispiel 1

Hier ist eine viel tiefere Hierarchie und eine komplexere Abfrage:

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)

Beispiel 2

Die folgende Hierarchie umfasst Schnittstellen:

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

Folgende Abfragen sind möglich:

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

Weiterführende Themen

Erstellen von Datenbanken

Da Entitätsklassen Über Attribute verfügen, die die Struktur der tabellen und Spalten der relationalen Datenbank beschreiben, ist es möglich, diese Informationen zum Erstellen neuer Instanzen Ihrer Datenbank zu verwenden. Sie können die CreateDatabase()-Methode für DataContext aufrufen, um LINQ to SQL eine neue Datenbank instance mit einer struktur erstellen zu lassen, die von Ihren Objekten definiert wird. Es gibt viele Gründe, warum Sie dies tun möchten: Sie können eine Anwendung erstellen, die sich automatisch auf einem Kundensystem installiert, oder eine Clientanwendung, die eine lokale Datenbank benötigt, um ihren Offlinezustand zu speichern. Für diese Szenarien ist CreateDatabase() ideal, insbesondere wenn ein bekannter Datenanbieter wie SQL Server Express 2005 verfügbar ist.

Die Datenattribute codieren jedoch möglicherweise nicht alles über eine vorhandene Datenbankstruktur. Der Inhalt von benutzerdefinierten Funktionen, gespeicherten Prozeduren, Triggern und Überprüfungseinschränkungen wird nicht durch die Attribute dargestellt. Die CreateDatabase()- Funktion erstellt nur ein Replikat der Datenbank unter Verwendung der informationen, die ihr bekannt sind, d. h. die Struktur der Datenbank und die Spaltentypen in jeder Tabelle. Für eine Vielzahl von Datenbanken ist dies jedoch ausreichend.

Im Folgenden finden Sie ein Beispiel dafür, wie Sie eine neue Datenbank mit dem Namen MyDVDs.mdf erstellen können:

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

Das Objektmodell kann wie folgt zum Erstellen einer Datenbank mit SQL Server Express 2005 verwendet werden:

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 stellt auch eine API zum Löschen einer vorhandenen Datenbank bereit, bevor eine neue Datenbank erstellt wird. Der obige Code für die Datenbankerstellung kann geändert werden, um zunächst mithilfe von DatabaseExists() auf eine vorhandene Version der Datenbank zu überprüfen und dann mithilfe von DeleteDatabase() zu löschen.

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

Nach dem Aufruf von CreateDatabase() kann die neue Datenbank Abfragen und Befehle wie SubmitChanges() akzeptieren, um der MDF-Datei Objekte hinzuzufügen.

Es ist auch möglich, CreateDatabase() mit einer anderen SKU als SQL Server Express zu verwenden, wobei entweder eine MDF-Datei oder nur ein Katalogname verwendet wird. Alles hängt davon ab, was Sie für Ihre Verbindungszeichenfolge verwenden. Die Informationen in der Verbindungszeichenfolge werden verwendet, um die datenbank zu definieren, die vorhanden sein wird, nicht unbedingt eine datenbank, die bereits vorhanden ist. LINQ to SQL fischt die relevanten Informationen aus und verwendet sie, um zu bestimmen, welche Datenbank erstellt werden soll und auf welchem Server sie erstellt werden soll. Dazu benötigen Sie natürlich Datenbankadministratorrechte oder gleichwertige Berechtigungen auf dem Server.

Interoperieren mit ADO.NET

LINQ to SQL gehört zur ADO.NET Technologiefamilie. Es basiert auf Diensten, die vom ADO.NET Anbietermodell bereitgestellt werden, sodass es möglich ist, LINQ to SQL Code mit vorhandenen ADO.NET-Anwendungen zu mischen.

Wenn Sie eine LINQ to SQL DataContext erstellen, können Sie ihn mit einer vorhandenen ADO.NET-Verbindung bereitstellen. Alle Vorgänge für den DataContext – einschließlich Abfragen – verwenden die von Ihnen bereitgestellte Verbindung. Wenn die Verbindung bereits geöffnet wurde, LINQ to SQL Ihre Autorität über die Verbindung honorieren und sie unverändert lassen, wenn sie fertig ist. Normalerweise schließt LINQ to SQL die Verbindung, sobald ein Vorgang abgeschlossen ist, es sei denn, eine Transaktion befindet sich im Bereich.

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

Sie können jederzeit über die Connection-Eigenschaft auf die von Ihrem DataContext verwendete Verbindung zugreifen und sie selbst schließen.

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

Sie können den DataContext auch mit Ihrer eigenen Datenbanktransaktion bereitstellen, falls Ihre Anwendung bereits eine initiiert hat und Sie möchten, dass dataContext mitspielt.

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

Wenn eine Transaktion festgelegt wird, wird sie vom DataContext verwendet, wenn er eine Abfrage ausgibt oder einen Befehl ausführt. Vergessen Sie nicht, die Eigenschaft wieder auf NULL zuzuweisen, wenn Sie fertig sind.

Die bevorzugte Methode für Transaktionen mit dem .NET Framework besteht jedoch darin, das TransactionScope-Objekt zu verwenden. Es ermöglicht Ihnen, verteilte Transaktionen durchzuführen, die über Datenbanken und andere speicherresidente Ressourcenmanager funktionieren. Die Idee besteht darin, dass Transaktionsbereiche billig beginnen und sich nur dann für verteilte Transaktionen vollständig bewerben, wenn sie tatsächlich auf mehrere Datenbanken oder mehrere Verbindungen innerhalb des Transaktionsbereichs verweisen.

C#

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

Visual Basic

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

Direktes Ausführen von SQL-Anweisungen

Verbindungen und Transaktionen sind nicht die einzige Möglichkeit, mit ADO.NET zu arbeiten. Möglicherweise stellen Sie fest, dass in einigen Fällen die Abfrage- oder Übermittlungsfunktion von DataContext für die spezialisierte Aufgabe, die Sie möglicherweise ausführen möchten, nicht ausreicht. Unter diesen Umständen ist es möglich, dataContext zu verwenden, um SQL-Raw-Befehle direkt an die Datenbank ausstellen zu können.

Mit der ExecuteQuery() -Methode können Sie eine unformatierte SQL-Abfrage ausführen und das Ergebnis Ihrer Abfrage direkt in Objekte konvertieren. Angenommen, die Daten für die Customer-Klasse sind auf die beiden Tabellen customer1 und customer2 verteilt, gibt die folgende Abfrage eine Sequenz von Customer-Objekten zurück.

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

Solange die Spaltennamen in den tabellarischen Ergebnissen mit den Spalteneigenschaften Ihrer Entitätsklasse übereinstimmen, werden LINQ to SQL Ihre Objekte aus jeder SQL-Abfrage materialisieren.

Die ExecuteQuery() -Methode lässt auch Parameter zu. Im folgenden Code wird eine parametrisierte Abfrage ausgeführt:

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

Die Parameter werden im Abfragetext mit derselben geschweiften Notation ausgedrückt, die von Console.WriteLine() und String.Format() verwendet wird. Tatsächlich wird String.Format() für die von Ihnen bereitgestellte Abfragezeichenfolge aufgerufen und ersetzt die geschweiften Parameter durch generierte Parameternamen wie p0, @p1 ..., p(n).

Konfliktlösung ändern

BESCHREIBUNG

Ein Änderungskonflikt tritt auf, wenn der Client versucht, Änderungen an einem Objekt zu übermitteln und mindestens ein In der Updateüberprüfung verwendeter Wert in der Datenbank aktualisiert wurde, seit der Client sie zuletzt gelesen hat.

Hinweis Nur Mitglieder, die als UpdateCheck.Always oder UpdateCheck.WhenChanged zugeordnet sind, nehmen an optimistischen Parallelitätsprüfungen teil. Für Mitglieder, die als UpdateCheck.Never gekennzeichnet sind, wird keine Überprüfung durchgeführt.

Die Lösung dieses Konflikts umfasst das Ermitteln, welche Elemente des Objekts sich in Konflikt befinden, und dann zu entscheiden, was dagegen zu tun ist. Beachten Sie, dass optimistische Parallelität in Ihrer jeweiligen Situation möglicherweise nicht die beste Strategie ist. Manchmal ist es durchaus sinnvoll, "das letzte Update gewinnen zu lassen".

Erkennen, Melden und Beheben von Konflikten in LINQ to SQL

Konfliktlösung ist der Vorgang, bei dem ein konfliktierendes Element aktualisiert wird, indem die Datenbank erneut abfragt und alle Unterschiede miteinander in Einklang stehen. Wenn ein Objekt aktualisiert wird, verfügt der Änderungsnachverfolgung über die alten ursprünglichen Werte und die neuen Datenbankwerte. LINQ to SQL bestimmt dann, ob das Objekt in Konflikt steht oder nicht. Wenn dies der Grund ist, bestimmt LINQ to SQL, welche Mitglieder beteiligt sind. Wenn sich der neue Datenbankwert für ein Element vom alten Original unterscheidet (das für die Aktualisierungsprüfung verwendet wurde, die nicht erfolgreich war), ist dies ein Konflikt. Alle Memberkonflikte werden einer Konfliktliste hinzugefügt.

Im folgenden Szenario beginnt Beispielsweise User1 mit der Vorbereitung eines Updates, indem die Datenbank nach einer Zeile abfragt. Bevor User1 die Änderungen übermitteln kann, hat User2 die Datenbank geändert. Die Übermittlung von User1 schlägt fehl, da sich die für Col B und Col C erwarteten Werte geändert haben.

Datenbankupdatekonflikt

Benutzer Col A Col B Col C
Ursprünglicher Zustand Alfreds Maria Sales
Benutzer 1 Alfred   Marketing
Benutzer 2   Mary Dienst

In LINQ to SQL verursachen Objekte, die aufgrund von Konflikten mit optimistischer Parallelität nicht aktualisiert werden können, eine Ausnahme (ChangeConflictException). Sie können angeben, ob die Ausnahme beim ersten Fehler ausgelöst werden soll oder ob alle Updates versucht werden sollen, bei denen Fehler angesammelt und in der Ausnahme gemeldet werden.

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

Wenn sie ausgelöst wird, bietet die Ausnahme Zugriff auf eine ObjectChangeConflict-Auflistung . Details sind für jeden Konflikt verfügbar (der einem einzelnen fehlgeschlagenen Updateversuch zugeordnet ist), einschließlich des Zugriffs auf die MemberConflicts-Liste . Jeder Memberkonflikt im Update wird einem Member zugewiesen, der die Parallelitätsprüfung nicht bestanden hat.

Konfliktbehandlung

Im vorherigen Szenario verfügt User1 über die unten beschriebenen RefreshMode-Optionen zum Abgleich der Unterschiede vor dem erneuten Übermitteln. In allen Fällen wird der Datensatz auf dem Client zuerst "aktualisiert", indem die aktualisierten Daten aus der Datenbank abgerufen werden. Diese Aktion stellt sicher, dass beim nächsten Updateversuch bei den gleichen Parallelitätsprüfungen kein Fehler auftritt.

Hier wählt User1 die Zusammenführung von Datenbankwerten mit den aktuellen Clientwerten aus, sodass die Datenbankwerte nur überschrieben werden, wenn der aktuelle Changeset diesen Wert ebenfalls geändert hat. (Siehe Beispiel 1 weiter unten in diesem Abschnitt.)

Im obigen Szenario sieht das Ergebnis nach der Konfliktlösung in der Datenbank wie folgt aus:

KeepChanges

  Col A Col B Col C
KeepChanges Alfred (Benutzer 1) Mary (Benutzer 2) Marketing (Benutzer 1)
  • Col A: Die Änderung von User1 (Alfred) wird angezeigt.
  • Col B: Die Änderung von User2 (Mary) wird angezeigt. Dieser Wert wurde zusammengeführt, da User1 ihn nicht geändert hat.
  • Col C: Die Änderung von User1 (Marketing) wird angezeigt. Die Änderung von User2 (Dienst) wird nicht zusammengeführt, da User1 dieses Element ebenfalls geändert hat.

Im Folgenden wählt User1 aus, alle Datenbankwerte mit den aktuellen Werten zu überschreiben. (Siehe Beispiel 2 weiter unten in diesem Abschnitt.)

Nach der Aktualisierung werden die Änderungen von User1 übermittelt. Das Ergebnis in der Datenbank lautet wie folgt:

KeepCurrentValues

  Col A Col B Col C
KeepCurrentValues Alfred (Benutzer 1) Maria (Original) Marketing (Benutzer 1)
  • Col A: Die Änderung von User1 (Alfred) wird angezeigt.
  • Col B: Die ursprüngliche Maria bleibt; Die Änderung von User2 wird verworfen.
  • Col C: Die Änderung von User1 (Marketing) wird angezeigt. Die Änderung von User2 (Dienst) wird verworfen.

Im nächsten Szenario wählt User1 aus, dass die Datenbankwerte die aktuellen Werte im Client überschreiben können. (Siehe Beispiel 3 weiter unten in diesem Abschnitt.)

Im obigen Szenario sieht das Ergebnis nach der Konfliktlösung in der Datenbank wie folgt aus:

ÜberschreibenCurrentValues

  Col A Col B Col C
ÜberschreibenCurrentValues Alfreds (Original) Mary (Benutzer 2) Dienst (Benutzer 2)
  • Col A: Der ursprüngliche Wert (Alfreds) bleibt; Der Wert von User1 (Alfred) wird verworfen.
  • Col B: Die Änderung von User2 (Mary) wird angezeigt.
  • Col C: Die Änderung von User2 (Dienst) wird angezeigt. Die Änderung von User1 (Marketing) wird verworfen.

Nachdem Konflikte behoben wurden, können Sie eine erneute Übermittlung versuchen. Da bei diesem zweiten Update möglicherweise auch ein Fehler auftritt, sollten Sie eine Schleife für Updateversuche verwenden.

Beispiele

Die folgenden Codeausschnitte zeigen verschiedene Informationsmember und -techniken, die Ihnen zur Ermittlung und Lösung von Memberkonflikten zur Verfügung stehen.

Beispiel 1

In diesem Beispiel werden Konflikte "automatisch" aufgelöst. Das heißt, Datenbankwerte werden mit den aktuellen Clientwerten zusammengeführt, es sei denn, der Client hat auch diesen Wert geändert (KeepChanges). Es erfolgt keine Überprüfung oder benutzerdefinierte Behandlung einzelner Memberkonflikte.

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)

Beispiel 2

In diesem Beispiel werden Konflikte ohne benutzerdefinierte Behandlung erneut aufgelöst. Dieses Mal werden Datenbankwerte jedoch nicht mit aktuellen Clientwerten zusammengeführt.

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

Beispiel 3

Auch hier findet keine benutzerdefinierte Behandlung statt. In diesem Fall werden jedoch alle Clientwerte mit den aktuellen Datenbankwerten aktualisiert.

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

Beispiel 4

Dieses Beispiel zeigt eine Möglichkeit, auf Informationen zu einer Entität zuzugreifen, die sich in Konflikt befindet.

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

Beispiel 5

In diesem Beispiel wird eine Schleife durch die einzelnen Member hinzugefügt. Hier können Sie eine benutzerdefinierte Behandlung für jedes Element bereitstellen.

Hinweis Hinzufügen mithilfe von System.Reflection; , um MemberInfo bereitzustellen.

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

Aufruf gespeicherter Prozeduren

LINQ to SQL unterstützt gespeicherte Prozeduren und benutzerdefinierte Funktionen. LINQ to SQL ordnet diese datenbankdefinierten Abstraktionen codegenerierten Clientobjekten zu, sodass Sie über Clientcode stark typisiert darauf zugreifen können. Sie können diese Methoden problemlos mithilfe von IntelliSense ermitteln, und die Methodensignaturen ähneln so ähnlich wie möglich den Signaturen der in der Datenbank definierten Prozeduren und Funktionen. Ein Resultset, das durch einen Aufruf einer zugeordneten Prozedur zurückgegeben wird, ist eine stark typisierte Auflistung. LINQ to SQL können die zugeordneten Methoden automatisch generieren, unterstützen aber auch die manuelle Zuordnung in Situationen, in denen Sie die Codegenerierung nicht verwenden möchten.

LINQ to SQL ordnet gespeicherte Prozeduren und Funktionen Methoden mithilfe von Attributen zu. Die Attribute StoredProcedure, Parameter und Function unterstützen alle eine Name-Eigenschaft , und das Parameter-Attribut unterstützt auch eine DBType-Eigenschaft . Zwei Beispiele:

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

Die folgenden Beispiele zeigen Zuordnungen für verschiedene Arten von gespeicherten Prozeduren.

Beispiel 1

Die folgende gespeicherte Prozedur verwendet einen einzelnen Eingabeparameter und gibt eine ganze Zahl zurück:

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

Die zugeordnete Methode würde wie folgt aussehen:

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

Beispiel 2

Wenn eine gespeicherte Prozedur mehrere Ergebnisformen zurückgeben kann, lässt sich der Rückgabetyp nicht auf eine einzige Projektionsform festlegen. Im folgenden Beispiel hängt das Ergebnis-Shape von der Eingabe ab:

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

Die zugeordnete Methode lautet wie folgt:

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

Sie können diese gespeicherte Prozedur wie folgt verwenden:

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 
         
      }           

Hier müssen Sie das GetResult-Muster verwenden, um basierend auf Ihren Kenntnissen über die gespeicherte Prozedur einen Enumerator des richtigen Typs abzurufen. LINQ to SQL kann alle möglichen Projektionstypen generieren, weiß aber nicht, in welcher Reihenfolge sie zurückgegeben werden. Sie können nur wissen, welche generierten Projektionstypen einer zugeordneten Methode entsprechen, indem Sie generierte Codekommentare für die Methoden verwenden.

Beispiel 3

Hier sehen Sie die T-SQL einer gespeicherten Prozedur, die mehrere Ergebnisformen sequenziell zurückgibt:

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

LINQ to SQL würde dieses Verfahren genau wie in Beispiel 2 oben zuordnen. In diesem Fall gibt es jedoch zwei sequenzielle Resultsets.

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

Sie können diese gespeicherte Prozedur wie folgt verwenden:

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

Beispiel 4

LINQ to SQL ordnet out Parametern Verweisparameter zu (ref Schlüsselwort (keyword)), und für Werttypen deklariert der Parameter als NULLable (z. B. int?). Die Prozedur im folgenden Beispiel verwendet einen einzelnen Eingabeparameter und gibt einen out Parameter zurück.

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

Die zugeordnete Methode lautet wie folgt:

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

In diesem Fall verfügt die Methode nicht über einen expliziten Rückgabewert, aber der Standardrückgabewert wird trotzdem zugeordnet. Für den Ausgabeparameter wird wie erwartet ein entsprechender Ausgabeparameter verwendet.

Sie würden die oben gespeicherte Prozedur wie folgt aufrufen:

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)

Benutzerdefinierte Funktionen

LINQ to SQL unterstützt sowohl Skalar- als auch Tabellenwertfunktionen und die Inline-Entsprechung beider.

LINQ to SQL verarbeitet inline skalare Aufrufe ähnlich wie systemdefinierte Funktionen aufgerufen werden. Betrachten Sie die folgende Abfrage:

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)

Hier wird der Methodenaufruf Math.Floor in einen Aufruf der Systemfunktion "FLOOR" übersetzt. Auf die gleiche Weise wird ein Aufruf einer Funktion, die einer UDF zugeordnet ist, in einen Aufruf der UDF in SQL übersetzt.

Beispiel 1

Hier ist eine skalare benutzerdefinierte Funktion (UDF) ReverseCustName(). In SQL Server kann die Funktion wie folgt definiert werden:

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

Sie können dieser UDF mithilfe des folgenden Codes eine Clientmethode zuordnen, die für eine Schemaklasse definiert ist. Beachten Sie, dass der Text der -Methode einen Ausdruck erstellt, der die Absicht des Methodenaufrufs erfasst und diesen Ausdruck zur Übersetzung und Ausführung an den DataContext übergibt. (Diese direkte Ausführung erfolgt nur, wenn die Funktion aufgerufen wird.)

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)

Beispiel 2

In der folgenden Abfrage wird ein Inlineaufruf der generierten UDF-Methode ReverseCustName angezeigt. In diesem Fall wird die Funktion nicht sofort ausgeführt. Die für diese Abfrage erstellte SQL-Instanz wird in einen Aufruf der in der Datenbank definierten UDF übersetzt (siehe SQL-Code nach der Abfrage).

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]

Wenn Sie dieselbe Funktion außerhalb einer Abfrage aufrufen, erstellt LINQ to SQL eine einfache Abfrage aus dem Methodenaufrufausdruck mit der folgenden SQL-Syntax (wobei der Parameter @p0 an die übergebene Konstante gebunden ist):

In LINQ to SQL:

C#

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

Visual Basic

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

Konvertiert in:

SELECT dbo.ReverseCustName(@p0)

Beispiel 3

Eine Tabellenwertfunktion (Table-Valued Function, TVF) gibt ein einzelnes Resultset zurück (im Gegensatz zu gespeicherten Prozeduren, die mehrere Ergebnisformen zurückgeben können). Da der TVF-Rückgabetyp eine Tabelle ist, können Sie eine TVF überall in SQL verwenden, die Sie eine Tabelle verwenden können, und Sie können die TVF auf die gleiche Weise wie eine Tabelle behandeln.

Betrachten Sie die folgende SQL Server Definition einer Tabellenwertfunktion:

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

Diese Funktion gibt explizit an, dass sie eine TABLE zurückgibt, sodass die zurückgegebene Resultsetstruktur implizit definiert ist. LINQ to SQL ordnet die Funktion wie folgt zu:

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)

Der folgende SQL-Code zeigt, dass Sie die von der Funktion zurückgegebene Tabelle verknüpfen und sie andernfalls wie jede andere Tabelle behandeln können:

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

In LINQ to SQL wird die Abfrage wie folgt gerendert (unter Verwendung der neuen Joinsyntax):

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 Einschränkungen für gespeicherte Prozeduren

LINQ to SQL unterstützt die Codegenerierung für gespeicherte Prozeduren, die statisch bestimmte Resultsets zurückgeben. Daher unterstützt der LINQ to SQL Codegenerator Folgendes nicht:

  • Gespeicherte Prozeduren, die dynamische SQL verwenden, um Resultsets zurückzugeben. Wenn eine gespeicherte Prozedur bedingte Logik zum Erstellen einer dynamischen SQL-Anweisung enthält, können LINQ to SQL keine Metadaten für das Resultset abrufen, da die zum Generieren des Resultsets verwendete Abfrage bis zur Laufzeit unbekannt ist.
  • Gespeicherte Prozeduren, die Ergebnisse basierend auf einer temporären Tabelle erzeugen.

Das Tool zum Generator für Entitätsklassen

Wenn Sie über eine vorhandene Datenbank verfügen, ist es unnötig, ein vollständiges Objektmodell von Hand zu erstellen, nur um es darzustellen. Die LINQ to SQL-Distribution enthält ein Tool namens SQLMetal. Es handelt sich um ein Befehlszeilenhilfsprogramm, das die Erstellung von Entitätsklassen automatisiert, indem die entsprechenden Klassen aus den Datenbankmetadaten abgeleitet werden.

Sie können SQLMetal verwenden, um SQL-Metadaten aus einer Datenbank zu extrahieren und eine Quelldatei mit Entitätsklassendeklarationen zu generieren. Alternativ können Sie den Prozess in zwei Schritte aufteilen, indem Sie zunächst eine XML-Datei generieren, die die SQL-Metadaten darstellt, und später diese XML-Datei in eine Quelldatei mit Klassendeklarationen übersetzen. Dieser Splitprozess ermöglicht es Ihnen, die Metadaten als Datei beizubehalten, sodass Sie sie bearbeiten können. Beim Extraktionsprozess, der die Datei erzeugt, werden einige Rückschlüsse auf die entsprechenden Klassen- und Eigenschaftsnamen unter Berücksichtigung der Tabellen- und Spaltennamen der Datenbank vorgenommen. Möglicherweise ist es notwendig, die XML-Datei zu bearbeiten, damit der Generator ansprechendere Ergebnisse erzeugt oder Aspekte der Datenbank ausblenden kann, die in Ihren Objekten nicht vorhanden sein sollen.

Das einfachste Szenario für die Verwendung von SQLMetal besteht darin, Klassen direkt aus einer vorhandenen Datenbank zu generieren. So rufen Sie das Tool auf:

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

Beim Ausführen des Tools wird eine Northwind.cs- oder VB-Datei erstellt, die das Durch Lesen der Datenbankmetadaten generierte Objektmodell enthält. Diese Verwendung funktioniert gut, wenn die Namen der Tabellen in der Datenbank den Namen der Objekte ähneln, die Sie generieren möchten. Andernfalls sollten Sie den zweistufigen Ansatz verfolgen.

Verwenden Sie das Tool wie folgt, um SQLMetal anzuweisen, eine DBML-Datei zu generieren:

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

Sobald die dbml-Datei generiert wurde, können Sie sie mit einer Klasse und einem Eigenschaftsattribut kommentieren, um zu beschreiben, wie Tabellen und Spalten Klassen und Eigenschaften zugeordnet werden. Nachdem Sie die Anmerkungen für die dbml-Datei abgeschlossen haben, können Sie Ihr Objektmodell generieren, indem Sie den folgenden Befehl ausführen:

C#

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

Visual Basic

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

Die SQLMetal-Verwendungssignatur lautet wie folgt:

SqlMetal [options] [filename]

Die folgende Tabelle zeigt die verfügbaren Befehlszeilenoptionen für SQLMetal.

Befehlszeilenoptionen für SQLMetal

Option BESCHREIBUNG
/server:<name> Gibt den Server an, mit dem eine Verbindung hergestellt werden soll, um auf die Datenbank zuzugreifen.
/database:<name> Gibt den Namen der Datenbank an, aus der Metadaten gelesen werden sollen.
/user:<name> Anmeldebenutzer-ID für den Server.
/password:<name> Anmeldekennwort für den Server.
/views Extrahieren Sie Datenbanksichten.
/functions Extrahieren Sie Datenbankfunktionen.
/sprocs Extrahieren Sie gespeicherte Prozeduren.
/code[:<dateiname>] Gibt an, dass die Ausgabe des Tools eine Quelldatei mit Entitätsklassendeklarationen ist.
/language:<language> Verwenden Sie Visual Basic oder C# (Standard).
/xml[:<Dateiname>] Gibt an, dass die Ausgabe der Tools eine DBML-Datei ist, die die Datenbankmetadaten und die erste Schätzung der Klassen- und Eigenschaftsnamen beschreibt.
/map[:<dateiname>] Gibt an, dass anstelle von Attributen eine externe Zuordnungsdatei verwendet werden soll.
/pluralize Gibt an, dass das Tool die Pluralisierung/Depluralisierung der Heuristik in englischer Sprache für die Namen der Tabellen ausführen soll, um die entsprechenden Klassen- und Eigenschaftsnamen zu erzeugen.
/namespace:<name> Gibt den Namespace an, in dem die Entitätsklassen generiert werden.
/timeout:<Sekunden> Timeoutwert in Sekunden, der für Datenbankbefehle verwendet werden soll.

Hinweis Um die Metadaten aus einer MDF-Datei zu extrahieren, müssen Sie den MDF-Dateinamen nach allen anderen Optionen angeben. Wenn kein /server angegeben ist , wird localhost angenommen.

DBML-Referenz zum Generatortool

Die DBML-Datei (Database Mapping Language) ist in erster Linie eine Beschreibung der SQL-Metadaten für eine bestimmte Datenbank. Es wird von SQLMetal extrahiert, indem die Datenbankmetadaten betrachtet werden. Dieselbe Datei wird auch von SQLMetal verwendet, um ein Standardobjektmodell zur Darstellung der Datenbank zu generieren.

Hier sehen Sie ein prototypisches Beispiel für die DBML-Syntax:

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

Die Elemente und ihre Attribute werden wie folgt beschrieben.

Datenbank

Dies ist das äußerste Element im XML-Format. Dieses Element wird dem Database-Attribut für den generierten DataContext lose zugeordnet.

Datenbankattribute

attribute type Standard BESCHREIBUNG
Name String None Der Name der Datenbank. Wenn vorhanden und beim Generieren eines DataContexts ein Database-Attribut mit diesem Namen angefügt wird. Wird auch als Name der DataContext-Klasse verwendet, wenn das Klassenattribute nicht vorhanden ist.
EntityNamespace STARK (Strong) None Standardnamespace für Klassen, die aus Type-Elementen in Tabellenelementen generiert werden. Wenn hier kein Namespace angegeben ist, werden Entitätsklassen im Stammnamespace generiert.
ContextNamespace String None Standardnamespace für die generierte DataContext-Klasse . Wenn hier kein Namespace angegeben ist, wird die DataContext-Klasse im Stammnamespace generiert.
Klasse String Database.Name Der Name der generierten DataContext-Klasse . Wenn keine vorhanden ist, verwenden Sie das Name-Attribut des Database-Elements.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene der generierten DataContext-Klasse . Gültige Werte sind Public, Protected, Internal und Private.
BaseType String "System.Data.Linq.DataContext" Der Basistyp der DataContext-Klasse .
Anbieter String "System.Data.Linq.SqlClient.Sql2005Provider" Der Anbieter des DataContext, verwenden Sie den Sql2005-Anbieter als Standard
ExternalMapping Boolean False Geben Sie an, ob der DBML zum Generieren einer externen Zuordnungsdatei verwendet wird.
Serialisierung SerializationMode SerializationMode.None Geben Sie an, ob die generierten DataContext - und Entitätsklassen serialisierbar sind.

DatenbankSub-Element attribute

Sub-Element Elementtyp Vorkommensbereich BESCHREIBUNG
<Table> Tabelle 0-ungebunden Stellt eine SQL Server Tabelle oder Sicht dar, die entweder einem einzelnen Typ oder einer Vererbungshierarchie zugeordnet wird.
<Function> Funktion 0-ungebunden Stellt eine SQL Server gespeicherte Prozedur oder eine db-Funktion dar, die einer Methode in der generierten DataContext-Klasse zugeordnet wird.
<Connection> Verbindung 0-1 Stellt die Datenbankverbindung dar, die von DataContext verwendet wird.

Tabelle

Dieses Element stellt eine Datenbanktabelle (oder eine Sicht) dar, die entweder einem einzelnen Typ oder einer Vererbungshierarchie zugeordnet wird. Dieses Element wird dem Table-Attribut für die generierte Entitätsklasse lose zugeordnet.

Tabellenattribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name der Tabelle in der Datenbank. Dient bei Bedarf als Basis des Standardnamens für den Tabellenadapter.
Member String Table.Name Der Name des Memberfelds, das für diese Tabelle innerhalb der DataContext-Klasse generiert wurde.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene des Tabellen-T-Verweises<> im DataContext. Gültige Werte sind Public, Protected, Internal und Private.

Tabellenattribute Sub-Element

Sub-Element Elementtyp Vorkommensbereich BESCHREIBUNG
<Typ> type 1-1 Stellt den Typ oder die Vererbungshierarchie dar, die dieser Tabelle zugeordnet ist.
<InsertFunction> TableFunction 0-1 Die Methode zum Einfügen. Wenn sie vorhanden ist, wird eine InsertT-Methode generiert.
<UpdateFunction> TableFunction 0-1 Die Methode zum Aktualisieren. Wenn sie vorhanden ist, wird eine UpdateT-Methode generiert.
<DeleteFunction> TableFunction 0-1 Die Methode zum Löschen. Wenn sie vorhanden ist, wird eine DeleteT-Methode generiert.

type

Dieses Element stellt eine Typdefinition für ein Tabellen- oder ein gespeichertes Prozedurergebnis-Shape dar. Dadurch wird ein Code für einen neuen CLR-Typ mit den angegebenen Spalten und Zuordnungen verwendet.

Typ kann auch eine Komponente einer Vererbungshierarchie darstellen, wobei mehrere Typen derselben Tabelle zugeordnet sind. In diesem Fall werden die Type-Elemente geschachtelt, um die Vererbungsbeziehungen zwischen übergeordnetem und untergeordnetem Element darzustellen, und in der Datenbank durch den angegebenen InheritanceCode unterschieden.

Typattribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name des zu generierenden CLR-Typs.
InheritanceCode String Keine Wenn dieser Typ an der Vererbung beteiligt ist, kann er über einen zugeordneten Vererbungscode verfügen, um beim Laden von Zeilen aus der Tabelle zwischen CLR-Typen zu unterscheiden. Der Typ , dessen InheritanceCode mit dem Wert der IsDiscriminator-Spalte übereinstimmt, wird verwendet, um das geladene Objekt zu instanziieren. Wenn der Vererbungscode nicht vorhanden ist, ist die generierte Entitätsklasse abstrakt.
IsInheritanceDefault Boolean False Wenn dies für einen Typ in einer Vererbungshierarchie zutrifft, wird dieser Typ beim Laden von Zeilen verwendet, die nicht mit definierten Vererbungscodes übereinstimmen.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene des zu erstellenden CLR-Typs. Gültige Werte sind: Public, Protected, Internal und Private.
Id String Keine Ein Typ kann eine eindeutige ID aufweisen. Die ID eines Typs kann von anderen Tabellen oder Funktionen verwendet werden. Die ID wird nur in der DBML-Datei und nicht im Objektmodell angezeigt.
Idref String Keine IdRef wird verwendet, um auf die ID eines anderen Typs zu verweisen. Wenn IdRef in einem type-Element vorhanden ist, darf das type-Element nur die IdRef-Informationen enthalten. IdRef wird nur in der DBML-Datei und nicht im Objektmodell angezeigt.

Typ Sub-Element Attribute

Sub-Element Elementtyp Vorkommensbereich BESCHREIBUNG
<Spalte> Column 0-ungebunden Stellt eine Eigenschaft innerhalb dieses Typs dar, die an ein Feld in der Tabelle dieses Typs gebunden wird.
<Zuordnung> Zuordnung 0-ungebunden Stellt eine Eigenschaft innerhalb dieses Typs dar, die an ein Ende einer Fremdschlüsselbeziehung zwischen Tabellen gebunden wird.
<Typ> SubType 0-ungebunden Stellt Untertypen dieses Typs innerhalb einer Vererbungshierarchie dar.

SubType

Dieses Element stellt einen abgeleiteten Typ in einer Vererbungshierarchie dar. Dies wird in einem neuen CLR-Typ mit den in diesem Typ angegebenen Spalten und Zuordnungen generiert. Für Untertypen werden keine Vererbungsattribute generiert.

Im Vergleich zu Type verfügen SubType-Elemente nicht über AccessModifier , da alle abgeleiteten Typen öffentlich sein müssen. SubTypes können nicht von anderen Tabellen und Funktionen wiederverwendet werden, sodass keine ID und IdRef enthalten sind.

SubType-Attribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name des zu generierenden CLR-Typs.
InheritanceCode String None Wenn dieser Typ an der Vererbung beteiligt ist, kann er über einen zugeordneten Vererbungscode verfügen, um beim Laden von Zeilen aus der Tabelle zwischen CLR-Typen zu unterscheiden. Der Typ, dessen InheritanceCode mit dem Wert der IsDiscriminator-Spalte übereinstimmt, wird verwendet, um das geladene Objekt zu instanziieren. Wenn der Vererbungscode nicht vorhanden ist, ist die generierte Entitätsklasse abstrakt.
IsInheritanceDefault Boolean False Wenn dies für einen Typ in einer Vererbungshierarchie zutrifft, wird dieser Typ beim Laden von Zeilen verwendet, die nicht mit definierten Vererbungscodes übereinstimmen.

SubType-Sub-Element-Attribute

Sub-Element Elementtyp Vorkommensbereich BESCHREIBUNG
<Spalte> Column 0-ungebunden Stellt eine Eigenschaft innerhalb dieses Typs dar, die an ein Feld in der Tabelle dieses Typs gebunden wird.
<Zuordnung> Zuordnung 0-ungebunden Stellt eine Eigenschaft innerhalb dieses Typs dar, an die an einem Ende einer Fremdschlüsselbeziehung zwischen Tabellen gebunden wird.
<Typ> SubType 0-ungebunden Stellt Untertypen dieses Typs innerhalb einer Vererbungshierarchie dar.

Column

Dieses Element stellt eine Spalte in einer Tabelle dar, die einer Eigenschaft (und einem Sicherungsfeld) innerhalb einer Klasse zugeordnet ist. Für beide Enden einer Fremdschlüsselbeziehung ist jedoch kein Column-Element vorhanden, da dies vollständig (an beiden Enden) durch Zuordnungselemente dargestellt wird.

Spaltenattribute

Attributes type Standard BESCHREIBUNG
Name String None Der Name des Datenbankfelds, dem diese Spalte zugeordnet wird.
Member String Name Der Name der CLR-Eigenschaft, die für den enthaltenden Typ generiert werden soll.
Storage String _Mitglied Der Name des privaten CLR-Sicherungsfelds, in dem der Wert dieser Spalte gespeichert wird. Entfernen Sie Storage beim Serialisieren nicht, auch wenn es sich um die Standardeinstellung handelt.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene der zu erstellenden CLR-Eigenschaft. Gültige Werte sind: Public, Protected, Internal und Private.
type String (erforderlich) Der Name des Typs der zu erstellenden CLR-Eigenschaft und des Sicherungsfelds. Dies kann von einem vollqualifizierten Namen bis hin zum direkten Namen einer Klasse sein, solange sich der Name letztendlich im Bereich befindet, wenn der generierte Code kompiliert wird.
DbType String None Vollständiger SQL Server Typ (einschließlich Anmerkung wie NOT NULL) für diese Spalte. Wird von LINQ to SQL verwendet, wenn Sie sie bereitstellen, um die generierten Abfragen zu optimieren und bei CreateDatabase() spezifischer zu sein. Serialisieren Sie DbType immer.
IsReadOnly Boolean False Wenn IsReadOnly festgelegt ist, wird kein Eigenschaftensetzer erstellt, was bedeutet, dass Personen den Wert dieser Spalte nicht mit diesem Objekt ändern können.
IsPrimaryKey Boolean False Gibt an, dass diese Spalte am Primärschlüssel der Tabelle beteiligt ist. Diese Informationen sind erforderlich, damit LINQ to SQL ordnungsgemäß funktioniert.
Isdbgenerated Boolean False Gibt an, dass die Daten dieses Felds von der Datenbank generiert werden. Dies ist in erster Linie bei AutoNumber-Feldern und bei berechneten Feldern der Fall. Es ist nicht sinnvoll, diesen Feldern Werte zuzuweisen, und daher sind sie automatisch IsReadOnly.
CanBeNull Boolean None Gibt an, dass der Wert den NULL-Wert enthalten kann. Wenn Sie tatsächlich NULL-Werte in der CLR verwenden möchten, müssen Sie den ClrType weiterhin als Nullable<T> angeben.
UpdateCheck UpdateCheck Immer (es sei denn, mindestens ein anderes Element hat IsVersion festgelegt, dann Nie) Gibt an, ob LINQ to SQL diese Spalte während der Erkennung optimistischer Parallelitätskonflikte verwenden soll. Normalerweise nehmen alle Spalten standardmäßig teil, es sei denn, es gibt eine IsVersion-Spalte , die dann selbst teilnimmt. Kann sein: Always, Never oder WhenChanged (d. h. die Spalte nimmt teil, wenn sich ihr eigener Wert geändert hat).
IsDiscriminator Boolean False Gibt an, ob dieses Feld den Diskriminatorcode enthält, der für die Auswahl zwischen Typen in einer Vererbungshierarchie verwendet wird.
Ausdruck String None Wirkt sich nicht auf den Vorgang von LINQ to SQL aus, sondern wird während verwendet.CreateDatabase() als unformatierter SQL-Ausdruck, der den berechneten Spaltenausdruck darstellt.
Isversion Boolean False Gibt an, dass dieses Feld ein TIMESTAMP-Feld in SQL Server darstellt, das bei jeder Änderung der Zeile automatisch aktualisiert wird. Dieses Feld kann dann verwendet werden, um eine effizientere Erkennung optimistischer Parallelitätskonflikte zu ermöglichen.
IsDelayLoaded Boolean False Gibt an, dass diese Spalte nicht sofort bei der Objektmaterialisierung geladen werden soll, sondern nur, wenn zuerst auf die relevante Eigenschaft zugegriffen wird. Dies ist nützlich für große Memofelder oder binäre Daten in einer Zeile, die nicht immer benötigt wird.
AutoSync AutoSync If (IsDbGenerated && IsPrimaryKey) OnInsert;

Else if (IsDbGenerated) Always

Sonst nie

Gibt an, ob die Spalte automatisch mit dem von der Datenbank generierten Wert synchronisiert wird. Gültige Werte für dieses Tag sind: OnInsert, Always und Never.

Zuordnung

Dieses Element stellt das Ende einer Fremdschlüsselbeziehung dar. Bei 1:n-Beziehungen ist dies ein EntitySet<T> auf der einen Seite und ein EntityRef<T> auf der Seite mit vielen. Bei 1:1-Beziehungen ist dies ein EntityRef<T> auf beiden Seiten.

Beachten Sie, dass es nicht erforderlich ist, einen Association-Eintrag auf beiden Seiten einer Vereinigung zu haben. In diesem Fall wird eine Eigenschaft nur auf der Seite generiert, die den Eintrag enthält (die eine unidirektionale Beziehung bildet).

Zuordnungsattribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name der Beziehung (normalerweise der Name der Fremdschlüsseleinschränkung). Dies kann technisch optional sein, sollte aber immer durch Code generiert werden, um Mehrdeutigkeiten zu vermeiden, wenn mehrere Beziehungen zwischen denselben beiden Tabellen bestehen.
Member String Name Der Name der CLR-Eigenschaft, die auf dieser Seite der Zuordnung generiert werden soll.
Storage String Wenn OneToMany und Not IsForeignKey:

_OtherTable

Oder:

_TypeName(OtherTable)

Der Name des privaten CLR-Sicherungsfelds, in dem der Wert dieser Spalte gespeichert wird.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene der zu erstellenden CLR-Eigenschaft. Gültige Werte sind Public, Protected, Internal und Private.
Thiskey String Die IsIdentity-Eigenschaft innerhalb der enthaltenden Klasse Eine durch Kommas getrennte Liste der Schlüssel auf dieser Seite der Zuordnung.
OtherTable String Siehe Beschreibung. Die Tabelle am anderen Ende der Beziehung. Normalerweise kann dies durch die LINQ to SQL Laufzeit durch übereinstimmende Beziehungsnamen bestimmt werden, aber dies ist für unidirektionale Zuordnungen oder anonyme Zuordnungen nicht möglich.
OtherKey String Die Primärschlüssel innerhalb der fremdsprachigen Klasse Eine durch Kommas getrennte Liste der Schlüssel auf der anderen Seite der Zuordnung.
IsForeignKey Boolean False Gibt an, ob dies die untergeordnete Seite der Beziehung ist, die viele Seite einer 1:n.
RelationshipType RelationshipType OneToMany Gibt an, ob der Benutzer angibt, dass die daten, die mit dieser Zuordnung verknüpft sind, die Kriterien der 1:1-Daten erfüllen oder dem allgemeineren Fall von 1:n entsprechen. Bei eins zu 1 behauptet der Benutzer, dass es für jede Zeile auf der Primärschlüsselseite ("eins") nur eine Zeile auf der Fremdschlüsselseite ("viele") gibt. Dies führt dazu, dass ein EntityRef<T> auf der "1"-Seite anstelle eines EntitySet<T> generiert wird. Die gültigen Werte sind OneToOne und OneToMany.
Deleterule String None Wird verwendet, um dieser Zuordnung Löschverhalten hinzuzufügen. Beispielsweise würde "CASCADE" der FK-Beziehung "ONDELETECASCADE" hinzufügen. Wenn auf NULL festgelegt ist, wird kein Löschverhalten hinzugefügt.

Funktion

Dieses Element stellt eine gespeicherte Prozedur oder eine Datenbankfunktion dar. Für jeden Funktionsknoten wird eine Methode in der DataContext-Klasse generiert.

Funktionsattribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name der gespeicherten Prozedur in der Datenbank.
Methode String Methode Der Name der zu generierenden CLR-Methode, die den Aufruf der gespeicherten Prozedur ermöglicht. Der Standardname für Method weist Elemente wie [dbo]. stripped Name auf.
AccessModifier AccessModifier Öffentlich Die Barrierefreiheitsebene der gespeicherten Prozedurmethode. Gültige Werte sind Public, Protected, Internal und Private.
HasMultipleResults Boolean Anzahl der Typen > 1 Gibt an, ob die gespeicherte Prozedur, die durch diesen Funktionsknoten dargestellt wird, mehrere Resultsets zurückgibt. Jedes Resultset ist eine tabellarische Form. Es kann sich entweder um einen vorhandenen Typ oder um eine Gruppe von Spalten handeln. Im letzteren Fall wird ein Knoten Typ für den Spaltensatz erstellt.
IsComposable Boolean False Gibt an, ob die Funktion/gespeicherte Prozedur in LINQ to SQL Abfragen zusammengesetzt werden kann. Nur DB-Funktionen, die void nicht zurückgeben, können zusammengesetzt werden.

FunktionsSub-Element attribute

Sub-Element Elementtypen Vorkommensbereich BESCHREIBUNG
<Parameter> Parameter 0-ungebunden Stellt die ein- und ausgehenden Parameter dieser gespeicherten Prozedur dar.
<ElementType> type 0-ungebunden Stellt die tabellarischen Shapes dar, die die entsprechende gespeicherte Prozedur zurückgeben kann.
<Return> Rückgabewert 0-1 Der zurückgegebene skalare Typ dieser db-Funktion oder gespeicherten Prozedur. Wenn Return NULL ist, gibt die Funktion void zurück. Eine Funktion kann nicht sowohl Return als auch ElementType aufweisen.

TableFunction

Dieses Element stellt CUD-Überschreibungsfunktionen für Tabellen dar. Der LINQ to SQL-Designer ermöglicht die Erstellung von Insert-, Update- und Delete-Überschreibungsmethoden für LINQ TO SQL und ermöglicht die Zuordnung von Entitätseigenschaftennamen zu Namen gespeicherter Prozedurparametern.

Der Methodenname für CUD-Funktionen wurde festgelegt, sodass in DBML für TableFunction-Elemente kein Method-Attribut vorhanden ist. Für die Tabelle Customer werden die CUD-Methoden beispielsweise als InsertCustomer, UpdateCustomer und DeleteCustomer benannt.

Eine Tabellenfunktion kann keine tabellarische Form zurückgeben, sodass im TableFunction-Element kein ElementType-Attribut vorhanden ist.

TableFunction-Attribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Name der gespeicherten Prozedur in der Datenbank.
AccessModifier AccessModifier Privat Die Barrierefreiheitsebene der gespeicherten Prozedurmethode. Gültige Werte sind Public, Protected, Internal und Private.
HasMultipleResults Boolean Anzahl der Typen > 1 Gibt an, ob die gespeicherte Prozedur, die durch diesen Funktionsknoten dargestellt wird, mehrere Resultsets zurückgibt. Jedes Resultset ist eine tabellarische Form. Es kann sich entweder um einen vorhandenen Typ oder um eine Gruppe von Spalten handeln. Im letzteren Fall wird ein Knoten Typ für den Spaltensatz erstellt.
IsComposable Boolean False Gibt an, ob die Funktion/gespeicherte Prozedur in LINQ to SQL Abfragen zusammengesetzt werden kann. Nur DB-Funktionen, die void nicht zurückgeben, können zusammengesetzt werden.

TableFunction-Sub-Element-Attribute

Sub-Elements Elementtyp Vorkommensbereich BESCHREIBUNG
<Parameter> TableFunctionParameter 0-ungebunden Stellt die In- und Out-Parameter dieser Tabellenfunktion dar.
<Return> TableFunctionReturn 0-1 Der zurückgegebene Skalartyp dieser Tabellenfunktion. Wenn Return NULL ist, gibt die Funktion void zurück.

Parameter

Dieses Element stellt einen gespeicherten Prozedur-/Funktionsparameter dar. Parameter können Daten ein- und aus übergeben.

Parameterattribute

attribute type Standard Beschreibungen
Name String (erforderlich) Der Datenbankname des gespeicherten proc/function-Parameters.
Parameter String Name Der CLR-Name des Methodenparameters.
  String (erforderlich) Der CLR-Name des Methodenparameters.
DbType String None Der DB-Typ des gespeicherten proc/function-Parameters.
Direction ParameterDirection In Die Richtung, in die der Parameter fließt. Kann eine der In-, Out- und InOut-Dateien sein.

Rückgabewert

Dieses Element stellt den Rückgabetyp einer gespeicherten Prozedur/Funktion dar.

Rückgabeattribute

attribute type Standard BESCHREIBUNG
type String (erforderlich) Der CLR-Typ des gespeicherten Proc/Function-Ergebnisses.
DbType String None Der DB-Typ des gespeicherten Proc/Function-Ergebnisses.

TableFunctionParameter

Dieses Element stellt einen Parameter einer CUD-Funktion dar. Parameter können Daten ein- und aus übergeben. Jeder Parameter ist einer Table-Spalte zugeordnet, zu der diese CUD-Funktion gehört. Dieses Element enthält keine Type- oder DbType-Attribute , da Typinformationen aus der Spalte abgerufen werden können, der der Parameter zugeordnet ist.

TableFunctionParameter-Attribute

attribute type Standard BESCHREIBUNG
Name String (erforderlich) Der Datenbankname des CUD-Funktionsparameters.
Parameter String Name Der CLR-Name des Methodenparameters.
Column String Name Der Spaltenname, dem dieser Parameter zugeordnet ist.
Direction ParameterDirection In Die Richtung, in die der Parameter fließt. Kann einer von In, Out oder InOut sein.
Version Version Aktuell Gibt an, ob PropertyName auf die aktuelle oder ursprüngliche Version einer bestimmten Spalte verweist. Gilt nur während der Updateüberschreibung . Kann aktuell oder original sein.

TableFunctionReturn

Dieses Element stellt einen Rückgabetyp einer CUD-Funktion dar. Sie enthält tatsächlich nur den Spaltennamen, der dem Ergebnis der CUD-Funktion zugeordnet ist. Die Typinformationen der Rückgabe können aus der Spalte abgerufen werden.

TableFunctionReturn-Attribut

Attrobit type Standard BESCHREIBUNG
Column String None Der Spaltenname, dem die Rückgabe zugeordnet ist.

Verbindung

Dieses Element stellt standard datenbankverbindungsparameter dar. Dies ermöglicht die Erstellung eines Standardkonstruktors für den DataContext-Typ , der bereits weiß, wie eine Verbindung mit einer Datenbank hergestellt werden kann.

Es sind zwei Arten von Standardverbindungen möglich, eine mit einer direkten ConnectionString und eine, die aus App.Settings liest.

Verbindungsattribute

attribute type Standard BESCHREIBUNG
UseApplicationSettings Boolean False Bestimmt, ob eine App.Settings-Datei verwendet oder Anwendungseinstellungen aus einem direkten ConnectionString abgerufen werden sollen.
ConnectionString String None Die Verbindungszeichenfolge, die an den SQL-Datenanbieter gesendet werden soll.
EinstellungenObjektname String Einstellungen Das App.Settings-Objekt , aus dem Eigenschaften abgerufen werden sollen.
EinstellungenPropertyName String ConnectionString Die App.Settings-Eigenschaft , die den ConnectionString enthält.

Entitäten mit mehreren Ebenen

In Anwendungen mit zwei Ebenen verarbeitet ein einzelner DataContext Abfragen und Updates. Für Anwendungen mit zusätzlichen Ebenen ist es jedoch häufig erforderlich, separate DataContext-Instanzen für Abfragen und Updates zu verwenden. Im Fall von ASP.NET Anwendungen werden beispielsweise Abfragen und Aktualisierungen für separate Anforderungen an den Webserver durchgeführt. Daher ist es nicht praktikabel, dieselbe DataContext-instance für mehrere Anforderungen zu verwenden. In solchen Fällen muss ein DataContext-instance objekte aktualisieren können, die nicht abgerufen wurden. Die Unterstützung von Entitäten mit mehreren Ebenen in LINQ to SQL stellt eine solche Funktion über die Attach()-Methode bereit.

Hier sehen Sie ein Beispiel dafür, wie ein Customer-Objekt mit einem anderen DataContext-instance geändert werden kann:

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

In Anwendungen mit mehreren Ebenen wird die gesamte Entität aus Gründen der Einfachheit, Interoperabilität oder des Datenschutzes häufig nicht schichtübergreifend gesendet. Beispielsweise kann ein Lieferant einen Datenvertrag für einen Webdienst definieren, der sich von der Entität Order unterscheidet, die auf der mittleren Ebene verwendet wird. Ebenso kann eine Webseite nur eine Teilmenge der Mitglieder einer Employee-Entität anzeigen. Daher ist die mehrschichtige Unterstützung für solche Fälle konzipiert. Nur die Member, die einer oder mehreren der folgenden Kategorien angehören, müssen zwischen den Ebenen transportiert und festgelegt werden, bevor Attach() aufgerufen wird.

  1. Member, die Teil der Identität der Entität sind.
  2. Elemente, die geändert wurden.
  3. Mitglieder, die an der Überprüfung der optimistischen Parallelität teilnehmen.

Wenn ein Zeitstempel oder eine Versionsnummernspalte für die Überprüfung der optimistischen Parallelität verwendet wird, muss der entsprechende Member festgelegt werden, bevor Attach()aufgerufen wird. Werte für andere Member müssen nicht festgelegt werden, bevor Attach()aufgerufen wird. LINQ to SQL verwendet minimale Updates mit Überprüfungen der optimistischen Parallelität. Das heißt, ein Element, das nicht festgelegt oder auf optimistische Parallelität überprüft wird, wird ignoriert.

Ursprüngliche Werte, die für die Überprüfung der optimistischen Parallelität erforderlich sind, können mithilfe einer Vielzahl von Mechanismen außerhalb des Bereichs LINQ to SQL APIs beibehalten werden. Eine ASP.NET Anwendung kann einen Ansichtszustand (oder ein Steuerelement verwenden, das den Ansichtszustand verwendet). Ein Webdienst kann dataContract für eine Updatemethode verwenden, um sicherzustellen, dass die ursprünglichen Werte für die Updateverarbeitung verfügbar sind. Im Interesse der Interoperabilität und Allgemeinheit bestimmt LINQ to SQL nicht die Form der zwischen Ebenen ausgetauschten Daten oder die Mechanismen, die zum Roundtrip der ursprünglichen Werte verwendet werden.

Entitäten zum Einfügen und Löschen erfordern nicht die Attach()- Methode. Die Methoden, die für Anwendungen mit zwei Ebenen verwendet werden– Table.Add()und Table.Remove() können zum Einfügen und Löschen verwendet werden. Wie bei Updates mit zwei Ebenen ist ein Benutzer für die Verarbeitung von Fremdschlüsseleinschränkungen verantwortlich. Ein Kunde mit Bestellungen kann nicht einfach entfernt werden, ohne seine Bestellungen zu bearbeiten, wenn eine Fremdschlüsseleinschränkung in der Datenbank vorhanden ist, die das Löschen eines Kunden mit Bestellungen verhindert.

LINQ to SQL behandelt auch das Anfügen von Entitäten für Updates transitiv. Der Benutzer erstellt im Wesentlichen das Objektdiagramm vor der Aktualisierung wie gewünscht und ruft Attach()auf. Alle Änderungen können dann im angefügten Graphen "wiedergegeben" werden, um die erforderlichen Updates wie unten gezeigt durchzuführen:

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

Externe Zuordnung

Zusätzlich zur attributbasierten Zuordnung unterstützt LINQ to SQL auch die externe Zuordnung. Die häufigste Form der externen Zuordnung ist eine XML-Datei. Zuordnungsdateien ermöglichen zusätzliche Szenarien, in denen die Trennung von Zuordnung und Code wünschenswert ist.

DataContext stellt einen zusätzlichen Konstruktor zum Bereitstellen einer MappingSource bereit. Eine Form von MappingSource ist eine XmlMappingSource , die aus einer XML-Zuordnungsdatei erstellt werden kann.

Hier sehen Sie ein Beispiel für die Verwendung der Zuordnungsdatei:

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   )

Hier sehen Sie einen entsprechenden Codeausschnitt aus der Zuordnungsdatei, der die Zuordnung für die Product-Klasse zeigt. Sie zeigt die Klasse Product im Namespace Mapping , die der Tabelle Products in der Northwind-Datenbank zugeordnet ist. Die Elemente und Attribute sind mit den Attributnamen und Parametern konsistent.

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

Unterstützung und Hinweise zu NET Framework-Funktionen

Die folgenden Absätze enthalten grundlegende Informationen zur Unterstützung LINQ to SQL Typs und zu unterschieden vom .NET Framework.

Primitive Typen

Implementiert

  • Arithmetische Operatoren und Vergleichsoperatoren
  • Schichtoperatoren: << und >>
  • Die Konvertierung zwischen char und numeric erfolgt durch UNICODE/NCHAR, andernfalls wird CONVERT von SQL verwendet.

Nicht implementiert

  • <Geben Sie ein>. Parse
  • Enumerationen können verwendet und ganzen Zahlen und Zeichenfolgen in einer Tabelle zugeordnet werden. Für letzteres werden die Methoden Parse und ToString() verwendet.

Unterschied zu .NET

  • Die Ausgabe von ToString für double verwendet CONVERT(NVARCHAR(30), @x, 2) in SQL, das immer 16 Ziffern und "Scientific Notation" verwendet. Beispiel: "0.000000000000000000e+000" für 0, sodass nicht dieselbe Zeichenfolge wie angegeben wird. Convert.ToString() von NET.

System.String

Implementiert

  • Nicht statische Methoden:

    • Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. Alle Signaturen werden unterstützt, außer wenn sie den StringComparison-Parameter usw. verwenden, wie unten beschrieben.
  • Statische Methoden:

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

        String(Char, Int32)
    
  • -Operatoren:

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

Nicht implementiert

  • Methoden, die ein Array von char verwenden oder erzeugen.

  • Methoden, die einen CultureInfo/StringComparison/IFormatProvider verwenden.

  • Statisch (freigegeben in 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
    
  • Instanz:

       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)
    

Einschränkungen/Unterschied zu .NET

SQL verwendet Sortierungen, um die Gleichheit und Reihenfolge von Zeichenfolgen zu bestimmen. Diese können für eine SQL Server-Instanz, eine Datenbank, eine Tabellenspalte oder einen Ausdruck angegeben werden.

Die Übersetzungen der bisher implementierten Funktionen ändern weder die Sortierung noch geben eine andere Sortierung für die übersetzten Ausdrücke an. Wenn bei der Standardsortierung also die Groß-/Kleinschreibung nicht beachtet wird, können Funktionen wie CompareTo oder IndexOf Ergebnisse liefern, die sich von den .NET-Funktionen (unter Berücksichtigung der Groß-/Kleinschreibung) unterscheiden.

Die Methoden StartsWith(str)/EndsWith(str) gehen davon aus, dass das Argument str eine Konstante oder ein Ausdruck ist, der auf dem Client ausgewertet wird. Das heißt, es ist derzeit nicht möglich, eine Spalte für str zu verwenden.

System.Math

Implementierte statische Methoden

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

Nicht implementiert

  • IEEERemainder.
  • DivRem verfügt über einen out-Parameter, sodass Sie diesen in einem Ausdruck nicht verwenden können. Die Konstanten Math.PI und Math.E werden auf dem Client ausgewertet, sodass sie keine Übersetzung benötigen.

Unterschied zu .NET

Die Übersetzung der .NET-Funktion Math.Round ist die SQL-Funktion ROUND. Die Übersetzung wird nur unterstützt, wenn eine Überladung angegeben wird, die den MidpointRounding-Enumerationswert angibt. MidpointRounding.AwayFromZero ist SQL-Verhalten und MidpointRounding.ToEven gibt clR-Verhalten an.

System.Convert

Implementiert

  • Methoden des Formulars To<Type1>(<Type2> x), wobei Type1, Type2 eine der folgenden Elemente ist:
    • bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 oder string.
  • Das Verhalten ist identisch mit einer Umwandlung:
    • Für ToString(Double) gibt es speziellen Code, um die volle Genauigkeit zu erhalten.
    • Für die Konvertierung von Int32/Char verwendet LINQ to SQL die UNICODE-NCHAR-Funktion/ von SQL.
    • Andernfalls handelt es sich bei der Übersetzung um eine CONVERT-Übersetzung.

Nicht implementiert

  • ToSByte, UInt16, 32, 64: Diese Typen sind in SQL nicht vorhanden.

    To<integer type>(String, Int32) 
    ToString(..., Int32)       any overload ending with an Int32 toBase
    IsDBNull(Object)
    GetTypeCode(Object)
    ChangeType(...)
    
  • Versionen mit dem Parameter IFormatProvider .

  • Methoden, die ein Array enthalten (To/FromBase64CharArray, To/FromBase64String).

System.TimeSpan

Implementiert

  • Konstruktoren:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • -Operatoren:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Statische (in Visual Basic freigegebene) Methoden:

       Compare(t1,t2)
    
  • Nicht statische (Instanz) Methoden/Eigenschaften:

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

Nicht implementiert

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

System.DateTime

Implementiert

  • Konstruktoren:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • -Operatoren:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • Statische (freigegebene) Methoden:

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • Nicht statische (Instanz) Methoden/Eigenschaften:

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

Unterschied zu .NET

Die datetime-Werte von SQL werden auf 000, 003 oder 007 Sekunden gerundet, sodass sie weniger genau sind als diejenigen von .NET.

Der Bereich von SQL datetime beginnt am 1. Januar 1753.

SQL verfügt nicht über einen integrierten Typ für TimeSpan. Es werden verschiedene DATEDIFF-Methoden verwendet, die 32-Bit-Ganzzahlen zurückgeben. Eine ist DATEDIFF(DAY,...), die die Anzahl der Tage angibt; eine andere ist DATEDIFF(MILLISECOND,...), die die Anzahl der Millisekunden angibt. Ein Fehler tritt auf, wenn die DateTimes mehr als 24 Tage voneinander entfernt sind. Im Gegensatz dazu verwendet .NET ganze 64-Bit-Zahlen und misst TimeSpans in Ticks.

Um der .NET-Semantik in SQL so nahe wie möglich zu kommen, übersetzt LINQ to SQL TimeSpans in 64-Bit-Ganzzahlen und verwendet die beiden oben genannten DATEDIFF-Methoden, um die Anzahl der Ticks zwischen zwei Datumsangaben zu berechnen.

Datetime UtcNow wird auf dem Client ausgewertet, wenn die Abfrage übersetzt wird (wie jeder Ausdruck, der keine Datenbankdaten enthält).

Nicht implementiert

   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

Debugunterstützung

DataContext stellt Methoden und Eigenschaften bereit, um die für Abfragen und Änderungsverarbeitung generierte SQL abzurufen. Diese Methoden können nützlich sein, um LINQ to SQL Funktionalität zu verstehen und bestimmte Probleme zu debuggen.

DataContext-Methoden zum Abrufen von generierten SQL-Instanzen

Member Zweck
Log Gibt SQL aus, bevor es ausgeführt wird. Deckt Abfrage-, Einfüge-, Aktualisierungs- und Löschbefehle ab. Syntax:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(query) Gibt den Abfragetext der Abfrage zurück, ohne ihn auszuführen. Syntax:

C#

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

Visual Basic

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

GetChangeText() Gibt den Text von SQL-Befehlen für insert/update/delete zurück, ohne sie auszuführen. Syntax:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())