LINQ to SQL: Language-Integrated Query de .NET para datos relacionales
Dinesh Kulkarni, Luca Bolognese, Matt Warren, Anders Hejlsberg, Kit George
Marzo de 2007
Se aplica a:
Visual Studio Code nombre "Orcas"
.NET Framework 3.5
Resumen: LINQ to SQL proporciona una infraestructura en tiempo de ejecución para administrar datos relacionales como objetos sin perder la capacidad de realizar consultas. La aplicación es libre de manipular los objetos mientras LINQ to SQL permanece en segundo plano realizando un seguimiento automático de los cambios. (119 páginas impresas)
Contenido
Introducción
Un paseo rápido
Creación de clases de entidad
The DataContext
Definir relaciones
Realizar consultas en varias relaciones
Modificar y guardar entidades
Consultas In-Depth
Ejecución de la consulta
Identidad de objetos
Relaciones
Combinaciones
Proyecciones
Consultas compiladas
Traducción de SQL
Ciclo de vida de la entidad
Seguimiento de cambios
Envío de cambios
Cambios simultáneos
Transacciones
Procedimientos almacenados
Clases de entidad In-Depth
Utilizar atributos
Coherencia de grafos
Notificaciones de cambio
Herencia
Temas avanzados
Crear bases de datos
Interoperación con ADO.NET
Cambiar resolución de conflictos
Invocación de procedimientos almacenados
Herramienta Generador de clases de entidad
Referencia de DBML de la herramienta Generador
Entidades de varios niveles
Asignación externa
Compatibilidad y notas de la función de NET Framework
Capacidad de depuración
Introducción
La mayoría de los programas escritos hoy manipulan datos de una forma u otra y, a menudo, estos datos se almacenan en una base de datos relacional. Sin embargo, hay una enorme división entre los lenguajes de programación modernos y las bases de datos en la forma en que representan y manipulan información. Esta falta de coincidencia de impedancia es visible de varias maneras. Lo más notable es que los lenguajes de programación acceden a la información de las bases de datos a través de api que requieren que las consultas se especifiquen como cadenas de texto. Estas consultas son partes significativas de la lógica del programa. Sin embargo, son opacos para el lenguaje, no pueden beneficiarse de la comprobación en tiempo de compilación y las características en tiempo de diseño, como IntelliSense.
Por supuesto, las diferencias van mucho más profundas que eso. La forma en que se representa la información (el modelo de datos) es bastante diferente entre los dos. Los lenguajes de programación modernos definen información en forma de objetos. Las bases de datos relacionales usan filas. Los objetos tienen una identidad única, ya que cada instancia es físicamente diferente de otra. Las filas se identifican mediante valores de clave principal. Los objetos tienen referencias que identifican y vinculan instancias juntas. Las filas se dejan intencionadamente distintas, lo que requiere que las filas relacionadas se vincule de forma flexible mediante claves externas. Los objetos independientes, existentes siempre y cuando sigan siendo referenciados por otro objeto. Las filas existen como elementos de las tablas, desapareciendo en cuanto se quitan.
No es de extrañar que las aplicaciones esperadas para salvar esta brecha son difíciles de compilar y mantener. Ciertamente simplificaría la ecuación para deshacerse de un lado u otro. Sin embargo, las bases de datos relacionales proporcionan una infraestructura crítica para el procesamiento de consultas y almacenamiento a largo plazo, y los lenguajes de programación modernos son indispensables para el desarrollo ágil y el cálculo enriquecido.
Hasta ahora, ha sido el trabajo del desarrollador de aplicaciones resolver este error de coincidencia en cada aplicación por separado. Las mejores soluciones hasta ahora han sido elaboradas capas de abstracción de base de datos que transporta la información entre los modelos de objetos específicos del dominio de las aplicaciones y la representación tabular de la base de datos, reformando y reformando los datos de cada forma. Sin embargo, al ocultar el verdadero origen de datos, estas soluciones terminan descartando la característica más atractiva de las bases de datos relacionales; la capacidad de consultar los datos.
LINQ to SQL, un componente de Visual Studio Code Nombre "Orcas", proporciona una infraestructura en tiempo de ejecución para administrar datos relacionales como objetos sin perder la capacidad de realizar consultas. Para ello, traduce consultas integradas en lenguaje en SQL para su ejecución por parte de la base de datos y, a continuación, traduce los resultados tabulares en objetos que defina. A continuación, la aplicación es libre de manipular los objetos mientras LINQ to SQL permanece en segundo plano realizando un seguimiento automático de los cambios.
- LINQ to SQL está diseñado para ser no intrusivo para la aplicación.
- Es posible migrar las soluciones de ADO.NET actuales a LINQ to SQL de manera por etapas (compartiendo las mismas conexiones y transacciones), ya que LINQ to SQL es simplemente otro componente de la familia ADO.NET. LINQ to SQL también tiene una amplia compatibilidad con los procedimientos almacenados, lo que permite reutilizar los recursos empresariales existentes.
- LINQ to SQL aplicaciones son fáciles de empezar.
- Los objetos vinculados a datos relacionales se pueden definir igual que los objetos normales, solo decorados con atributos para identificar cómo se corresponden las propiedades con las columnas. Por supuesto, ni siquiera es necesario hacer esto a mano. Se proporciona una herramienta en tiempo de diseño para automatizar la traducción de esquemas de base de datos relacionales preexistentes en definiciones de objetos automáticamente.
Juntos, la LINQ to SQL infraestructura en tiempo de ejecución y herramientas en tiempo de diseño reducen significativamente la carga de trabajo para el desarrollador de aplicaciones de base de datos. En los capítulos siguientes se proporciona información general sobre cómo se puede usar LINQ to SQL para realizar tareas comunes relacionadas con la base de datos. Se supone que el lector está familiarizado con Language-Integrated Query y los operadores de consulta estándar.
LINQ to SQL es independiente del idioma. Cualquier lenguaje creado para proporcionar Language-Integrated Query puede usarlo para permitir el acceso a la información almacenada en bases de datos relacionales. Los ejemplos de este documento se muestran en C# y Visual Basic; LINQ to SQL también se puede usar con la versión habilitada para LINQ del compilador de Visual Basic.
Un paseo rápido
El primer paso para crear una aplicación de LINQ to SQL declara las clases de objeto que usará para representar los datos de la aplicación. Revisemos un ejemplo.
Creación de clases de entidad
Comenzaremos con una clase simple Customer y la asociaremos a la tabla customers de la base de datos de ejemplo Northwind. Para ello, solo es necesario aplicar un atributo personalizado a la parte superior de la declaración de clase. LINQ to SQL define el atributo Table para este fin.
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
El atributo Table tiene una propiedad Name que puede usar para especificar el nombre exacto de la tabla de base de datos. Si no se proporciona ninguna propiedad Name, LINQ to SQL asume que la tabla de base de datos tiene el mismo nombre que la clase . Solo las instancias de clases declaradas como tablas se almacenarán en la base de datos. Las instancias de estos tipos de clases se conocen como entidades. Las propias clases se conocen como clases de entidad.
Además de asociar clases a tablas, deberá indicar cada campo o propiedad que quiera asociar a una columna de base de datos. Para ello, LINQ to SQL define el atributo Column.
C#
[Table(Name="Customers")]
public class Customer
{
[Column(IsPrimaryKey=true)]
public string CustomerID;
[Column]
public string City;
}
Visual Basic
<Table(Name:="Customers")> _
Public Class Customer
<Column(IsPrimaryKey:=true)> _
Public CustomerID As String
<Column> _
Public City As String
End Class
El atributo Column tiene una variedad de propiedades que puede usar para personalizar la asignación exacta entre los campos y las columnas de la base de datos. Una propiedad de nota es la propiedad Id . Indica LINQ to SQL que la columna de base de datos forma parte de la clave principal de la tabla.
Al igual que con el atributo Table , solo tiene que proporcionar información en el atributo Column si difiere de lo que se puede deducir de la declaración de campo o propiedad. En este ejemplo, debe indicar a LINQ to SQL que el campo CustomerID forma parte de la clave principal de la tabla, pero no tiene que especificar el nombre o el tipo exactos.
Solo los campos y propiedades declarados como columnas se conservarán o recuperarán de la base de datos. Otros se considerarán partes transitorias de la lógica de la aplicación.
The DataContext
DataContext es el conducto principal por el que se recuperan objetos de la base de datos y se vuelven a enviar los cambios. Se usa de la misma manera que usaría una conexión de ADO.NET. De hecho, DataContext se inicializa con una cadena de conexión o conexión que proporcione. El propósito de DataContext es traducir las solicitudes de objetos en consultas SQL realizadas en la base de datos y, a continuación, ensamblar objetos fuera de los resultados. DataContext habilita la consulta integrada en lenguaje mediante la implementación del mismo patrón de operador que los operadores de consulta estándar, como Where y Select.
Por ejemplo, puede usar DataContext para recuperar objetos de cliente cuya ciudad es Londres de la siguiente manera:
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
Cada tabla de base de datos se representa como una colección Table , accesible a través del método GetTable() mediante su clase de entidad para identificarla. Se recomienda declarar un DataContext fuertemente tipado en lugar de basarse en la clase DataContext básica y en el método GetTable(). Un DataContext fuertemente tipado declara todas las colecciones table como miembros del contexto.
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
La consulta para los clientes de Londres se puede expresar más simplemente como:
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
Continuaremos usando la clase Northwind fuertemente tipada para el resto del documento de información general.
Definir relaciones
Las relaciones en las bases de datos relacionales normalmente se modelan como valores de clave externa que hacen referencia a las claves principales de otras tablas. Para navegar entre ellas, debe reunir explícitamente las dos tablas mediante una operación de combinación relacional. Por otro lado, los objetos hacen referencia entre sí mediante referencias de propiedades o colecciones de referencias navegadas mediante notación "punto". Obviamente, el dotting es más sencillo que unirse, ya que no necesita recuperar la condición de combinación explícita cada vez que navega.
Para las relaciones de datos como estas que siempre serán las mismas, resulta bastante conveniente codificarlas como referencias de propiedad en la clase de entidad. LINQ to SQL define un atributo Association que se puede aplicar a un miembro utilizado para representar una relación. Una relación de asociación es una como una relación de clave externa a clave principal que se realiza mediante la coincidencia de valores de columna entre tablas.
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
La clase Customer ahora tiene una propiedad que declara la relación entre los clientes y sus pedidos. La propiedad Orders es de tipo EntitySet porque la relación es de uno a varios. Usamos la propiedad OtherKey en el atributo Association para describir cómo se realiza esta asociación. Especifica los nombres de las propiedades de la clase relacionada que se van a comparar con esta. También había una propiedad ThisKey que no se especificó. Normalmente, lo usaríamos para enumerar los miembros de este lado de la relación. Sin embargo, si se omite, se permite LINQ to SQL deducirlos de los miembros que componen la clave principal.
Observe cómo se invierte en la definición de la clase Order .
C#
[Table(Name="Orders")]
public class Order
{
[Column(Id=true)]
public int OrderID;
[Column]
public string CustomerID;
private EntityRef<Customer> _Customer;
[Association(Storage="_Customer", ThisKey="CustomerID")]
public Customer Customer {
get { return this._Customer.Entity; }
set { this._Customer.Entity = value; }
}
}
Visual Basic
<Table(Name:="Orders")> _
Public Class Order
<Column(Id:=true)> _
Public OrderID As String
<Column> _
Public CustomerID As String
Private _Customer As EntityRef(Of Customer)
<Association(Storage:="_Customer", ThisKey:="CustomerID")> _
Public Property Customer() As Customer
Get
Return Me._Customer.Entity
End Get
Set(ByVal value As Customer)
Me._Customers.Entity = value
End Set
End Property
End Class
La clase Order usa el tipo EntityRef para describir la relación de vuelta al cliente. El uso de la clase EntityRef es necesario para admitir la carga diferida (se describe más adelante). El atributo Association de la propiedad Customer especifica la propiedad ThisKey , ya que los miembros no inferibles están ahora en este lado de la relación.
Eche un vistazo también a la propiedad Storage . Indica LINQ to SQL qué miembro privado se usa para contener el valor de la propiedad. Esto permite LINQ to SQL omitir los descriptores de acceso de propiedad pública cuando almacena y recupera su valor. Esto es esencial si desea LINQ to SQL evitar cualquier lógica de negocios personalizada escrita en los descriptores de acceso. Si no se especifica la propiedad de almacenamiento, se usarán en su lugar los descriptores de acceso públicos. También puede usar la propiedad Storage con atributos Column .
Una vez que introduzca relaciones en las clases de entidad, la cantidad de código que necesita escribir crece a medida que introduce compatibilidad con las notificaciones y la coherencia del grafo. Afortunadamente, hay una herramienta (descrita más adelante) que se puede usar para generar todas las definiciones necesarias como clases parciales, lo que le permite usar una combinación de código generado y lógica de negocios personalizada.
En el resto de este documento, se supone que la herramienta se ha usado para generar un contexto de datos Northwind completo y todas las clases de entidad.
Realizar consultas en varias relaciones
Ahora que tiene relaciones, puede usarlas al escribir consultas simplemente haciendo referencia a las propiedades de relación definidas en la clase .
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
La consulta anterior usa la propiedad Orders para formar el producto cruzado entre clientes y pedidos, lo que genera una nueva secuencia de pares Cliente y Pedido .
También es posible hacer lo contrario.
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
En este ejemplo, se consultan los pedidos y la relación Customer se usa para obtener acceso a la información sobre el objeto Customer asociado.
Modificar y guardar entidades
Algunas aplicaciones se compilan solo teniendo en cuenta la consulta. También se deben crear y modificar los datos. LINQ to SQL está diseñado para ofrecer la máxima flexibilidad en la manipulación y conservación de los cambios realizados en los objetos. Tan pronto como los objetos de entidad estén disponibles (ya sea recuperandolos a través de una consulta o construyendolos de nuevo), puede manipularlos como objetos normales en la aplicación, cambiar sus valores o agregarlos y quitarlos de las colecciones como considere oportuno. LINQ to SQL realiza un seguimiento de todos los cambios y está listo para transmitirlos a la base de datos tan pronto como haya terminado.
En el ejemplo siguiente se usan las clases Customer y Order generadas por una herramienta a partir de los metadatos de toda la base de datos de ejemplo Northwind. Las definiciones de clase no se han mostrado por motivos de brevedad.
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()
Cuando se llama a SubmitChanges(), LINQ to SQL genera y ejecuta automáticamente comandos SQL para transmitir los cambios a la base de datos. También es posible invalidar este comportamiento con lógica personalizada. La lógica personalizada puede llamar a un procedimiento almacenado de base de datos.
Consultas In-Depth
LINQ to SQL proporciona una implementación de los operadores de consulta estándar para los objetos asociados a tablas de una base de datos relacional. En este capítulo se describen los aspectos específicos del LINQ to SQL de las consultas.
Ejecución de la consulta
Tanto si escribe una consulta como una expresión de consulta de alto nivel como si crea uno de los operadores individuales, la consulta que escribe no es una instrucción imperativa ejecutada inmediatamente. Es una descripción. Por ejemplo, en la declaración debajo de la variable local q hace referencia a la descripción de la consulta no al resultado de ejecutarla.
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
El tipo real de q en esta instancia es IQueryable<Customer>. No es hasta que la aplicación intenta enumerar el contenido de la consulta que realmente ejecuta. En este ejemplo, la instrucción foreach hace que se produzca la ejecución.
Un objeto IQueryable es similar a un objeto de comando ADO.NET. Tener una en la mano no implica que se haya ejecutado una consulta. Un objeto de comando se mantiene en una cadena que describe una consulta. Del mismo modo, un objeto IQueryable contiene una descripción de una consulta codificada como una estructura de datos conocida como expresión. Un objeto de comando tiene un método ExecuteReader() que provoca la ejecución y devuelve resultados como DataReader. Un objeto IQueryable tiene un método GetEnumerator() que provoca la ejecución y devuelve resultados como un cliente> de IEnumerator<.
Por lo tanto, sigue que si una consulta se enumera dos veces, se ejecutará dos veces.
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
Este comportamiento se conoce como ejecución diferida. Al igual que con un objeto de comando ADO.NET, es posible mantener en una consulta y volver a ejecutarla.
Por supuesto, los escritores de aplicaciones a menudo deben ser muy explícitos sobre dónde y cuándo se ejecuta una consulta. Sería inesperado si una aplicación ejecutara una consulta varias veces simplemente porque necesitaba examinar los resultados más de una vez. Por ejemplo, puede que desee enlazar los resultados de una consulta a algo parecido a dataGrid. El control puede enumerar los resultados cada vez que pinta en la pantalla.
Para evitar la ejecución de varias veces, convierta los resultados en cualquier número de clases de colección estándar. Es fácil convertir los resultados en una lista o matriz mediante los operadores de consulta estándar ToList() o ToArray()..
C#
var q =
from c in db.Customers
where c.City == "London"
select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)
Console.WriteLine(c.CompanyName);
foreach (Customer c in list)
Console.WriteLine(c.CompanyName);
Visual Basic
Dim londonCustomers = From cust In db.Customers _
where cust.City = "London"
' Execute once using ToList() or ToArray()
Dim londonCustList = londonCustomers.ToList()
' Neither of these iterations re-executes the query
For Each cust In londonCustList
Console.WriteLine(cust.CompanyName)
Next
For Each cust In londonCustList
Console.WriteLine(cust.CompanyName)
Next
Una ventaja de la ejecución diferida es que las consultas se pueden construir por partes con la ejecución que solo se produce cuando se completa la construcción. Puede empezar a componer una parte de una consulta, asignarla a una variable local y, a continuación, algún tiempo después, seguir aplicando más operadores a ella.
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
En este ejemplo, q comienza como una consulta para todos los clientes de Londres. Más adelante cambia a una consulta ordenada en función del estado de la aplicación. Al aplazar la ejecución, la consulta se puede construir para satisfacer las necesidades exactas de la aplicación sin necesidad de manipulación de cadenas de riesgo.
Identidad de objetos
Los objetos del entorno de ejecución tienen una identidad única. Si dos variables hacen referencia al mismo objeto, realmente hacen referencia a la misma instancia de objeto. Por este motivo, los cambios realizados a través de una ruta de acceso a través de una variable son visibles inmediatamente a través de la otra. Las filas de una tabla de base de datos relacional no tienen identidad única. Sin embargo, tienen una clave principal y esa clave principal puede ser única, lo que significa que ninguna dos filas pueden compartir la misma clave. Sin embargo, esto solo restringe el contenido de la tabla de base de datos. Por lo tanto, siempre que solo interactúemos con los datos a través de comandos remotos, equivale a aproximadamente lo mismo.
Sin embargo, esto rara vez es el caso. La mayoría de las veces, los datos se sacan de la base de datos y se colocan en un nivel diferente en el que una aplicación la manipula. Claramente, este es el modelo que LINQ to SQL está diseñado para admitir. Cuando los datos se sacan de la base de datos como filas, no hay ninguna expectativa de que dos filas que representen los mismos datos se correspondan realmente con las mismas instancias de fila. Si consulta dos veces un cliente específico, obtiene dos filas de datos, cada una que contiene la misma información.
Sin embargo, con objetos, espera algo bastante diferente. Espera que, si solicita a DataContext la misma información de nuevo, de hecho le devolverá la misma instancia de objeto. Esto se espera porque los objetos tienen un significado especial para la aplicación y espera que se comporten como objetos normales. Los diseñó como jerarquías o grafos y, sin duda, espera recuperarlos como tal, sin hordas de instancias replicadas simplemente porque solicitó lo mismo dos veces.
Por este motivo, DataContext administra la identidad del objeto. Cada vez que se recupera una nueva fila de la base de datos, se registra en una tabla de identidad mediante su clave principal y se crea un nuevo objeto. Cada vez que se recupera de nuevo esa misma fila, la instancia de objeto original se devuelve a la aplicación. De este modo, DataContext convierte el concepto de identidad (claves) de las bases de datos en el concepto de lenguajes (instancias). La aplicación solo ve el objeto en el estado que se recuperó por primera vez. Los nuevos datos, si son diferentes, se descartan.
Podría estar desconcertado por esto, ya que ¿por qué cualquier aplicación tiraría datos? Como resulta, esto es cómo LINQ to SQL administra la integridad de los objetos locales y es capaz de admitir actualizaciones optimistas. Dado que la aplicación crea inicialmente los únicos cambios que se producen después de crear el objeto, la intención de la aplicación es clara. Si se han producido cambios por parte externa en el período provisional, se identificarán en el momento en que se llame a SubmitChanges(). Más información se explica en la sección Cambios simultáneos.
Tenga en cuenta que, en caso de que la base de datos contenga una tabla sin una clave principal, LINQ to SQL permite enviar consultas a través de la tabla, pero no permite actualizaciones. Esto se debe a que el marco no puede identificar la fila que se va a actualizar dada la falta de una clave única.
Por supuesto, si el objeto solicitado por la consulta es fácilmente identificable por su clave principal, ya que uno ya recuperado no se ejecuta ninguna consulta. La tabla de identidades actúa como una memoria caché que almacena todos los objetos recuperados anteriormente.
Relaciones
Como vimos en el recorrido rápido, las referencias a otros objetos o colecciones de otros objetos de las definiciones de clase corresponden directamente a las relaciones de clave externa de la base de datos. Puede usar estas relaciones al consultar simplemente mediante la notación de puntos para acceder a las propiedades de la relación y navegar de un objeto a otro. Estas operaciones de acceso se traducen en combinaciones más complicadas o subconsultas correlacionadas en el SQL equivalente, lo que le permite recorrer el gráfico de objetos durante una consulta. Por ejemplo, la consulta siguiente navega de pedidos a clientes como una manera de restringir los resultados a sólo los pedidos de los clientes de Londres.
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"
Si las propiedades de relación no existían, tendría que escribirlas manualmente como combinaciones como haría en una consulta SQL.
C#
var q =
from c in db.Customers
join o in db.Orders on c.CustomerID equals o.CustomerID
where c.City == "London"
select o;
Visual Basic
Dim londonOrders = From cust In db.Customers _
Join ord In db.Orders _
On cust.CustomerID Equals ord.CustomerID _
Where ord.Customer.City = "London" _
Select ord
La propiedad relationship permite definir esta relación determinada una vez que se habilita el uso de la sintaxis de puntos más conveniente. Sin embargo, esta no es la razón por la que existen propiedades de relación. Existen porque tendemos a definir nuestros modelos de objetos específicos del dominio como jerarquías o gráficos. Los objetos en los que se elige programar tienen referencias a otros objetos. Solo es una coincidencia feliz que, dado que las relaciones de objeto a objeto corresponden a relaciones de estilo de clave externa en las bases de datos a las que el acceso de propiedad conduce a una manera cómoda de escribir combinaciones.
Por lo tanto, la existencia de propiedades de relación es más importante en el lado de los resultados de una consulta que como parte de la propia consulta. Una vez que tenga las manos sobre un cliente determinado, su definición de clase le indica que los clientes tienen pedidos. Por lo tanto, cuando examine la propiedad Orders de un cliente determinado, espera ver la colección rellenada con todos los pedidos del cliente, ya que es de hecho el contrato que declaró definiendo las clases de esta manera. Espera ver los pedidos allí incluso si no solicitó pedidos por adelantado. Espera que el modelo de objetos mantenga una ilusión de que es una extensión en memoria de la base de datos, con objetos relacionados disponibles inmediatamente.
LINQ to SQL implementa una técnica denominada carga diferida para ayudar a mantener esta ilusión. Cuando se consulta un objeto, en realidad solo se recuperan los objetos que solicitó. No se capturan los objetos relacionados al mismo tiempo automáticamente. Sin embargo, el hecho de que los objetos relacionados aún no están cargados no es observable desde que intenta acceder a ellos una solicitud sale para recuperarlos.
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
Por ejemplo, puede que desee consultar un conjunto determinado de pedidos y, a continuación, enviar ocasionalmente una notificación por correo electrónico a determinados clientes. No sería necesario recuperar todos los datos del cliente por adelantado con cada pedido. La carga diferida permite aplazar el costo de recuperar información adicional hasta que sea absolutamente necesario.
Por supuesto, lo contrario también podría ser cierto. Es posible que tenga una aplicación que necesite examinar los datos de clientes y pedidos al mismo tiempo. Sabe que necesita ambos conjuntos de datos. Sabe que la aplicación va a explorar en profundidad los pedidos de cada cliente tan pronto como los obtenga. Sería lamentable desencadenar consultas individuales para pedidos para cada cliente. Lo que realmente desea suceder es que los datos de pedido se recuperen junto con los clientes.
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
Ciertamente, siempre puede encontrar una manera de combinar clientes y pedidos en una consulta mediante la formación del producto cruzado y la recuperación de todos los bits relativos de datos como una gran proyección. Pero los resultados no serían entidades. Las entidades son objetos con identidad que se pueden modificar mientras los resultados serían proyecciones que no se pueden cambiar y conservar. Peor, recuperaría una gran cantidad de datos redundantes a medida que cada cliente repite para cada pedido en la salida de combinación acoplada.
Lo que realmente necesita es una manera de recuperar un conjunto de objetos relacionados al mismo tiempo: una parte delimitada de un gráfico para que nunca se recupere más o menos de lo necesario para su uso previsto.
LINQ to SQL permite solicitar la carga inmediata de una región del modelo de objetos por este motivo. Para ello, permite la especificación de dataShape para dataContext. La clase DataShape se usa para indicar al marco qué objetos recuperar cuando se recupera un tipo determinado. Esto se logra mediante el método LoadWith como se muestra a continuación:
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
En la consulta anterior, todos los pedidos de todos los clientes que viven en Londres se recuperan cuando se ejecuta la consulta, de modo que el acceso sucesivo a la propiedad Orders de un objeto Customer no desencadene una consulta de base de datos.
La clase DataShape también se puede usar para especificar subconsultas que se aplican a una navegación de relaciones. Por ejemplo, si desea recuperar solo los pedidos que se han enviado hoy, puede usar el método AssociateWith en DataShape como en lo siguiente:
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
En el código anterior, la instrucción foreach interna recorre en iteración las órdenes que se han enviado hoy, ya que solo estos pedidos se han recuperado de la base de datos.
Es importante observar dos hechos sobre la clase DataShape :
Después de asignar una clase DataShape a DataContext, no se puede modificar DataShape . Cualquier llamada al método LoadWith o AssociateWith en este tipo de DataShape devolverá un error en tiempo de ejecución.
Es imposible crear ciclos mediante LoadWith o AssociateWith. Por ejemplo, lo siguiente genera un error en tiempo de ejecución:
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)
Combinaciones
La mayoría de las consultas en los modelos de objetos dependen en gran medida de navegar por las referencias de objetos en el modelo de objetos. Sin embargo, hay "relaciones" interesantes entre entidades que pueden no capturarse en el modelo de objetos como referencias. Por ejemplo , Customer.Orders es una relación útil basada en relaciones de clave externa en la base de datos Northwind. Sin embargo, proveedores y clientes de la misma ciudad o país es una relación ad hoc que no se basa en una relación de clave externa y puede que no se capture en el modelo de objetos. Las combinaciones proporcionan un mecanismo adicional para controlar estas relaciones. LINQ to SQL admite los nuevos operadores de combinación introducidos en LINQ.
Considere el siguiente problema: busque proveedores y clientes basados en la misma ciudad. En la consulta siguiente se devuelven los nombres de empresa de proveedores y clientes y la ciudad común como resultado plano. Este es el equivalente de la combinación equitativa interna en bases de datos relacionales:
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
La consulta anterior elimina los proveedores que no están en la misma ciudad que un cliente determinado. Sin embargo, hay ocasiones en las que no queremos eliminar una de las entidades en una relación ad hoc . En la consulta siguiente se enumeran todos los proveedores con grupos de clientes para cada uno de los proveedores. Si un proveedor determinado no tiene ningún cliente en la misma ciudad, el resultado es una colección vacía de clientes correspondientes a ese proveedor. Tenga en cuenta que los resultados no son planos: cada proveedor tiene una colección asociada. De hecho, esto proporciona combinación de grupo: combina dos secuencias y grupos elementos de la segunda secuencia por los elementos de la primera secuencia.
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
La combinación de grupos también se puede extender a varias colecciones. La consulta siguiente amplía la consulta anterior enumerando los empleados que se encuentran en la misma ciudad que el proveedor. Aquí, el resultado muestra un proveedor con colecciones (posiblemente vacías) de clientes y empleados.
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
Los resultados de una combinación de grupo también se pueden aplanar. Los resultados de aplanar la unión de grupo entre proveedores y clientes son varias entradas para proveedores con varios clientes en su ciudad, uno por cliente. Las colecciones vacías se reemplazan por valores NULL. Esto equivale a una combinación externa externa izquierda en bases de datos relacionales.
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
Las firmas de los operadores de combinación subyacentes se definen en el documento operadores de consulta estándar. Solo se admiten combinaciones equi-joins y los dos operandos de equals deben tener el mismo tipo.
Proyecciones
Hasta ahora, solo hemos examinado las consultas para recuperar entidades: objetos directamente asociados a las tablas de base de datos. No necesitamos limitarnos a esto. La belleza de un lenguaje de consulta es que puede recuperar información en cualquier forma que desee. No podrá aprovechar el seguimiento automático de cambios ni la administración de identidades cuando lo haga. Sin embargo, puede obtener solo los datos que desee.
Por ejemplo, es posible que simplemente necesite conocer los nombres de empresa de todos los clientes de Londres. Si este es el caso, no hay ninguna razón concreta para recuperar objetos de cliente completos simplemente para elegir nombres. Puede proyectar los nombres como parte de la consulta.
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
En este caso, q se convierte en una consulta que recupera una secuencia de cadenas.
Si desea recuperar más que un solo nombre, pero no lo suficiente para justificar la captura del objeto de cliente completo, puede especificar cualquier subconjunto que desee construyendo los resultados como parte de la consulta.
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
En este ejemplo se usa un inicializador de objeto anónimo para crear una estructura que contenga el nombre de la empresa y el número de teléfono. Es posible que no sepa qué llamar al tipo, pero con la declaración de variable local con tipo implícito en el lenguaje que no necesita necesariamente.
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
Si va a consumir los datos inmediatamente, los tipos anónimos hacen una buena alternativa a definir explícitamente las clases para contener los resultados de la consulta.
También puede formar productos cruzados de objetos completos, aunque es posible que rara vez tenga un motivo para hacerlo.
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
Esta consulta construye una secuencia de pares de objetos customer y order.
También es posible realizar proyecciones en cualquier fase de la consulta. Puede proyectar datos en objetos recién construidos y, a continuación, hacer referencia a los miembros de esos objetos en operaciones de consulta posteriores.
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
Sin embargo, tenga cuidado con el uso de constructores parametrizados en esta fase. Técnicamente es válido hacerlo, pero es imposible que LINQ to SQL realizar un seguimiento de cómo afecta el uso del constructor al estado miembro sin comprender el código real dentro del constructor.
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
Dado que LINQ to SQL intentos de traducir la consulta en tipos de objeto puramente relacionales definidos localmente no están disponibles en el servidor para construir realmente. Toda la construcción de objetos se pospone realmente hasta después de recuperar los datos de la base de datos. En lugar de constructores reales, sql generado usa la proyección de columna SQL normal. Puesto que no es posible que el traductor de consultas comprenda lo que sucede durante una llamada al constructor, no puede establecer un significado para el campo Nombre de MyType.
En su lugar, el procedimiento recomendado es usar siempre inicializadores de objetos para codificar proyecciones.
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
El único lugar seguro para usar un constructor con parámetros está en la proyección final de una consulta.
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>
Incluso puede usar un anidamiento elaborado de constructores de objetos si lo desea, como en este ejemplo, que construye XML directamente fuera del resultado de una consulta. Funciona siempre que sea la última proyección de la consulta.
Aun así, aunque se comprendan las llamadas de constructor, es posible que no sean llamadas a métodos locales. Si la proyección final requiere la invocación de métodos locales, es poco probable que LINQ to SQL pueda obligar. Las llamadas de método que no tienen una traducción conocida a SQL no se pueden usar como parte de la consulta. Una excepción a esta regla es las llamadas de método que no tienen argumentos dependientes de variables de consulta. No se consideran parte de la consulta traducida y, en su lugar, se tratan como parámetros.
Las proyecciones (transformaciones) aún elaboradas pueden requerir lógica de procedimientos locales para implementar. Para poder usar sus propios métodos locales en una proyección final, deberá proyectar dos veces. La primera proyección extrae todos los valores de datos a los que deberá hacer referencia y la segunda proyección realiza la transformación. Entre estas dos proyecciones hay una llamada al operador AsEnumerable() que desplaza el procesamiento en ese punto desde una consulta de LINQ to SQL a una ejecutada localmente.
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)}
Nota El operador AsEnumerable(), a diferencia de ToList() y ToArray(), no provoca la ejecución de la consulta. Todavía se aplaza. El operador AsEnumerable() simplemente cambia la escritura estática de la consulta, convirtiendo un T IQueryable<(>IQueryable(ofT) en un IEnumerable<T>
(IEnumerable (ofT) en Visual Basic), engañando al compilador para tratar el resto de la consulta como ejecutado localmente.
Consultas compiladas
Es habitual en muchas aplicaciones ejecutar consultas estructuralmente similares muchas veces. En tales casos, es posible aumentar el rendimiento mediante la compilación de la consulta una vez y su ejecución varias veces en la aplicación con parámetros diferentes. Este resultado se obtiene en LINQ to SQL mediante la clase CompiledQuery. En el código siguiente se muestra cómo definir una consulta compilada:
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
El método Compile devuelve un delegado que se puede almacenar en caché y ejecutar después varias veces cambiando los parámetros de entrada. El código siguiente muestra un ejemplo de esto:
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
Traducción de SQL
LINQ to SQL realmente no ejecuta consultas; la base de datos relacional sí. LINQ to SQL traduce las consultas escritas en consultas SQL equivalentes y las envía al servidor para su procesamiento. Dado que la ejecución se aplaza, LINQ to SQL puede examinar toda la consulta incluso si se ensambla desde varias partes.
Dado que el servidor de bases de datos relacionales no ejecuta il realmente (aparte de la integración clR en SQL Server 2005); las consultas no se transmiten al servidor como IL. De hecho, se transmiten como consultas SQL con parámetros en formato de texto.
Por supuesto, SQL, incluso T-SQL con integración CLR, no es capaz de ejecutar la variedad de métodos que están disponibles localmente para el programa. Por lo tanto, las consultas que escriba deben traducirse en operaciones y funciones equivalentes que están disponibles en el entorno de SQL.
La mayoría de los métodos y operadores de los tipos integrados de .Net Framework tienen traducciones directas a SQL. Algunos se pueden producir fuera de las funciones disponibles. Los que no se pueden traducir no se permiten, lo que genera excepciones en tiempo de ejecución si intenta usarlas. Hay una sección más adelante en el documento que detalla los métodos de marco que se implementan para traducir a SQL.
Ciclo de vida de la entidad
LINQ to SQL es más que una implementación de los operadores de consulta estándar para las bases de datos relacionales. Además de traducir consultas, es un servicio que administra los objetos durante toda su duración, lo que le ayuda a mantener la integridad de los datos y automatizar el proceso de traducción de las modificaciones en el almacén.
En un escenario típico, los objetos se recuperan a través de una o varias consultas y, a continuación, se manipulan de alguna manera u otra hasta que la aplicación esté lista para devolver los cambios al servidor. Este proceso puede repetirse varias veces hasta que la aplicación ya no tenga uso para esta información. En ese momento, el tiempo de ejecución reclama los objetos igual que los objetos normales. Sin embargo, los datos permanecen en la base de datos. Incluso después de borrarse de su existencia en tiempo de ejecución, los objetos que representan los mismos datos todavía se pueden recuperar. En este sentido, la verdadera duración del objeto existe más allá de cualquier manifestación en tiempo de ejecución única.
El foco de este capítulo es el ciclo de vida de la entidad donde un ciclo hace referencia al intervalo de tiempo de una única manifestación de un objeto de entidad dentro de un contexto en tiempo de ejecución determinado. El ciclo se inicia cuando DataContext se da cuenta de una nueva instancia y termina cuando el objeto o DataContext ya no es necesario.
Seguimiento de cambios
Una vez que las entidades se recuperan de la base de datos, puede manipularlas a medida que quiera. Son sus objetos; úselos como lo hará. Al hacerlo, LINQ to SQL realiza un seguimiento de los cambios para que pueda conservarlos en la base de datos cuando se llama a SubmitChanges().
LINQ to SQL comienza a realizar el seguimiento de las entidades en el momento en que se recuperan de la base de datos, antes de que nunca los haya hecho. De hecho, el servicio de administración de identidades descrito anteriormente ya se ha iniciado. El seguimiento de cambios cuesta muy poco en sobrecarga adicional hasta que realmente empiece a realizar cambios.
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"
En cuanto se asigna CompanyName en el ejemplo anterior, LINQ to SQL se da cuenta del cambio y es capaz de registrarlo. El servicio de seguimiento de cambios conserva los valores originales de todos los miembros de datos.
El servicio de seguimiento de cambios también registra todas las manipulaciones de las propiedades de relación. Las propiedades de relación se usan para establecer los vínculos entre las entidades, aunque se puedan vincular mediante valores de clave en la base de datos. No es necesario modificar directamente los miembros asociados a las columnas de clave. LINQ to SQL los sincroniza automáticamente antes de enviar los cambios.
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
Puede mover pedidos de un cliente a otro simplemente realizando una asignación a su propiedad Customer . Puesto que la relación existe entre el cliente y el pedido, puede cambiar la relación modificando cualquier lado. Podría haber quitado los pedidos de la colección Orders de
cust2 y agregarlos a la colección orders de cust1, como se muestra a continuación.
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)
Por supuesto, si asigna una relación el valor de NULL, de hecho se deshace completamente de la relación. La asignación de una propiedad Customer de un pedido a NULL elimina realmente el pedido de la lista del cliente.
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))
La actualización automática de ambos lados de una relación es esencial para mantener la coherencia del gráfico de objetos. A diferencia de los objetos normales, las relaciones entre los datos suelen ser bidireccionales. LINQ to SQL permite usar propiedades para representar relaciones. Sin embargo, no ofrece un servicio para mantener automáticamente estas propiedades bidireccionales sincronizadas. Se trata de un nivel de servicio que se debe hornear directamente en las definiciones de clase. Las clases de entidad generadas mediante la herramienta de generación de código tienen esta funcionalidad. En el siguiente capítulo, le mostraremos cómo hacerlo en sus propias clases escritas a mano.
Sin embargo, es importante tener en cuenta que quitar una relación no implica que se haya eliminado un objeto de la base de datos. Recuerde que la duración de los datos subyacentes persiste en la base de datos hasta que la fila se haya eliminado de la tabla. La única manera de eliminar realmente un objeto es quitarlo de su colección Table .
C#
Customer cust = db.Customers.Single(c => c.CustomerID == custId1);
// Pick some order
Order o = cust.Orders[0];
// Remove it directly from the table (I want it gone!)
db.Orders.Remove(o);
// Displays 'false'.. gone from customer's Orders
Console.WriteLine(cust.Orders.Contains(o));
// Displays 'true'.. order is detached from its customer
Console.WriteLine(o.Customer == null);
Visual Basic
Dim targetCustomer = (From cust In db.Customers _
Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Remove it directly from the table (I want it gone!)
db.Orders.Remove(o)
' Displays 'False'.. gone from customer’s Orders
Msgbox(targetCustomer.Orders.Contains(o))
' Displays 'True'.. order is detached from its customer
Msgbox(o.Customer = Nothing)
Al igual que con todos los demás cambios, el pedido no se ha eliminado realmente. Solo nos parece así desde que se ha quitado y desasociado del resto de nuestros objetos. Cuando el objeto order se quitó de la tabla Orders , el servicio de seguimiento de cambios lo marcó para su eliminación. La eliminación real de la base de datos se producirá cuando se envíen los cambios en una llamada a SubmitChanges(). Tenga en cuenta que el propio objeto nunca se elimina. El tiempo de ejecución administra la duración de las instancias de objeto, por lo que se mantiene siempre y cuando todavía mantenga una referencia a ella. Sin embargo, después de quitar un objeto de su tabla y los cambios enviados ya no se realiza el seguimiento por parte del servicio de seguimiento de cambios.
La única vez que se deja una entidad sin realizar el seguimiento es cuando existe antes de que DataContext sea consciente de ella. Esto sucede cada vez que se crean nuevos objetos en el código. Puede usar instancias de clases de entidad en la aplicación sin recuperarlas nunca de una base de datos. Los cambios en la administración de identidades y táctiles solo se aplican a los objetos de los que dataContext es consciente. Por lo tanto, ninguno de los servicios está habilitado para las instancias recién creadas hasta que las agregue a DataContext.
Esto puede ocurrir de dos maneras. Puede llamar manualmente al método Add() en la colección Table relacionada.
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)
Como alternativa, puede adjuntar una nueva instancia a un objeto del que DataContext ya conoce.
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 detectará las nuevas instancias de objeto, incluso si están asociadas a otras nuevas instancias.
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
}
} )
Básicamente, DataContext reconocerá cualquier entidad del gráfico de objetos que no se realice actualmente como una nueva instancia, independientemente de si llamó o no al método Add().
Uso de DataContext de solo lectura
Muchos escenarios no requieren actualizar las entidades recuperadas de la base de datos. Mostrar una tabla de clientes en una página web es un ejemplo obvio. En todos estos casos, es posible mejorar el rendimiento al indicar a DataContext que no realice un seguimiento de los cambios en las entidades. Esto se logra especificando la propiedad ObjectTracking en DataContext para que sea false como en el código siguiente:
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
Envío de cambios
Independientemente del número de cambios que realice en los objetos, esos cambios solo se realizaron en réplicas en memoria. Todavía no ha ocurrido nada con los datos reales de la base de datos. La transmisión de esta información al servidor no se producirá hasta que la solicite explícitamente llamando a SubmitChanges() en DataContext.
C#
Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
db.SubmitChanges();
Visual Basic
Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
db.SubmitChanges()
Al llamar a SubmitChanges(),DataContext intentará traducir todos los cambios en comandos SQL equivalentes, insertando, actualizando o eliminando filas en las tablas correspondientes. Estas acciones se pueden invalidar por su propia lógica personalizada si lo desea, pero el orden de envío se organiza mediante un servicio de DataContext conocido como procesador de cambios.
Lo primero que sucede cuando se llama a SubmitChanges() es que se examina el conjunto de objetos conocidos para determinar si se han adjuntado nuevas instancias a ellas. Estas nuevas instancias se agregan al conjunto de objetos con seguimiento. A continuación, todos los objetos con cambios pendientes se ordenan en una secuencia de objetos en función de las dependencias entre ellos. Esos objetos cuyos cambios dependen de otros objetos se secuencian después de sus dependencias. Las restricciones de clave externa y las restricciones de unicidad en la base de datos desempeñan un papel importante en la determinación del orden correcto de los cambios. A continuación, justo antes de que se transmitan los cambios reales, se inicia una transacción para encapsular la serie de comandos individuales a menos que ya esté dentro del ámbito. Por último, uno por uno los cambios realizados en los objetos se traducen en comandos SQL y se envían al servidor.
En este momento, los errores detectados por la base de datos harán que se anule el proceso de envío y se producirá una excepción. Todos los cambios realizados en la base de datos se revertirán como si no se hubiera realizado ninguno de los envíos. DataContext seguirá teniendo una grabación completa de todos los cambios, por lo que es posible intentar rectificar el problema y volver a enviarlos llamando a SubmitChanges().
C#
Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
try {
db.SubmitChanges();
}
catch (Exception e) {
// make some adjustments
...
// try again
db.SubmitChanges();
}
Visual Basic
Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
Try
db.SubmitChanges()
Catch e As Exception
' make some adjustments
...
' try again
db.SubmitChanges()
End Try
Cuando la transacción alrededor del envío se complete correctamente, DataContext aceptará los cambios en los objetos olvidando simplemente la información de seguimiento de cambios.
Cambios simultáneos
Hay varias razones por las que se puede producir un error en una llamada a SubmitChanges(). Es posible que haya creado un objeto con una clave principal no válida; uno que ya está en uso o con un valor que infringe alguna restricción check de la base de datos. Estos tipos de comprobaciones son difíciles de crear en la lógica de negocios, ya que a menudo requieren un conocimiento absoluto del estado completo de la base de datos. Sin embargo, el motivo más probable de error es simplemente que otra persona haya realizado cambios en los objetos antes de usted.
Sin duda, esto sería imposible si bloqueara cada objeto de la base de datos y usara una transacción totalmente serializada. Sin embargo, este estilo de programación (simultaneidad pesimista) rara vez se usa, ya que es costoso y los verdaderos enfrentamientos rara vez se producen. La forma más popular de administrar cambios simultáneos es emplear una forma de simultaneidad optimista. En este modelo, no se toman bloqueos en las filas de la base de datos. Esto significa que se podría haber producido cualquier número de cambios en la base de datos entre la primera vez que recuperó los objetos y la hora en que envió los cambios.
Por lo tanto, a menos que desee ir con una directiva que gana la última actualización, borrando lo que haya ocurrido antes de usted, es probable que desee recibir una alerta sobre el hecho de que alguien más cambió los datos subyacentes.
DataContext tiene compatibilidad integrada con la simultaneidad optimista mediante la detección automática de conflictos de cambios. Las actualizaciones individuales solo se realizan correctamente si el estado actual de la base de datos coincide con el estado en el que entendió que los datos se encuentran cuando recuperó por primera vez los objetos. Esto sucede por objeto, solo se le avisa de las infracciones si se producen en objetos a los que se han realizado cambios.
Puede controlar el grado en que DataContext detecta conflictos de cambios al definir las clases de entidad. Cada atributo Column tiene una propiedad denominada UpdateCheck que se puede asignar a uno de los tres valores: Always, Never y WhenChanged. Si no se establece el valor predeterminado para un atributo Column es Always, lo que significa que los valores de datos representados por ese miembro siempre se comprueban si hay conflictos, es decir, a menos que haya un desempate obvio como una marca de versión. Un atributo Column tiene una propiedad IsVersion que permite especificar si el valor de datos constituye una marca de versión mantenida por la base de datos. Si existe una versión, la versión se usa solo para determinar si se ha producido un conflicto.
Cuando se produce un conflicto de cambios, se producirá una excepción como si fuera cualquier otro error. La transacción que rodea el envío se anulará, pero DataContext seguirá siendo la misma, lo que le permitirá rectificar el problema e intentarlo de nuevo.
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
Si va a realizar cambios en un servidor o de nivel intermedio, lo más sencillo que puede hacer para rectificar un conflicto de cambios es simplemente empezar de nuevo e intentarlo de nuevo, volver a crear el contexto y volver a aplicar los cambios. En la sección siguiente se describen opciones adicionales.
Transacciones
Una transacción es un servicio proporcionado por bases de datos o cualquier otro administrador de recursos que se pueda usar para garantizar que se produzca automáticamente una serie de acciones individuales; lo que significa que todos tienen éxito o todos no lo hacen. Si no lo hacen, también se deshacieron automáticamente antes de que se permita que ocurra nada más. Si no hay ninguna transacción en el ámbito, DataContext iniciará automáticamente una transacción de base de datos para proteger las actualizaciones al llamar a SubmitChanges().
Puede optar por controlar el tipo de transacción usada, su nivel de aislamiento o lo que realmente abarca iniciando el mismo. El aislamiento de transacción que usará DataContext se conoce como ReadCommitted.
C#
Product prod = db.Products.Single(p => p.ProductID == 15);
if (prod.UnitsInStock > 0)
prod.UnitsInStock--;
using(TransactionScope ts = new TransactionScope()) {
db.SubmitChanges();
ts.Complete();
}
Visual Basic
Dim product = (From prod In db.Products _
Where prod.ProductID = 15).First
If product.UnitsInStock > 0) Then
product.UnitsInStock -= 1
End If
Using ts As TransactionScope = New TransactionScope())
db.SubmitChanges()
ts.Complete()
End Using
En el ejemplo anterior se inicia una transacción totalmente serializada mediante la creación de un nuevo objeto de ámbito de transacción. La transacción protegerá todos los comandos de base de datos ejecutados dentro del ámbito de la transacción.
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
Esta versión modificada del mismo ejemplo usa el método ExecuteCommand() en DataContext para ejecutar un procedimiento almacenado en la base de datos justo antes de enviar los cambios. Independientemente de lo que hace el procedimiento almacenado en la base de datos, podemos estar seguros de que sus acciones forman parte de la misma transacción.
Si la transacción se completa correctamente, DataContext inicia toda la información de seguimiento acumulada y trata los nuevos estados de las entidades sin cambios. Sin embargo, no revierte los cambios en los objetos si se produce un error en la transacción. Esto le permite la máxima flexibilidad en el tratamiento de problemas durante el envío de cambios.
También es posible usar una transacción SQL local en lugar del nuevo TransactionScope. LINQ to SQL ofrece esta funcionalidad para ayudarle a integrar LINQ to SQL características en aplicaciones de ADO.NET preexistentes. Sin embargo, si va a esta ruta, tendrá que ser responsable de mucho más.
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
Como puede ver, el uso de una transacción de base de datos controlada manualmente es un poco más implicado. No solo tiene que iniciarlo usted mismo, debe indicar explícitamente a DataContext que lo use asignando a la propiedad Transaction . A continuación, debe usar un bloque try-catch para encapsular la lógica de envío, recordar indicar explícitamente a la transacción que se confirme y para indicar explícitamente a DataContext que acepte los cambios o para anular las transacciones si se produce un error en cualquier momento. Además, no olvide volver a establecer la propiedad Transaction en NULL cuando haya terminado.
Procedimientos almacenados
Cuando se llama a SubmitChanges(), LINQ to SQL genera y ejecuta comandos SQL para insertar, actualizar y eliminar filas en la base de datos. Los desarrolladores de aplicaciones pueden invalidar estas acciones y, en su lugar, el código personalizado se puede usar para realizar las acciones deseadas. De este modo, el procesador de cambios puede invocar automáticamente las instalaciones alternativas como los procedimientos almacenados en la base de datos.
Considere un procedimiento almacenado para actualizar las unidades en existencias de la tabla Products de la base de datos de ejemplo Northwind. La declaración SQL del procedimiento es la siguiente.
SQL
create proc UpdateProductStock
@id int,
@originalUnits int,
@decrement int
as
Puede usar el procedimiento almacenado en lugar del comando de actualización generado automáticamente normalmente definiendo un método en su DataContext fuertemente tipado. Incluso si la clase DataContext se está generando automáticamente mediante la herramienta de generación de código LINQ to SQL, todavía puede especificar estos métodos en una clase parcial propia.
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
La firma del método y el parámetro genérico indican a DataContext que use este método en lugar de una instrucción de actualización generada. Los parámetros originales y actuales se usan LINQ to SQL para pasar las copias originales y actuales del objeto del tipo especificado. Los dos parámetros están disponibles para la detección de conflictos de simultaneidad optimista.
Nota Si invalida la lógica de actualización predeterminada, la detección de conflictos es su responsabilidad.
El procedimiento almacenado UpdateProductStock se invoca mediante el método ExecuteCommand() de DataContext. Devuelve el número de filas afectadas y tiene la firma siguiente:
C#
public int ExecuteCommand(string command, params object[] parameters);
Visual Basic
Public Function ExecuteCommand(command As String, _
ParamArray parameters() As Object) As Integer
La matriz de objetos se usa para pasar parámetros necesarios para ejecutar el comando.
De forma similar al método update, se pueden especificar métodos de inserción y eliminación. Los métodos de inserción y eliminación solo toman un parámetro del tipo de entidad que se va a actualizar. Por ejemplo, los métodos para insertar y eliminar una instancia de Product se pueden especificar de la siguiente manera:
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) ...
Clases de entidad In-Depth
Utilizar atributos
Una clase de entidad es igual que cualquier clase de objeto normal que pueda definir como parte de la aplicación, salvo que se anota con información especial que la asocia a una tabla de base de datos determinada. Estas anotaciones se realizan como atributos personalizados en la declaración de clase. Los atributos solo son significativos cuando se usa la clase junto con LINQ to SQL. Son similares a los atributos de serialización XML en .NET Framework. Estos atributos de "datos" proporcionan LINQ to SQL con suficiente información para traducir las consultas de los objetos en consultas SQL en la base de datos y cambios en los objetos en comandos de inserción, actualización y eliminación de SQL.
También es posible representar la información de asignación mediante un archivo de asignación XML en lugar de atributos. Este escenario se describe con más detalle en la sección Asignación externa.
Atributo Database
El atributo Database se usa para especificar el nombre predeterminado de la base de datos si la conexión no la proporciona. Los atributos de base de datos se pueden aplicar a declaraciones DataContext fuertemente tipadas. Este atributo es opcional.
Atributo Database
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Especifica el nombre de la base de datos. La información solo se usa si la propia conexión no especifica el nombre de la base de datos. Si este atributo Database no existe en la declaración de contexto y no se especifica uno mediante la conexión, se supone que la base de datos tiene el mismo nombre que la clase de contexto. |
C#
[Database(Name="Database#5")]
public class Database5 : DataContext {
...
}
Visual Basic
<Database(Name:="Database#5")> _
Public Class Database5
Inherits DataContext
...
End Class
Atributo table
El atributo Table se usa para designar una clase como una clase de entidad asociada a una tabla de base de datos. Las clases con el atributo Table se tratarán especialmente mediante LINQ to SQL.
Atributo table
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Especifica el nombre de la tabla. Si no se especifica esta información, se supone que la tabla tiene el mismo nombre que la clase de entidad. |
C#
[Table(Name="Customers")]
public class Customer {
...
}
Visual Basic
<Table(Name:="Customers")> _
Public Class Customer
...
End Class
Atributo Column
El atributo Column se usa para designar un miembro de una clase de entidad que representa una columna en una tabla de base de datos. Se puede aplicar a cualquier campo o propiedad, público, privado o interno. Solo se conservan los miembros identificados como columnas cuando LINQ to SQL guarda los cambios en la base de datos.
Atributo column
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Nombre de la columna en la tabla o vista. Si no se especifica, se supone que la columna tiene el mismo nombre que el miembro de clase. |
Almacenamiento | String | Nombre del almacenamiento subyacente. Si se especifica, indica LINQ to SQL cómo omitir el descriptor de acceso de la propiedad pública para el miembro de datos e interactuar con el propio valor sin procesar. Si no se especifica LINQ to SQL obtiene y establece el valor mediante el descriptor de acceso público. |
Dbtype | String | Tipo de columna de base de datos especificada mediante tipos de base de datos y modificadores. Este será el texto exacto que se usa para definir la columna en un comando de declaración de tabla de T-SQL. Si no se especifica el tipo de columna de base de datos se deduce del tipo de miembro. El tipo de base de datos específico solo es necesario si se espera que se use el método CreateDatabase() para crear una instancia de la base de datos. |
IsPrimaryKey | Bool | Si se establece en true, el miembro de clase representa una columna que forma parte de la clave principal de la tabla. Si se designa más de un miembro de la clase como identificador, se dice que la clave principal es una composición de las columnas asociadas. |
IsDbGenerated | Boolean | Identifica que el valor de columna del miembro se genera automáticamente mediante la base de datos. Las claves principales designadas como IsDbGenerated=true también deben tener un DBType con el modificador IDENTITY .
IsDbGenerated los miembros se sincronizan inmediatamente después de insertar la fila de datos y están disponibles después de que se complete SubmitChanges(). |
IsVersion | Boolean | Identifica el tipo de columna del miembro como una marca de tiempo de base de datos o un número de versión. Los números de versión se incrementan y la base de datos actualiza las columnas de marca de tiempo cada vez que se actualiza la fila asociada. Los miembros con IsVersion=true se sincronizan inmediatamente después de actualizar la fila de datos. Los nuevos valores son visibles después de que se complete SubmitChanges(). |
UpdateCheck | UpdateCheck | Determina cómo LINQ to SQL implementa la detección de conflictos de simultaneidad optimista. Si no se designa ningún miembro como IsVersion=true , la detección se realiza comparando los valores de miembro originales con el estado actual de la base de datos. Puede controlar qué miembros LINQ to SQL usan durante la detección de conflictos proporcionando a cada miembro un valor de enumeración UpdateCheck.
|
IsDiscriminator | Boolean | Determina si el miembro de clase contiene el valor discriminador de una jerarquía de herencia. |
Expression | String | No afecta a la operación de LINQ to SQL, pero se usa durante .CreateDatabase() como una expresión SQL sin procesar que representa la expresión de columna calculada. |
CanBeNull | Boolean | Indica que el valor puede contener el valor NULL. Esto suele deducirse del tipo CLR del miembro de entidad. Use este atributo para indicar que un valor de cadena se representa como una columna que no acepta valores NULL en la base de datos. |
AutoSync | AutoSync | Especifica si la columna se sincroniza automáticamente a partir del valor generado por la base de datos en los comandos de inserción o actualización. Los valores válidos para esta etiqueta son OnInsert, Always y Never. |
Una clase de entidad típica usará atributos Column en propiedades públicas y almacenará valores reales en campos privados.
C#
private string _city;
[Column(Storage="_city", DBType="NVarChar(15)")]
public string City {
get { ... }
set { ... }
}
Visual Basic
Private _city As String
<Column(Storage:="_city", DBType:="NVarChar(15)")> _
public Property City As String
Get
set
End Property
DbType solo se especifica para que el método CreateDatabase() pueda construir la tabla con el tipo más preciso. De lo contrario, no se usa el conocimiento de que la columna subyacente está limitada a 15 caracteres.
Los miembros que representan la clave principal de un tipo de base de datos a menudo se asociarán con valores generados automáticamente.
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
Si especifica dbType, asegúrese de incluir el modificador IDENTITY . LINQ to SQL no aumentará un DBType especificado personalizado. Sin embargo, si dbType no se especifica LINQ to SQL deducirá que se necesita el modificador IDENTITY al crear la base de datos a través del método CreateDatabase().
Del mismo modo, si la propiedad IsVersion es true, dbType debe especificar los modificadores correctos para designar un número de versión o una columna de marca de tiempo. Si no se especifica ningún DBType, LINQ to SQL deducirá los modificadores correctos.
Puede controlar el acceso a un miembro asociado a una columna generada automáticamente, una marca de versión o cualquier columna que quiera ocultar mediante la designación del nivel de acceso del miembro o incluso limitar el propio descriptor de acceso.
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
La propiedad CustomerID del pedido se puede hacer de solo lectura sin definir un descriptor de acceso set. LINQ to SQL todavía puede obtener y establecer el valor subyacente a través del miembro de almacenamiento.
También puede hacer que un miembro sea completamente inaccesible para el resto de la aplicación colocando un atributo Column en un miembro privado. Esto permite que la clase de entidad contenga información relevante para la lógica de negocios de la clase sin exponerla en general. Aunque los miembros privados forman parte de los datos traducidos, ya que son privados, no puede hacer referencia a ellos en una consulta integrada en lenguaje.
De forma predeterminada, todos los miembros se usan para realizar la detección de conflictos de simultaneidad optimista. Puede controlar si se usa un miembro determinado especificando su valor UpdateCheck .
C#
[Column(Storage="_city", UpdateCheck=UpdateCheck.WhenChanged)]
public string City {
get { ... }
set { ... }
}
Visual Basic
<Column(Storage:="_city", UpdateCheck:=UpdateCheck.WhenChanged)> _
Public Property City As String
Get
Set
End Property
En la tabla siguiente se muestran las asignaciones permitidas entre los tipos de base de datos y el tipo CLR correspondiente. Use esta tabla como guía para determinar qué tipo CLR se va a usar para representar una columna de base de datos determinada.
Tipos de base de datos y asignaciones permitidas de tipo CLR correspondientes
Tipo de base de datos | Tipo CLR de .NET | Comentarios |
---|---|---|
bit, tinyint, smallint, int, bigint | Bye, Int16, Uint16, Int32, Uint32, Int64, Uint64 | Conversiones perdidas posibles. Es posible que los valores no se realicen de ida y vuelta. |
bit | Boolean | |
decimal, numeric, smallmoney, money | Decimal | La diferencia de escala puede dar lugar a una conversión perdida. No puede ida y vuelta. |
real, float | Single, Double | Diferencias de precisión. |
char, varchar, text, nchar, nvarchar, ntext | String | Diferencias de configuración regional posibles. |
datetime, smalldatetime | DateTime | La precisión diferente puede causar problemas de conversión de pérdida y ida y vuelta. |
UNIQUEIDENTIFIER | Guid | Diferentes reglas de intercalación. Es posible que la ordenación no funcione según lo previsto. |
timestamp | Byte[] (Byte() en Visual Basic), Binary | La matriz de bytes se trata como un tipo escalar. El usuario es responsable de asignar un almacenamiento adecuado cuando se llama al constructor. Se considera inmutable y no se realiza un seguimiento de los cambios. |
binary, varbinary | Byte[] (Byte() en Visual Basic), Binary |
Atributo de asociación
El atributo Association se usa para designar una propiedad que representa una asociación de base de datos como una clave externa a una relación de clave principal.
Atributo de asociación
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Nombre de la asociación. Suele ser el mismo que el nombre de restricción de clave externa de la base de datos. Se usa cuando Se usa CreateDatabase() para crear una instancia de la base de datos con el fin de generar la restricción pertinente. También se usa para ayudar a distinguir varias relaciones en una sola clase de entidad que hace referencia a la misma clase de entidad de destino. En este caso, las propiedades de relación en los lados de la relación (si ambas están definidas) deben tener el mismo nombre. |
Almacenamiento | String | Nombre del miembro de almacenamiento subyacente. Si se especifica, indica LINQ to SQL cómo omitir el descriptor de acceso de la propiedad pública para el miembro de datos e interactuar con el propio valor sin procesar. Si no se especifica LINQ to SQL obtiene y establece el valor mediante el descriptor de acceso público. Se recomienda que todos los miembros de asociación sean propiedades con miembros de almacenamiento independientes identificados. |
ThisKey | String | Lista separada por comas de nombres de uno o varios miembros de esta clase de entidad que representan los valores de clave en este lado de la asociación. Si no se especifica, se supone que los miembros son los miembros que componen la clave principal. |
OtherKey | String | Lista separada por comas de nombres de uno o varios miembros de la clase de entidad de destino que representan los valores de clave en el otro lado de la asociación. Si no se especifica, se supone que los miembros son los miembros que componen la clave principal de la otra clase de entidad. |
IsUnique | Boolean | True si hay una restricción de unicidad en la clave externa, lo que indica una relación verdadera 1:1. Esta propiedad rara vez se usa como relaciones 1:1 casi imposibles de administrar dentro de la base de datos. En su mayoría, los modelos de entidad se definen mediante relaciones 1:n incluso cuando los desarrolladores de aplicaciones los tratan como 1:1. |
IsForeignKey | Boolean | True si el tipo de destino "otro" de la asociación es el elemento primario del tipo de origen. Con relaciones de clave externa a clave principal, el lado que contiene la clave externa es el secundario y el lado que contiene la clave principal es el elemento primario. |
DeleteRule | String | Se usa para agregar el comportamiento de eliminación a esta asociación. Por ejemplo, "CASCADE" agregaría "ON DELETE CASCADE" a la relación FK. Si se establece en NULL, no se agrega ningún comportamiento de eliminación. |
Las propiedades de asociación representan una sola referencia a otra instancia de clase de entidad o representan una colección de referencias. Las referencias singleton deben codificarse en la clase de entidad mediante el tipo de valor EntityRef<T> (EntityRef (OfT) en Visual Basic) para almacenar la referencia real. El tipo EntityRef es la forma en que LINQ to SQL habilita la carga diferida de referencias.
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
La propiedad pública se escribe como Customer, no EntityRef<Customer>. Es importante no exponer el tipo EntityRef como parte de la API pública, ya que las referencias a este tipo en una consulta no se traducirán a SQL.
Del mismo modo, una propiedad de asociación que representa una colección debe usar el tipo de colección EntitySet<T> (EntitySet(OfT) en Visual Basic) para almacenar la relación.
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
Sin embargo, dado que EntitySet T> (EntitySet<(OfT) en Visual Basic es una colección, es válido usar EntitySet como tipo de valor devuelto. También es válido ocultar el verdadero tipo de la colección, usando en su lugar la interfaz ICollection<T> (ICollection(OfT) en Visual Basic.
C#
class Customer
{
...
private EntitySet<Order> _Orders;
[Association(Name="FK_Orders_Customers", Storage="_Orders",
OtherKey="CustomerID")]
public ICollection<Order> Orders {
get { return this._Orders; }
set { this._Orders.Assign(value); }
}
}
Visual Basic
Class Customer
...
Private _orders As EntitySet(Of Order)
<Association(Name:="FK_Orders_Customers", _
Storage:="_Orders", OtherKey:="CustomerID")> _
public Property Orders() As ICollection (Of Order)
Get
Return _orders
End Get
Set (value As ICollection (Of Order))
_orders.Assign(value)
End Property
End Class
Asegúrese de usar el método Assign() en EntitySet si expone un establecedor público para la propiedad . Esto permite que la clase de entidad siga usando la misma instancia de colección, ya que es posible que ya esté vinculada al servicio de seguimiento de cambios.
Atributo ResultType
Este atributo especifica un tipo de elemento de una secuencia enumerable que se puede devolver de una función que se ha declarado para devolver la interfaz IMultipleResults . Este atributo se puede especificar más de una vez.
Atributo ResultType
Propiedad | Tipo | Descripción |
---|---|---|
Tipo | Tipo | Tipo de los resultados devueltos. |
Atributo StoredProcedure
El atributo StoredProcedure se usa para declarar que una llamada a un método definido en el tipo DataContext o Schema se traduce como una llamada a un procedimiento almacenado de base de datos.
Atributo StoredProcedure
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Nombre del procedimiento almacenado en la base de datos. Si no se especifica, se supone que el procedimiento almacenado tiene el mismo nombre que el método . |
Atributo de función
El atributo Function se usa para declarar que una llamada a un método definido en un DataContext o Schema se traduce como una llamada a una función escalar o con valores de tabla definidos por el usuario de una base de datos.
Atributo de función
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Nombre de la función en la base de datos. Si no se especifica, se supone que la función tiene el mismo nombre que el método. |
Atributo del parámetro
El atributo Parameter se usa para declarar una asignación entre un método y los parámetros de un procedimiento almacenado de base de datos o una función definida por el usuario.
Atributo de parámetro
Propiedad | Tipo | Descripción |
---|---|---|
Nombre | String | Nombre del parámetro en la base de datos. Si no se especifica, el parámetro se deduce del nombre del parámetro de método. |
Dbtype | String | Tipo de parámetro especificado mediante tipos de base de datos y modificadores. |
Atributo InheritanceMapping
El atributo InheritanceMapping se usa para describir la correspondencia entre un determinado código discriminador y un subtipo de herencia. Todos los atributos de InheritanceMapping usados para una jerarquía de herencia deben declararse en el tipo raíz de la jerarquía.
Atributo InheritanceMapping
Propety | Tipo | Descripción |
---|---|---|
Código | Object | Valor de código discriminador. |
Tipo | Tipo | Subtipo Herencia. Puede tratarse de cualquier tipo no abstracto en la jerarquía de herencia, incluido el tipo raíz. |
IsDefault | Boolean | Determina si el subtipo de herencia especificado es el tipo predeterminado construido cuando LINQ to SQL encuentra un código discriminador que no está definido por los atributos InheritanceMapping. Exactamente uno de los atributos InheritanceMapping debe declararse con IsDefault como true. |
Coherencia de grafos
Un gráfico es un término general para una estructura de datos de objetos que hacen referencia entre sí mediante referencias. Una jerarquía (o árbol) es una forma degenerada de grafo. Los modelos de objetos específicos del dominio a menudo describen una red de referencias que se describen mejor como un gráfico de objetos. El estado del gráfico de objetos es vitalmente importante para la estabilidad de la aplicación. Por eso es importante asegurarse de que las referencias dentro del grafo sigan siendo coherentes con las reglas de negocio o las restricciones definidas en la base de datos.
LINQ to SQL no administra automáticamente la coherencia de las referencias de relación. Cuando las relaciones son bidireccionales, un cambio en un lado de la relación debe actualizar automáticamente el otro. Tenga en cuenta que es poco habitual que los objetos normales se comporten de esta manera, por lo que es poco probable que haya diseñado los objetos de esta manera.
LINQ to SQL proporciona algunos mecanismos para facilitar este trabajo y un patrón para que siga para asegurarse de que está administrando las referencias correctamente. Las clases de entidad generadas por la herramienta de generación de código implementarán automáticamente los patrones correctos.
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
);}
El tipo EntitySet T> (EntitySet<(OfT) en Visual Basic) tiene un constructor que permite proporcionar dos delegados que se usarán como devoluciones de llamada; el primero cuando se agrega un elemento a la colección, el segundo cuando se quita. Como puede ver en el ejemplo, el código que especifique para estos delegados puede y debe escribirse para actualizar la propiedad de relación inversa. Así es como se cambia automáticamente la propiedad Customer en una instancia de Order cuando se agrega un pedido a la colección Orders de un cliente.
La implementación de la relación en el otro extremo no es tan fácil. EntityRef<T> (EntityRef(OfT) en Visual Basic) es un tipo de valor definido para contener la menor sobrecarga adicional de la referencia de objeto real como sea posible. No tiene espacio para un par de delegados. En su lugar, el código que administra la coherencia del grafo de las referencias singleton debe incrustarse en los propios descriptores de acceso de propiedad.
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
Eche un vistazo al establecedor. Cuando se cambia la propiedad Customer , la instancia de pedido se quita primero de la colección Orders del cliente actual y, a continuación, solo se agrega más tarde a la colección del nuevo cliente. Observe que antes de la llamada a Remove() se realiza la referencia de entidad real se establece en NULL. Esto se hace para evitar la recursividad cuando se llama al método Remove(). Recuerde que EntitySet usará delegados de devolución de llamada para asignar la propiedad Customer de este objeto a null. Lo mismo sucede justo antes de la llamada a Add(). La referencia de entidad real se actualiza al nuevo valor. Esto restringirá de nuevo cualquier recursividad potencial y, por supuesto, logrará la tarea del establecedor en primer lugar.
La definición de una relación uno a uno es muy similar a la definición de una relación uno a varios desde el lado de la referencia singleton. En lugar de llamar a Add()
y Remove(), se asigna un nuevo objeto o se asigna un valor NULL para anular la relación.
De nuevo, es fundamental que las propiedades de relación mantengan la coherencia del gráfico de objetos. Si el gráfico de objetos en memoria es incoherente con los datos de la base de datos, se genera una excepción en tiempo de ejecución cuando se llama al método SubmitChanges . Considere la posibilidad de usar la herramienta de generación de código para mantener el trabajo de coherencia.
Cambiar notificaciones
Los objetos pueden participar en el proceso de seguimiento de cambios. No es necesario que lo hagan, pero pueden reducir considerablemente la cantidad de sobrecarga necesaria para realizar un seguimiento de los posibles cambios en los objetos. Es probable que la aplicación recupere muchos más objetos de las consultas que se modificarán. Sin ayuda proactiva de los objetos, el servicio de seguimiento de cambios está limitado en la forma en que realmente puede realizar un seguimiento de los cambios.
Dado que no hay ningún servicio de interceptación verdadero en el tiempo de ejecución, el seguimiento formal no se produce realmente. En su lugar, las copias duplicadas de los objetos se almacenan cuando se recuperan por primera vez. Más adelante, al llamar a SubmitChanges(), estas copias se usan para compararlas con las que se le han dado. Si sus valores difieren, el objeto se ha modificado. Esto significa que cada objeto requiere dos copias en memoria incluso si nunca las cambia.
Una mejor solución es hacer que los propios objetos anuncien al servicio de seguimiento de cambios cuando realmente se cambian. Esto se puede lograr haciendo que el objeto implemente una interfaz que exponga un evento de devolución de llamada. Después, el servicio de seguimiento de cambios puede conectar cada objeto y recibir notificaciones cuando cambian.
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
Para ayudar a mejorar el seguimiento de cambios, las clases de entidad deben implementar la interfaz INotifyPropertyChanging . Solo requiere que defina un evento denominado PropertyChanging: el servicio de seguimiento de cambios se registra con el evento cuando los objetos entran en su posesión. Todo lo que debe hacer es generar este evento inmediatamente antes de cambiar el valor de una propiedad.
No olvide colocar también la misma lógica de generación de eventos en los establecedores de propiedades de relación. Para EntitySets, genere los eventos en los delegados que proporcione.
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
Herencia
LINQ to SQL admite la asignación de tabla única, en la que se almacena una jerarquía de herencia completa en una sola tabla de base de datos. La tabla contiene la unión plana de todas las columnas de datos posibles para toda la jerarquía y cada fila tiene valores NULL en las columnas que no son aplicables al tipo de la instancia representada por la fila. La estrategia de asignación de tabla única es la representación más simple de la herencia y presenta buenas características de rendimiento para muchas categorías diferentes de consultas.
Asignación
Para implementar esta asignación mediante LINQ to SQL, debe especificar los siguientes atributos y propiedades de atributo en la clase raíz de la jerarquía de herencia:
- Atributo [Table] (<Table> en Visual Basic).
- Atributo [InheritanceMapping] (<InheritanceMapping> en Visual Basic) para cada clase de la estructura de jerarquía. Para las clases no abstractas, este atributo debe definir una propiedad Code (un valor que aparece en la tabla de base de datos de la columna Discriminador de herencia para indicar a qué clase o subclase pertenecen esta fila de datos) y una propiedad Type (que especifica a qué clase o subclase significa el valor de clave).
- Propiedad IsDefault en un único atributo [InheritanceMapping] (<InheritanceMapping> en Visual Basic). Esta propiedad sirve para designar una asignación de "reserva" en caso de que el valor discriminador de la tabla de base de datos no coincida con ninguno de los valores de Código en las asignaciones de herencia.
- Una propiedad IsDiscriminator para un atributo [Column] (<Column> en Visual Basic), para indicar que se trata de la columna que contiene el valor Code para la asignación de herencia.
No se requieren atributos o propiedades especiales en las subclases. Tenga en cuenta especialmente que las subclases no tienen el atributo [Table] (<Table> in Visual Basic).
En el ejemplo siguiente, los datos contenidos en las subclases Car y Truck se asignan a la tabla de base de datos única Vehicle. (Para simplificar el ejemplo, el código de ejemplo usa campos en lugar de propiedades para la asignación de columnas).
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
El diagrama de clases aparece de la siguiente manera:
Figura 1. Diagrama de clases de vehículo
Al ver el diagrama de base de datos resultante en el Explorador de servidores, verá que todas las columnas se han asignado a una sola tabla, como se muestra aquí:
Ilustración 2. Columnas asignadas a una sola tabla
Tenga en cuenta que los tipos de las columnas que representan campos de los subtipos deben ser nullables o deben tener un valor predeterminado especificado. Esto es necesario para que los comandos de inserción se realicen correctamente.
Consultas
El código siguiente proporciona un tipo de cómo puede usar tipos derivados en las consultas:
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
Avanzadas
Puede expandir una jerarquía mucho más allá del ejemplo simple ya proporcionado.
Ejemplo 1
Esta es una jerarquía mucho más profunda y una consulta más compleja:
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)
Ejemplo 2
La jerarquía siguiente incluye interfaces:
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
Entre las posibles consultas se incluyen las siguientes:
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))
Temas avanzados
Creación de bases de datos
Dado que las clases de entidad tienen atributos que describen la estructura de las tablas y columnas de base de datos relacionales, es posible usar esta información para crear nuevas instancias de la base de datos. Puede llamar al método CreateDatabase() en DataContext para que LINQ to SQL construir una nueva instancia de base de datos con una estructura definida por los objetos. Hay muchas razones por las que es posible que quiera hacerlo: es posible que esté creando una aplicación que se instale automáticamente en un sistema cliente o una aplicación cliente que necesite una base de datos local para guardar su estado sin conexión. En estos escenarios, CreateDatabase() es ideal, especialmente si hay disponible un proveedor de datos conocido como SQL Server Express 2005.
Sin embargo, es posible que los atributos de datos no codifiquen todo lo relacionado con una estructura de base de datos existente. El contenido de las funciones definidas por el usuario, los procedimientos almacenados, los desencadenadores y las restricciones check no están representados por los atributos. La función CreateDatabase() solo creará una réplica de la base de datos con la información que conoce, que es la estructura de la base de datos y los tipos de columnas de cada tabla. Sin embargo, para una variedad de bases de datos, esto es suficiente.
A continuación se muestra un ejemplo de cómo crear una nueva base de datos denominada MyDVDs.mdf:
C#
[Table(Name="DVDTable")]
public class DVD
{
[Column(Id = true)]
public string Title;
[Column]
public string Rating;
}
public class MyDVDs : DataContext
{
public Table<DVD> DVDs;
public MyDVDs(string connection) : base(connection) {}
}
Visual Basic
<Table(Name:="DVDTable")> _
Public Class DVD
<Column(Id:=True)> _
public Title As String
<Column> _
Public Rating As String
End Class
Public Class MyDVDs
Inherits DataContext
Public DVDs As Table(Of DVD)
Public Sub New(connection As String)
End Class
El modelo de objetos se puede usar para crear una base de datos mediante SQL Server Express 2005 de la siguiente manera:
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 también proporciona una API para quitar una base de datos existente antes de crear una nueva. El código de creación de la base de datos anterior se puede modificar para comprobar primero una versión existente de la base de datos mediante DatabaseExists() y, a continuación, quitarla mediante DeleteDatabase().
C#
MyDVDs db = new MyDVDs("c:\\mydvds.mdf");
if (db.DatabaseExists()) {
Console.WriteLine("Deleting old database...");
db.DeleteDatabase();
}
db.CreateDatabase();
Visual Basic
Dim db As MyDVDs = New MyDVDs("c:\mydvds.mdf")
If (db.DatabaseExists()) Then
Console.WriteLine("Deleting old database...")
db.DeleteDatabase()
End If
db.CreateDatabase()
Después de la llamada a CreateDatabase(), la nueva base de datos puede aceptar consultas y comandos como SubmitChanges() para agregar objetos al archivo MDF.
También es posible usar CreateDatabase() con una SKU distinta de SQL Server Express, mediante un archivo MDF o simplemente un nombre de catálogo. Todo depende de lo que use para la cadena de conexión. La información de la cadena de conexión se usa para definir la base de datos que existirá, no necesariamente una que ya exista. LINQ to SQL pescará los fragmentos de información pertinentes y lo usará para determinar qué base de datos crear y en qué servidor crearla. Por supuesto, necesitará derechos de administrador de bases de datos o equivalentes en el servidor para hacerlo.
Interoperación con ADO.NET
LINQ to SQL forma parte de la familia de tecnologías ADO.NET. Se basa en los servicios proporcionados por el modelo de proveedor de ADO.NET, por lo que es posible mezclar LINQ to SQL código con aplicaciones ADO.NET existentes.
Al crear una LINQ to SQL DataContext, puede proporcionarla con una conexión ADO.NET existente. Todas las operaciones en DataContext (incluidas las consultas) usarán la conexión que proporcionó. Si la conexión ya se abrió LINQ to SQL respetará su autoridad sobre la conexión y la dejará tal cual cuando termine con ella. Normalmente, LINQ to SQL cierra su conexión en cuanto finaliza una operación a menos que una transacción esté en el ámbito.
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
Siempre puede acceder a la conexión que usa DataContext a través de la propiedad Connection y cerrarla usted mismo.
C#
db.Connection.Close();
Visual Basic
db.Connection.Close()
También puede proporcionar DataContext con su propia transacción de base de datos, en caso de que la aplicación ya haya iniciado una y desee que DataContext juegue junto con ella.
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
Cada vez que se establece una transacción , DataContext la usará cada vez que emite una consulta o ejecuta un comando. No olvide volver a asignar la propiedad a NULL cuando haya terminado.
Sin embargo, el método preferido para realizar transacciones con .NET Framework es usar el objeto TransactionScope . Permite realizar transacciones distribuidas que funcionen entre bases de datos y otros administradores de recursos residentes en memoria. La idea es que los ámbitos de transacción empiezan a ser baratos, solo se promueven a la totalidad de la transacción distribuida cuando realmente hacen referencia a varias bases de datos o a varias conexiones dentro del ámbito de la transacción.
C#
using(TransactionScope ts = new TransactionScope()) {
db.SubmitChanges();
ts.Complete();
}
Visual Basic
Using ts As TransactionScope= New TransactionScope()
db.SubmitChanges()
ts.Complete()
End Using
Ejecutar instrucciones SQL directamente
Las conexiones y las transacciones no son la única manera de interoperar con ADO.NET. Es posible que, en algunos casos, la consulta o la instalación de envío de cambios de DataContext no sea suficiente para la tarea especializada que puede que desee realizar. En estas circunstancias, es posible usar DataContext para emitir comandos SQL sin procesar directamente a la base de datos.
El método ExecuteQuery() le permite ejecutar una consulta SQL sin procesar y convierte el resultado de la consulta directamente en objetos. Por ejemplo, suponiendo que los datos de la clase Customer se reparten en dos tablas customer1 y customer2, la consulta siguiente devuelve una secuencia de objetos Customer .
C#
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
@"select c1.custid as CustomerID, c2.custName as ContactName
from customer1 as c1, customer2 as c2
where c1.custid = c2.custid"
);
Visual Basic
Dim results As IEnumerable(Of Customer) = _
db.ExecuteQuery(Of Customer)( _
"select c1.custid as CustomerID, " & _
"c2.custName as ContactName " & _
"from customer1 as c1, customer2 as c2 "& _
"where c1.custid = c2.custid" )
Siempre que los nombres de columna de los resultados tabulares coincidan con las propiedades de columna de la clase de entidad LINQ to SQL materializarán los objetos de cualquier consulta SQL.
El método ExecuteQuery() también permite parámetros. En el código siguiente, se ejecuta una consulta con parámetros:
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" )
Los parámetros se expresan en el texto de la consulta con la misma notación curly usada por Console.WriteLine() y String.Format(). De hecho, realmente se llama a String.Format() en la cadena de consulta que se proporciona, sustituyendo los parámetros con llave con nombres de parámetro generados como p0, @p1 ..., p(n).
Cambiar resolución de conflictos
Descripción
Se produce un conflicto de cambios cuando el cliente intenta enviar cambios a un objeto y uno o varios valores usados en la comprobación de actualización se han actualizado en la base de datos desde que el cliente los leyó por última vez.
Nota Solo los miembros asignados como UpdateCheck.Always o UpdateCheck.WhenChanged participan en comprobaciones de simultaneidad optimista. No se realiza ninguna comprobación para los miembros marcados como UpdateCheck.Never.
La resolución de este conflicto incluye detectar qué miembros del objeto están en conflicto y, a continuación, decidir qué hacer sobre él. Tenga en cuenta que la simultaneidad optimista podría no ser la mejor estrategia en su situación concreta. A veces es perfectamente razonable "dejar que la última actualización gane".
Detección, creación de informes y resolución de conflictos en LINQ to SQL
La resolución de conflictos es el proceso de actualizar un elemento en conflicto consultando de nuevo la base de datos y reconciliando las diferencias. Cuando se actualiza un objeto, el seguimiento de cambios tiene los valores originales antiguos y los nuevos valores de la base de datos. LINQ to SQL determina si el objeto está en conflicto o no. Si es así, LINQ to SQL determina qué miembros están implicados. Si el nuevo valor de la base de datos de un miembro es diferente del original anterior (que se usó para la comprobación de actualización que produjo un error), se trata de un conflicto. Los conflictos de miembros se agregan a una lista de conflictos.
Por ejemplo, en el escenario siguiente, User1 comienza a preparar una actualización consultando la base de datos de una fila. Para que User1 pueda enviar los cambios, User2 ha cambiado la base de datos. Se produce un error en el envío de User1 porque los valores esperados para col B y Col C han cambiado.
Conflicto de actualización de base de datos
Usuario | Col A | Col B | Col C |
---|---|---|---|
Estado original | Alfreds | Maria | Ventas |
Usuario 1 | Alfred | Marketing | |
Usuario 2 | Mary | Servicio |
En LINQ to SQL, los objetos que no se actualizan debido a conflictos de simultaneidad optimista hacen que se inicie una excepción (ChangeConflictException). Puede especificar si la excepción debe iniciarse en el primer error o si se deben intentar todas las actualizaciones con los errores que se acumulan y notifican en la excepción.
// [C#]
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
db.SubmitChanges(ConflictMode.ContinueOnConflict);
' [Visual Basic]
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Cuando se produce, la excepción proporciona acceso a una colección ObjectChangeConflict . Los detalles están disponibles para cada conflicto (asignado a un único intento de actualización con error), incluido el acceso a la lista MemberConflicts . Cada conflicto de miembro se asigna a un único miembro en la actualización que no pasó la comprobación de simultaneidad.
Control de conflictos
En el escenario anterior, User1 tiene las opciones RefreshMode que se describen a continuación para conciliar las diferencias antes de intentar volver a enviar. En todos los casos, el registro del cliente se "actualiza" primero mediante la extracción de los datos actualizados de la base de datos. Esta acción garantiza que el siguiente intento de actualización no producirá un error en las mismas comprobaciones de simultaneidad.
Aquí, User1 elige combinar valores de base de datos con los valores de cliente actuales para que los valores de la base de datos se sobrescriban solo cuando el conjunto de cambios actual también haya modificado ese valor. (Vea el ejemplo 1 más adelante en esta sección).
En el escenario anterior, después de la resolución de conflictos, el resultado en la base de datos es el siguiente:
KeepChanges
Col A | Col B | Col C | |
---|---|---|---|
KeepChanges | Alfred (Usuario 1) | Mary (Usuario 2) | Marketing (Usuario 1) |
- Col A: Aparece el cambio de User1 (Alfred).
- Col B: Aparece el cambio del usuario2 (Mary). Este valor se combinó porque User1 no lo ha cambiado.
- Col C: Aparece el cambio de Usuario1 (Marketing). El cambio de User2 (servicio) no se combina porque User1 también ha cambiado ese elemento.
A continuación, User1 elige sobrescribir los valores de base de datos con los valores actuales. (Vea el ejemplo 2 más adelante en esta sección).
Después de la actualización, se envían los cambios de User1. El resultado de la base de datos es el siguiente:
KeepCurrentValues
Col A | Col B | Col C | |
---|---|---|---|
KeepCurrentValues | Alfred (Usuario 1) | Maria (original) | Marketing (Usuario 1) |
- Col A: Aparece el cambio de User1 (Alfred).
- Col B: La María original permanece; El cambio de User2 se descarta.
- Col C: Aparece el cambio de Usuario1 (Marketing). El cambio de User2 (servicio) se descarta.
En el escenario siguiente, User1 elige permitir que los valores de la base de datos sobrescriban los valores actuales en el cliente. (Vea el ejemplo 3 más adelante en esta sección).
En el escenario anterior, después de la resolución de conflictos, el resultado en la base de datos es el siguiente:
OverwriteCurrentValues
Col A | Col B | Col C | |
---|---|---|---|
OverwriteCurrentValues | Alfreds (original) | Mary (Usuario 2) | Servicio (usuario 2) |
- Col A: El valor original (Alfreds) permanece; El valor de User1 (Alfred) se descarta.
- Col B: Aparece el cambio de Usuario2 (Mary).
- Col C: Aparece el cambio del usuario2 (servicio). El cambio de User1 (Marketing) se descarta.
Una vez resueltos los conflictos, puede intentar volver a enviar. Dado que esta segunda actualización también podría producir un error, considere la posibilidad de usar un bucle para los intentos de actualización.
Ejemplos
Los siguientes extractos de código muestran diversos miembros informativos y técnicas a su disposición para detectar y resolver conflictos de miembros.
Ejemplo 1
En este ejemplo, los conflictos se resuelven "automáticamente". Es decir, los valores de base de datos se combinan con los valores de cliente actuales a menos que el cliente también haya cambiado ese valor (KeepChanges). No se realiza ninguna inspección ni control personalizado de conflictos de miembros individuales.
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)
Ejemplo 2
En este ejemplo, los conflictos se resuelven de nuevo sin ningún control personalizado. Pero esta vez, los valores de la base de datos no se combinan en los valores de cliente actuales.
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
Ejemplo 3
Aquí de nuevo, no se realiza ningún control personalizado. Pero en este caso, todos los valores de cliente se actualizan con los valores de base de datos actuales.
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
Ejemplo 4
En este ejemplo se muestra una manera de acceder a la información sobre una entidad en conflicto.
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
Ejemplo 5
En este ejemplo se agrega un bucle a través de los miembros individuales. Aquí puede proporcionar un control personalizado de cualquier miembro.
Nota Agregar mediante System.Reflection; para proporcionar MemberInfo.
C#
try {
user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
Console.WriteLine("Optimistic concurrency error");
Console.ReadLine();
foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
ITable table = cc.Table;
Customers entityInConflict = (Customers)cc.Object;
Console.WriteLine("Table name: {0}", table.Name);
Console.Write("Customer ID: ");
Console.WriteLine(entityInConflict.CustomerID);
foreach (MemberChangeConflict mc in cc.MemberConflicts) {
object currVal = mc.CurrentValue;
object origVal = mc.OriginalValue;
object databaseVal = mc.DatabaseValue;
MemberInfo mi = mc. Member;
Console.WriteLine("Member: {0}", mi.Name);
Console.WriteLine("current value: {0}", currVal);
Console.WriteLine("original value: {0}", origVal);
Console.WriteLine("database value: {0}", databaseVal);
Console.ReadLine();
}
}
}
Visual Basic
Try
user1.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
Console.WriteLine("Optimistic concurrency error")
Console.ReadLine()
For Each cc As ObjectChangeConflict In context.ChangeConflicts
Dim table As ITable = cc.Table
Dim entityInConflict As Customers = CType(cc.Object, Customers)
Console.WriteLine("Table name: {0}", table.Name)
Console.Write("Customer ID: ")
Console.WriteLine(entityInConflict.CustomerID)
For Each mc As MemberChangeConflict In cc.MemberConflicts
Dim currVal As Object = mc.CurrentValue
Dim origVal As Object = mc.OriginalValue
Dim databaseVal As Object = mc.DatabaseValue
Dim mi As MemberInfo = mc.Member
Console.WriteLine("Member: {0}", mi.Name)
Console.WriteLine("current value: {0}", currVal)
Console.WriteLine("original value: {0}", origVal)
Console.WriteLine("database value: {0}", databaseVal)
Console.ReadLine()
Next
Next
End Try
Invocación de procedimientos almacenados
LINQ to SQL admite los procedimientos almacenados y las funciones definidas por el usuario. LINQ to SQL asigna estas abstracciones definidas por la base de datos a objetos de cliente generados por código, de modo que pueda acceder a ellas de forma fuertemente tipada desde el código de cliente. Puede detectar fácilmente estos métodos mediante IntelliSense y las firmas de método son lo más parecidas posible a las firmas de los procedimientos y funciones definidos en la base de datos. Un conjunto de resultados devuelto por una llamada a un procedimiento asignado es una colección fuertemente tipada. LINQ to SQL puede generar automáticamente los métodos asignados, pero también admite la asignación manual en situaciones en las que decide no usar la generación de código.
LINQ to SQL asigna procedimientos almacenados y funciones a métodos mediante el uso de atributos. Los atributos StoredProcedure, Parameter y Function admiten una propiedad Name y el atributo Parameter también admiten una propiedad DBType . Estos son dos ejemplos:
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
En los ejemplos siguientes se muestran asignaciones para varios tipos de procedimientos almacenados.
Ejemplo 1
El siguiente procedimiento almacenado toma un único parámetro de entrada y devuelve un entero:
CREATE PROCEDURE GetCustomerOrderCount(@CustomerID nchar(5))
AS
Declare @count int
SELECT @count = COUNT(*) FROM ORDERS WHERE CustomerID = @CustomerID
RETURN @count
El método asignado sería el siguiente:
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
Ejemplo 2
Cuando un procedimiento almacenado puede devolver varias formas de resultados, el tipo de valor devuelto no puede estar fuertemente tipado para una forma de proyección única. En el ejemplo siguiente, la forma de resultado depende de la entrada:
CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
select OrderID, ShipName from orders
El método asignado es el siguiente:
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
Puede usar este procedimiento almacenado de la siguiente manera:
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
}
Aquí debe usar el patrón GetResult para obtener un enumerador del tipo correcto, en función del conocimiento del procedimiento almacenado. LINQ to SQL puede generar todos los tipos de proyección posibles, pero no tiene forma de saber en qué orden se devolverán. La única manera de saber qué tipos de proyección generados corresponden a un método asignado es mediante el uso de comentarios de código generados en los métodos .
Ejemplo 3
Este es el T-SQL de un procedimiento almacenado que devuelve varias formas de resultado secuencialmente:
CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers
LINQ to SQL asignaría este procedimiento igual que en el ejemplo 2 anterior. Sin embargo, en este caso hay dos conjuntos de resultados secuenciales .
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
Puede usar este procedimiento almacenado de la siguiente manera:
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
Ejemplo 4
LINQ to SQL asigna out
parámetros para hacer referencia a parámetros (palabra clave ref) y para los tipos de valor declara el parámetro como que acepta valores NULL (por ejemplo, int?). El procedimiento del ejemplo siguiente toma un único parámetro de entrada y devuelve un out
parámetro .
CREATE PROCEDURE GetCustomerCompanyName(
@customerID nchar(5),
@companyName nvarchar(40) output
)
AS
SELECT @companyName = CompanyName FROM Customers
WHERE CustomerID=@CustomerID
El método asignado es el siguiente:
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
En este caso, el método no tiene un valor devuelto explícito, pero el valor devuelto predeterminado se asigna de todos modos. Para el parámetro de salida, se usa un parámetro de salida correspondiente según lo previsto.
Llamaría al procedimiento almacenado anterior de la siguiente manera:
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)
Funciones definidas por el usuario
LINQ to SQL admite funciones escalares y con valores de tabla, y admite el homólogo en línea de ambos.
LINQ to SQL controla las llamadas escalares insertadas de forma similar a la forma en que se llama a las funciones definidas por el sistema. Considere la siguiente consulta:
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)
Aquí, la llamada de método Math.Floor se traduce a una llamada a la función del sistema "FLOOR". De la misma manera, una llamada a una función que se asigna a una UDF se traduce en una llamada a la UDF en SQL.
Ejemplo 1
Esta es una función escalar definida por el usuario (UDF) ReverseCustName(). En SQL Server, es posible que la función se defina de la siguiente manera:
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
Puede asignar un método cliente definido en una clase de esquema a esta UDF mediante el código siguiente. Tenga en cuenta que el cuerpo del método construye una expresión que captura la intención de la llamada al método y pasa esa expresión a DataContext para la traducción y ejecución. (Esta ejecución directa solo se produce si se llama a la función).
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)
Ejemplo 2
En la consulta siguiente, puede ver una llamada insertada al método UDF generado ReverseCustName. En este caso, la función no se ejecuta inmediatamente. El CÓDIGO SQL creado para esta consulta se traduce en una llamada a la UDF definida en la base de datos (vea el código SQL que sigue a la consulta).
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]
Cuando se llama a la misma función fuera de una consulta, LINQ to SQL crea una consulta simple a partir de la expresión de llamada de método con la siguiente sintaxis SQL (donde el parámetro @p0
está enlazado a la constante pasada):
En LINQ to SQL:
C#
string str = db.ReverseCustName("LINQ to SQL");
Visual Basic
Dim str As String = db.ReverseCustName("LINQ to SQL")
Convierte en:
SELECT dbo.ReverseCustName(@p0)
Ejemplo 3
Una función con valores de tabla (TVF) devuelve un único conjunto de resultados (a diferencia de los procedimientos almacenados, que pueden devolver varias formas de resultados). Dado que el tipo de valor devuelto TVF es una tabla, puede usar un TVF en cualquier parte de SQL que pueda usar una tabla y puede tratar el TVF de la misma manera que lo haría con una tabla.
Tenga en cuenta la siguiente definición de SQL Server de una función con valores de tabla:
CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
SELECT ProductID, UnitPrice
FROM Products
WHERE UnitPrice > @cost
Esta función indica explícitamente que devuelve una tabla, por lo que la estructura del conjunto de resultados devuelto se define implícitamente. LINQ to SQL asigna la función de la siguiente manera:
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)
El siguiente código SQL muestra que puede unirse a la tabla devuelta por la función y, de lo contrario, tratarla como lo haría con cualquier otra tabla:
SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID
En LINQ to SQL, la consulta se representaría de la siguiente manera (mediante la nueva sintaxis 'join'):
C#
var q =
from p in db.ProductsCostingMoreThan(80.50m)
join s in db.Products on p.ProductID equals s.ProductID
select new {p.ProductID, s.UnitPrice};
Visual Basic
Dim productInfos = From costlyProd In db.ProductsCostingMoreThan(80.50m) _
Join prod In db.Products _
On costlyProd.ProductID Equals prod.ProductID _
Select costlyProd.ProductID, prod.UnitPrice
limitaciones de LINQ to SQL en procedimientos almacenados
LINQ to SQL admite la generación de código para los procedimientos almacenados que devuelven conjuntos de resultados determinados estáticamente. Por lo tanto, el generador de código LINQ to SQL no admite lo siguiente:
- Procedimientos almacenados que usan SQL dinámico para devolver conjuntos de resultados. Cuando un procedimiento almacenado contiene lógica condicional para compilar una instrucción SQL dinámica, LINQ to SQL no puede adquirir metadatos para el conjunto de resultados porque la consulta usada para generar el conjunto de resultados es desconocida hasta el tiempo de ejecución.
- Procedimientos almacenados que generan resultados basados en la tabla temporal.
Herramienta Generador de clases de entidad
Si tiene una base de datos existente, no es necesario crear un modelo de objetos completo a mano solo para representarlo. La distribución de LINQ to SQL viene con una herramienta denominada SQLMetal. Es una utilidad de línea de comandos que automatiza la tarea de crear clases de entidad mediante la inferencia de las clases adecuadas de los metadatos de la base de datos.
Puede usar SQLMetal para extraer metadatos SQL de una base de datos y generar un archivo de origen que contenga declaraciones de clase de entidad. Como alternativa, puede dividir el proceso en dos pasos, generando primero un archivo XML que representa los metadatos de SQL y, después, traduciendo ese archivo XML en un archivo de origen que contiene declaraciones de clase. Este proceso de división le permite conservar los metadatos como un archivo para que pueda editarlos. El proceso de extracción que produce el archivo realiza algunas inferencias a lo largo del camino sobre los nombres de propiedad y clase adecuados según los nombres de tabla y columna de la base de datos. Es posible que sea necesario editar el archivo XML para que el generador genere resultados más agradables o ocultar aspectos de la base de datos que no desea presentar en los objetos.
El escenario más sencillo para usar SQLMetal es generar directamente clases a partir de una base de datos existente. A continuación se muestra cómo invocar la herramienta:
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
Al ejecutar la herramienta se crea un archivo Northwind.cs o .vb que contiene el modelo de objetos generado mediante la lectura de los metadatos de la base de datos. Este uso funciona bien si los nombres de las tablas de la base de datos son similares a los nombres de los objetos que desea generar. Si no es así, querrá adoptar el enfoque de dos pasos.
Para indicar a SQLMetal que genere un archivo DBML, use la herramienta de la siguiente manera:
SqlMetal /server:.\SQLExpress /database:Northwind /pluralize
/xml:Northwind.dbml
Una vez generado el archivo dbml, puede continuar y anotarlo con el atributo class y property para describir cómo se asignan las tablas y columnas a las clases y propiedades. Una vez que haya terminado de anotar el archivo dbml, puede generar el modelo de objetos ejecutando el siguiente comando:
C#
SqlMetal /namespace:nwind /code:Northwind.cs Northwind.dbml
Visual Basic
SqlMetal /namespace:nwind /code:Northwind.vb Northwind.dbml /language:vb
La firma de uso de SQLMetal es la siguiente:
SqlMetal [options] [filename]
A continuación se muestra una tabla que muestra las opciones de línea de comandos disponibles para SQLMetal.
Opciones de línea de comandos para SQLMetal
Opción | Descripción |
---|---|
/server:<name> | Indica al servidor al que se va a conectar para acceder a la base de datos. |
/database:<name> | Indica el nombre de la base de datos desde la que se van a leer los metadatos. |
/user:<name> | Id. de usuario de inicio de sesión para el servidor. |
/password:<name> | Contraseña de inicio de sesión para el servidor. |
/views | Extraer vistas de base de datos. |
/functions | Extraer funciones de base de datos. |
/sprocs | Extraiga procedimientos almacenados. |
/code[:<filename>] | Indica que la salida de la herramienta es un archivo de origen de declaraciones de clase de entidad. |
/language:<lenguaje> | Use Visual Basic o C# (valor predeterminado). |
/xml[:<filename>] | Indica que la salida de las herramientas es un archivo DBML que describe los metadatos de la base de datos y la primera aproximación de los nombres de clase y propiedad. |
/map[:<filename>] | Indica que se debe usar un archivo de asignación externo en lugar de atributos. |
/pluralize | Indica que la herramienta debe realizar la pluralización o des pluralización del idioma inglés en los nombres de las tablas con el fin de generar los nombres de propiedad y clase adecuados. |
/namespace:<name> | Indica el espacio de nombres en el que se generarán las clases de entidad. |
/timeout:<segundos> | Valor de tiempo de espera en segundos que se va a usar para los comandos de base de datos. |
Nota Para extraer los metadatos de un archivo MDF, debe especificar el nombre del archivo MDF después de todas las demás opciones. Si no se especifica localhost /server.
Referencia de DBML de la herramienta Generador
El archivo DBML (lenguaje de asignación de base de datos) es principalmente una descripción de los metadatos de SQL para una base de datos determinada. SqlMetal lo extrae examinando los metadatos de la base de datos. SQLMetal también usa el mismo archivo para generar un modelo de objetos predeterminado para representar la base de datos.
Este es un ejemplo prototíptico de la sintaxis DBML:
<?xml version="1.0" encoding="utf-16"?>
<Database Name="Northwind" EntityNamespace="Mappings.FunctionMapping"
ContextNamespace="Mappings.FunctionMapping"
Provider="System.Data.Linq.SqlClient.Sql2005Provider"
xmlns="https://schemas.microsoft.com/dsltools/LINQ to SQLML">
<Table Name="Categories">
<Type Name="Category">
<Column Name="CategoryID" Type="System.Int32"
DbType="Int NOT NULL IDENTITY" IsReadOnly="False"
IsPrimaryKey="True" IsDbGenerated="True" CanBeNull="False" />
<Column Name="CategoryName" Type="System.String"
DbType="NVarChar(15) NOT NULL" CanBeNull="False" />
<Column Name="Description" Type="System.String"
DbType="NText" CanBeNull="True" UpdateCheck="Never" />
<Column Name="Picture" Type="System.Byte[]"
DbType="Image" CanBeNull="True" UpdateCheck="Never" />
<Association Name="FK_Products_Categories" Member="Products"
ThisKey="CategoryID" OtherKey="CategoryID"
OtherTable="Products" DeleteRule="NO ACTION" />
</Type>
</Table>
<Function Name="GetCustomerOrders">
<Parameter Name="customerID" Type="System.String" DbType="NChar(5)" />
<ElementType Name="GetCustomerOrdersResult">
<Column Name="OrderID" Type="System.Int32"
DbType="Int" CanBeNull="True" />
<Column Name="ShipName" Type="System.String"
DbType="NVarChar(40)" CanBeNull="True" />
<Column Name="OrderDate" Type="System.DateTime"
DbType="DateTime" CanBeNull="True" />
<Column Name="Freight" Type="System.Decimal"
DbType="Money" CanBeNull="True" />
</ElementType>
</Function>
</Database>
Los elementos y sus atributos se describen de la siguiente manera.
Base de datos
Este es el elemento más externo en formato XML. Este elemento se asigna de forma flexible al atributo Database en el DataContext generado.
Atributos de base de datos
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | None | El nombre de la base de datos. Si está presente y si genera dataContext, adjuntará un atributo Database a él con este nombre. También se usa como nombre de la clase DataContext si el atributo de clase no está presente. |
EntityNamespace | Alta | None | Espacio de nombres predeterminado para las clases generadas a partir de elementos Type dentro de elementos Table. Si no se especifica ningún espacio de nombres aquí, las clases de entidad se generan en el espacio de nombres raíz. |
ContextNamespace | String | None | Espacio de nombres predeterminado para la clase DataContext generada. Si no se especifica ningún espacio de nombres aquí, se genera la clase DataContext en el espacio de nombres raíz. |
Clase | String | Database.Name | Nombre de la clase DataContext generada. Si no existe, use el atributo Name del elemento Database. |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad de la clase DataContext generada. Los valores válidos son Public, Protected, Internal y Private. |
BaseType | String | "System.Data.Linq.DataContext" | Tipo base de la clase DataContext . |
Proveedor | String | "System.Data.Linq.SqlClient.Sql2005Provider" | El proveedor de DataContext, use el proveedor Sql2005 como valor predeterminado. |
ExternalMapping | Boolean | False | Especifique si dbml se usa para generar un archivo de asignación externo. |
Serialización | SerializationMode | SerializationMode.None | Especifique si las clases dataContext y entity generadas son serializables. |
Atributos de Sub-Element de base de datos
Sub-Element | Tipo de elemento | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Table> | Tabla | 0 sin enlazar | Representa una tabla o vista de SQL Server que se asignará a un solo tipo o a una jerarquía de herencia. |
<Function> | Función | 0 sin enlazar | Representa un SQL Server procedimiento almacenado o una función db que se asignará a un método de la clase DataContext generada. |
<Connection> | Conexión | 0-1 | Representa la conexión de base de datos que usará dataContext . |
Tabla
Este elemento representa una tabla de base de datos (o una vista) que se asignará a un solo tipo o a una jerarquía de herencia. Este elemento se asigna de forma flexible al atributo Table en la clase de entidad generada.
Atributos de tabla
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (obligatorio) | Nombre de la tabla dentro de la base de datos. Actúa como base del nombre predeterminado del adaptador de tabla, si es necesario. |
Miembro | String | Table.Name | Nombre del campo miembro generado para esta tabla dentro de la clase DataContext . |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad de la referencia de Tabla<T> dentro de DataContext. Los valores válidos son Public, Protected, Internal y Private. |
Atributos de Sub-Element de tabla
Sub-Element | Tipo de elemento | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Tipo> | Tipo | 1-1 | Representa el tipo o jerarquía de herencia asignado a esta tabla. |
<InsertFunction> | TableFunction | 0-1 | Método para insertar. Cuando está presente, se genera un método InsertT . |
<UpdateFunction> | TableFunction | 0-1 | Método para la actualización. Cuando está presente, se genera un método UpdateT . |
<DeleteFunction> | TableFunction | 0-1 | Método para eliminar. Cuando está presente, se genera un método DeleteT . |
Tipo
Este elemento representa una definición de tipo para una forma de resultado de un procedimiento almacenado o table. Esto generará código en un nuevo tipo CLR con las columnas y asociaciones especificadas.
El tipo también puede representar un componente de una jerarquía de herencia, con varios tipos que se asignan a la misma tabla. En este caso, los elementos Type se anidan para representar las relaciones de herencia de elementos primarios y secundarios y se diferencian en la base de datos mediante el código de herencia especificado.
Atributos de tipo
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (necesario) | Nombre del tipo CLR que se va a generar. |
InheritanceCode | String | None | Si este tipo participa en la herencia, puede tener un código de herencia asociado para distinguir entre los tipos CLR al cargar filas de la tabla. Tipo cuyo código de herencia coincide con el valor de la columna IsDiscriminator se usa para crear instancias del objeto cargado. Si el código de herencia no está presente, la clase de entidad generada es abstracta. |
IsInheritanceDefault | Boolean | False | Si esto es cierto para un tipo en una jerarquía de herencia, este tipo se usará al cargar filas que no coincidan con ningún código de herencia definido. |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad del tipo CLR que se va a crear. Los valores válidos son: Public, Protected, Internal y Private. |
Identificador | String | None | Un tipo puede tener un identificador único. El identificador de un tipo se puede usar en otras tablas o funciones. El identificador solo aparece en el archivo DBML, no en el modelo de objetos. |
IdRef | String | None | IdRef se usa para hacer referencia al identificador de otro tipo. Si IdRef está presente en un elemento de tipo, el elemento de tipo solo debe contener la información de IdRef . IdRef solo aparece en el archivo DBML, no en el modelo de objetos. |
Atributos de tipo Sub-Element
Sub-Element | Tipo de elemento | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Columna> | Columna | 0 sin enlazar | Representa una propiedad dentro de este tipo que se enlazará a un campo de la tabla de este tipo. |
<Asociación> | Asociación | 0 sin enlazar | Representa una propiedad dentro de este tipo que se enlazará a un extremo de una relación de clave externa entre tablas. |
<Tipo> | SubType | 0 sin enlazar | Representa los subtipos de este tipo dentro de una jerarquía de herencia. |
SubType
Este elemento representa un tipo derivado en una jerarquía de herencia. Esto se generará en un nuevo tipo CLR con las columnas y asociaciones especificadas en este tipo. No se generan atributos de herencia para subtipos.
En comparación con Type, los elementos SubType no tienen AccessModifier porque todos los tipos derivados deben ser públicos. Las subtipos no se pueden reutilizar en otras tablas y funciones, por lo que no hay id y IdRef en ellas.
Atributos SubType
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (obligatorio) | Nombre del tipo CLR que se va a generar. |
InheritanceCode | String | None | Si este tipo participa en la herencia, puede tener un código de herencia asociado para distinguir entre los tipos CLR al cargar filas de la tabla. Tipo cuyo código de herencia coincide con el valor de la columna IsDiscriminator se usa para crear instancias del objeto cargado. Si el código de herencia no está presente, la clase de entidad generada es abstracta. |
IsInheritanceDefault | Boolean | False | Si esto es cierto para un tipo en una jerarquía de herencia, este tipo se usará al cargar filas que no coincidan con ningún código de herencia definido. |
Atributos de Sub-Element SubType
Sub-Element | Tipo de elemento | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Columna> | Columna | 0 sin enlazar | Representa una propiedad dentro de este tipo que se enlazará a un campo de la tabla de este tipo. |
<Asociación> | Asociación | 0 sin enlazar | Representa una propiedad dentro de este tipo que se enlazará a en un extremo de una relación de clave externa entre tablas. |
<Tipo> | SubType | 0 sin enlazar | Representa los subtipos de este tipo dentro de una jerarquía de herencia. |
Columna
Este elemento representa una columna dentro de una tabla que se asigna a una propiedad (y campo de respaldo) dentro de una clase . No habrá ningún elemento Column presente para ninguno de los extremos de una relación de clave externa, ya que eso está completamente representado (en ambos extremos) por los elementos Association.
Atributos de columna
Atributos | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | None | Nombre del campo de base de datos al que se asignará esta columna. |
Miembro | String | Nombre | Nombre de la propiedad CLR que se va a generar en el tipo contenedor. |
Almacenamiento | String | _Miembro | Nombre del campo de respaldo CLR privado que almacenará el valor de esta columna. No quite Storage al serializar, incluso si es el valor predeterminado. |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad de la propiedad CLR que se va a crear. Los valores válidos son: Public, Protected, Internal y Private. |
Tipo | String | (requerido) | Nombre del tipo de la propiedad CLR y del campo de respaldo que se va a crear. Esto puede ser cualquier cosa de un nombre completo a solo el nombre directo de una clase, siempre y cuando el nombre se encuentre en el ámbito cuando se compile el código generado. |
DbType | String | None | Tipo de SQL Server completo (incluida la anotación como NOT NULL) para esta columna. Lo usa LINQ to SQL si se proporciona para optimizar las consultas generadas y ser más específicas al realizar CreateDatabase(). Serialice siempre DbType. |
IsReadOnly | Boolean | False | Si se establece IsReadOnly , no se crea un establecedor de propiedades, lo que significa que las personas no pueden cambiar el valor de esta columna mediante ese objeto. |
IsPrimaryKey | Boolean | False | Indica que esta columna participa en la clave principal de la tabla. Esta información es necesaria para que LINQ to SQL funcionen correctamente. |
IsDbGenerated | Boolean | False | Indica que la base de datos genera los datos de este campo. Este es el caso principalmente para los campos AutoNumeración y para los campos calculados. No es significativo asignar valores a estos campos y, por tanto, son automáticamente IsReadOnly. |
CanBeNull | Boolean | None | Indica que el valor puede contener el valor NULL. Si realmente desea usar valores NULL en CLR, debe especificar ClrType como T> que acepta valores<NULL. |
UpdateCheck | UpdateCheck | Siempre (a menos que al menos otro miembro tenga establecido IsVersion y, a continuación, Nunca) | Indica si LINQ to SQL debe usar esta columna durante la detección de conflictos de simultaneidad optimista. Normalmente, todas las columnas participan de forma predeterminada, a menos que haya una columna IsVersion , que luego participa por sí misma. Puede ser: Always, Never o WhenChanged (lo que significa que la columna participa si su propio valor ha cambiado). |
IsDiscriminator | Boolean | False | Indica si este campo contiene el código discriminador usado para elegir entre tipos en una jerarquía de herencia. |
Expression | String | None | No afecta a la operación de LINQ to SQL, pero se usa durante .CreateDatabase() como una expresión SQL sin formato que representa la expresión de columna calculada. |
IsVersion | Boolean | False | Indica que este campo representa un campo TIMESTAMP en SQL Server que se actualiza automáticamente cada vez que se cambia la fila. A continuación, este campo se puede usar para habilitar la detección de conflictos de simultaneidad optimista más eficaz. |
IsDelayLoaded | Boolean | False | Indica que esta columna no se debe cargar inmediatamente después de la materialización del objeto, sino solo cuando se accede a la propiedad pertinente por primera vez. Esto es útil para campos de memo grandes o datos binarios en una fila que no siempre es necesaria. |
AutoSync | AutoSync | If (IsDbGenerated && IsPrimaryKey) OnInsert; Else if (IsDbGenerated) Always De lo contrario, nunca |
Especifica si la columna se sincroniza automáticamente a partir del valor generado por la base de datos. Los valores válidos para esta etiqueta son: OnInsert, Always y Never. |
Asociación
Este elemento representa cualquier extremo de una relación de clave externa. Para las relaciones uno a varios, será un EntitySet<T> en un lado y un EntityRef<T> en el lado varios. En el caso de las relaciones uno a uno, será un EntityRef<T> en ambos lados.
Tenga en cuenta que no es necesario tener una entrada de asociación en ambos lados de una asociación. En este caso, solo se generará una propiedad en el lado que tiene la entrada (formando una relación unidireccional).
Atributos de asociación
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (requerido) | Nombre de la relación (normalmente el nombre de restricción de clave externa). Técnicamente, esto puede ser opcional, pero siempre debe generarse mediante código para evitar ambigüedad cuando hay varias relaciones entre las mismas dos tablas. |
Miembro | String | Nombre | Nombre de la propiedad CLR que se va a generar en este lado de la asociación. |
Almacenamiento | String |
Si OneToMany y Not IsForeignKey:
_OtherTable Más: _TypeName(OtherTable) |
Nombre del campo de respaldo CLR privado que almacenará el valor de esta columna. |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad de la propiedad CLR que se va a crear. Los valores válidos son Public, Protected, Internal y Private. |
ThisKey | String | Propiedad IsIdentity dentro de la clase contenedora | Lista separada por comas de las claves de este lado de la asociación. |
OtherTable | String | Vea la descripción. | Tabla en el otro extremo de la relación. Normalmente, esto se puede determinar mediante el tiempo de ejecución de LINQ to SQL mediante la coincidencia de nombres de relación, pero esto no es posible para asociaciones unidireccionales o asociaciones anónimas. |
OtherKey | String | Claves principales dentro de la clase externa | Lista separada por comas de las claves en el otro lado de la asociación. |
IsForeignKey | Boolean | False | Indica si se trata del lado "secundario" de la relación, el lado varios de uno a varios. |
RelationshipType | RelationshipType | OneToMany | Indica si el usuario está afirmando que los datos relacionados con esta asociación cumplen los criterios de datos uno a uno o se ajusta al caso más general de uno a varios. Para uno a uno, el usuario está afirmando que, para cada fila de la clave principal ("una"), solo hay una fila en el lado de la clave externa ("varios"). Esto hará que se genere una T> EntityRef< en el lado "uno" en lugar de un EntitySet<T>. Los valores válidos son OneToOne y OneToMany. |
DeleteRule | String | None | Se usa para agregar el comportamiento de eliminación a esta asociación. Por ejemplo, "CASCADE" agregaría "ONDELETECASCADE" a la relación FK. Si se establece en NULL, no se agrega ningún comportamiento de eliminación. |
Función
Este elemento representa un procedimiento almacenado o una función de base de datos. Para cada nodo function , se genera un método en la clase DataContext .
Atributos de función
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (requerido) | Nombre del procedimiento almacenado dentro de la base de datos. |
Método | String | Método | Nombre del método CLR que se va a generar que permite la invocación del procedimiento almacenado. El nombre predeterminado de Method tiene elementos como [dbo]. Se ha quitado El nombre. |
AccessModifier | AccessModifier | Público | Nivel de accesibilidad del método de procedimiento almacenado. Los valores válidos son Public, Protected, Internal y Private. |
HasMultipleResults | Boolean | Número de tipos > 1 | Especifica si el procedimiento almacenado representado por este nodo Function devuelve varios conjuntos de resultados. Cada conjunto de resultados es una forma tabular, puede ser un tipo existente o ser un conjunto de columnas. En este último caso, se creará un nodo Type para el conjunto de columnas. |
IsComposable | Boolean | False | Especifica si el procedimiento almacenado o función se puede componer en LINQ to SQL consultas. Solo se pueden componer las funciones de base de datos que no devuelven void. |
Atributos de Sub-Element de función
Sub-Element | Tipos de elementos | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Parámetro> | Parámetro | 0 sin enlazar | Representa los parámetros de in y out de este procedimiento almacenado. |
<Elementtype> | Tipo | 0 sin enlazar | Representa las formas tabulares que puede devolver el procedimiento almacenado correspondiente. |
<Return> | Valor devuelto | 0-1 | Tipo escalar devuelto de esta función de base de datos o procedimiento almacenado. Si Return es null, la función devuelve void. Una función no puede tener Return y ElementType. |
TableFunction
Este elemento representa las funciones de invalidación de CUD para las tablas. El diseñador de LINQ to SQL permite la creación de métodos de invalidación Insert, Update y Delete para LINQ TO SQL y permite la asignación de nombres de propiedad de entidad a nombres de parámetros de procedimiento almacenados.
El nombre del método para las funciones CUD se fija, por lo que no hay ningún atributo Method en DBML para los elementos TableFunction . Por ejemplo, para la tabla Customer, los métodos CUD se denominan InsertCustomer, UpdateCustomer y DeleteCustomer.
Una función de tabla no puede devolver una forma tabular, por lo que no hay ningún atributo ElementType en el elemento TableFunction .
Atributos TableFunction
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (requerido) | Nombre del procedimiento almacenado dentro de la base de datos. |
AccessModifier | AccessModifier | Privados | Nivel de accesibilidad del método de procedimiento almacenado. Los valores válidos son Public, Protected, Internal y Private. |
HasMultipleResults | Boolean | Número de tipos > 1 | Especifica si el procedimiento almacenado representado por este nodo Function devuelve varios conjuntos de resultados. Cada conjunto de resultados es una forma tabular, puede ser un tipo existente o ser un conjunto de columnas. En este último caso, se creará un nodo Type para el conjunto de columnas. |
IsComposable | Boolean | False | Especifica si el procedimiento almacenado o función se puede componer en LINQ to SQL consultas. Solo se pueden componer las funciones de base de datos que no devuelven void. |
Atributos Sub-Element TableFunction
Sub-Elements | Tipo de elemento | Intervalo de repeticiones | Descripción |
---|---|---|---|
<Parámetro> | TableFunctionParameter | 0 sin enlazar | Representa los parámetros de in y out de esta función de tabla. |
<Return> | TableFunctionReturn | 0-1 | Tipo escalar devuelto de esta función de tabla. Si Return es null, la función devuelve void. |
Parámetro
Este elemento representa un parámetro de procedimiento o función almacenados. Los parámetros pueden pasar datos dentro y fuera.
Atributos de parámetro
Atributo | Tipo | Valor predeterminado | Descripciones |
---|---|---|---|
Nombre | String | (requerido) | Nombre de la base de datos del parámetro proc/function almacenado. |
Parámetro | String | Nombre | Nombre CLR del parámetro de método. |
String | (requerido) | Nombre CLR del parámetro de método. | |
DbType | String | None | Tipo de base de datos del parámetro proc/function almacenado. |
Dirección | ParameterDirection | In | Dirección en la que fluye el parámetro. Puede ser uno de In, Out e InOut. |
Valor devuelto
Este elemento representa el tipo de valor devuelto de un procedimiento almacenado o una función.
Devolver atributos
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Tipo | String | (requerido) | Tipo CLR del resultado del procedimiento o función almacenado. |
DbType | String | None | El tipo de base de datos del resultado del procedimiento o función almacenado. |
TableFunctionParameter
Este elemento representa un parámetro de una función CUD. Los parámetros pueden pasar datos dentro y fuera. Cada parámetro se asigna a una columna Table a la que pertenece esta función CUD. No hay atributos Type o DbType en este elemento porque la información de tipo se puede obtener de la columna a la que se asigna el parámetro.
Atributos TableFunctionParameter
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Nombre | String | (requerido) | Nombre de la base de datos del parámetro de función CUD. |
Parámetro | String | Nombre | Nombre CLR del parámetro de método. |
Columna | String | Nombre | El nombre de columna al que se asigna este parámetro. |
Dirección | ParameterDirection | In | Dirección en la que fluye el parámetro. Puede ser uno de In, Out o InOut. |
Versión | Versión | Current | Indica si PropertyName hace referencia a la versión actual o original de una columna determinada. Solo se aplica durante la invalidación de actualización . Puede ser Actual o Original. |
TableFunctionReturn
Este elemento representa un tipo de valor devuelto de una función CUD. En realidad, solo contiene el nombre de columna asignado al resultado de la función CUD. La información de tipo del valor devuelto se puede obtener de la columna .
Atributo TableFunctionReturn
Attrobite | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
Columna | String | None | Nombre de columna al que se asigna el valor devuelto. |
Conexión
Este elemento representa los parámetros de conexión de base de datos predeterminados. Esto permite la creación de un constructor predeterminado para el tipo DataContext que ya sabe cómo conectarse a una base de datos.
Hay dos tipos de conexiones predeterminadas posibles, una con connectionString directa y otra que lee de App.Settings.
Atributos de conexión
Atributo | Tipo | Valor predeterminado | Descripción |
---|---|---|---|
UseApplicationSettings | Boolean | False | Determina si se debe usar un archivo App.Settings u obtener la configuración de la aplicación de un connectionString directo. |
ConnectionString | String | None | Cadena de conexión que se va a enviar al proveedor de datos SQL. |
SettingsObjectName | String | Configuración | Objeto App.Settings del que se van a recuperar las propiedades. |
SettingsPropertyName | String | ConnectionString | La propiedad App.Settings que contiene ConnectionString. |
Entidades de varios niveles
En las aplicaciones de dos niveles, un único DataContext controla las consultas y las actualizaciones. Sin embargo, para las aplicaciones con niveles adicionales, a menudo es necesario usar instancias independientes de DataContext para consultas y actualizaciones. Por ejemplo, en el caso de las aplicaciones de ASP.NET, la consulta y la actualización se realizan para solicitudes independientes al servidor web. Por lo tanto, no es práctico usar la misma instancia de DataContext en varias solicitudes. En tales casos, una instancia de DataContext debe ser capaz de actualizar objetos que no ha recuperado. La compatibilidad con entidades de varios niveles en LINQ to SQL proporciona esta funcionalidad a través del método Attach().
Este es un ejemplo de cómo se puede cambiar un objeto Customer mediante una instancia de DataContext diferente:
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()
En las aplicaciones de varios niveles, la entidad completa a menudo no se envía entre niveles por motivos de simplicidad, interoperabilidad o privacidad. Por ejemplo, un proveedor puede definir un contrato de datos para un servicio web que difiere de la entidad Order usada en el nivel intermedio. Del mismo modo, una página web solo puede mostrar un subconjunto de los miembros de una entidad Employee. Por lo tanto, la compatibilidad con varios niveles está diseñada para adaptarse a estos casos. Solo los miembros que pertenecen a una o varias de las siguientes categorías deben transportarse entre niveles y establecerse antes de llamar a Attach().
- Miembros que forman parte de la identidad de la entidad.
- Miembros que se han cambiado.
- Miembros que participan en la comprobación de simultaneidad optimista.
Si se usa una marca de tiempo o una columna de número de versión para la comprobación de simultaneidad optimista, el miembro correspondiente debe establecerse antes de llamar a Attach(). No es necesario establecer valores para otros miembros antes de llamar a Attach(). LINQ to SQL usa actualizaciones mínimas con comprobaciones de simultaneidad optimista; es decir, se omite un miembro que no está establecido o comprobado para la simultaneidad optimista.
Los valores originales necesarios para las comprobaciones de simultaneidad optimistas se pueden conservar mediante diversos mecanismos fuera del ámbito de las API de LINQ to SQL. Una aplicación ASP.NET puede usar un estado de vista (o un control que use el estado de vista). Un servicio web puede usar DataContract para un método de actualización para asegurarse de que los valores originales están disponibles para el procesamiento de actualizaciones. En interés de la interoperabilidad y la generalidad, LINQ to SQL no dicta la forma de los datos intercambiados entre niveles o los mecanismos utilizados para realizar un recorrido de ida y vuelta de los valores originales.
Las entidades para la inserción y eliminación no requieren el método Attach(). Los métodos usados para aplicaciones de dos niveles: Table.Add()
y Table.Remove() se pueden usar para la inserción y eliminación. Como en el caso de las actualizaciones de dos niveles, un usuario es responsable de controlar las restricciones de clave externa. Un cliente con pedidos no se puede quitar sin controlar sus pedidos si hay una restricción de clave externa en la base de datos que impide la eliminación de un cliente con pedidos.
LINQ to SQL también controla los datos adjuntos de las entidades para las actualizaciones de forma transitiva. Básicamente, el usuario crea el gráfico de objetos de actualización previa según sea necesario y llama a Attach(). Todos los cambios se pueden "reproducir" en el gráfico adjunto para realizar las actualizaciones necesarias, como se muestra a continuación:
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()
Asignación externa
Además de la asignación basada en atributos, LINQ to SQL también admite la asignación externa. La forma más común de asignación externa es un archivo XML. Los archivos de asignación permiten escenarios adicionales en los que es deseable separar la asignación del código.
DataContext proporciona un constructor adicional para proporcionar un objeto MappingSource. Una forma de MappingSource es xmlMappingSource que se puede construir a partir de un archivo de asignación XML.
Este es un ejemplo de cómo se puede usar el archivo de asignación:
C#
String path = @"C:\Mapping\NorthwindMapping.xml";
XmlMappingSource prodMapping =
XmlMappingSource.FromXml(File.ReadAllText(path));
Northwind db = new Northwind(
@"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf",
prodMapping
);
Visual Basic
Dim path As String = "C:\Mapping\NorthwindMapping.xml"
Dim prodMapping As XmlMappingSource = _
XmlMappingSource.FromXml(File.ReadAllText(path))
Dim db As Northwind = New Northwind( _
"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf", _
prodMapping )
Este es un fragmento de código correspondiente del archivo de asignación que muestra la asignación de la clase Product . Muestra la clase Product en el espacio de nombres Mapping asignada a la tabla Products de la base de datos Northwind . Los elementos y atributos son coherentes con los nombres de atributo y los parámetros.
<?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>
Compatibilidad y notas de la función de NET Framework
En los párrafos siguientes se proporciona información básica sobre LINQ to SQL compatibilidad con tipos y diferencias de .NET Framework.
Tipos primitivos
Implemented
- Operadores de comparación y aritméticos
- Operadores shift: << y >>
- La conversión entre char y numeric se realiza mediante NCHAR UNICODE
/
; de lo contrario, se usa CONVERT de SQL.
No implementado
- <Tipo>. Analizar
- Las enumeraciones se pueden usar y asignar a enteros y cadenas de una tabla. Para este último, se usan los métodos Parse y ToString().
Diferencia de .NET
- La salida de ToString para double usa CONVERT(NVARCHAR(30), @x, 2) en SQL, que siempre usa 16 dígitos y "Notación científica". Por ejemplo: "0.000000000000000e+000" para 0, por lo que no proporciona la misma cadena que . Convert.ToString() de NET.
System.String
Implemented
Métodos no estáticos:
- Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. Se admiten todas las firmas, excepto cuando toman el parámetro StringComparison , etc., como se detalla a continuación.
Métodos estáticos:
Concat(...) all signatures Compare(String, String) String (indexer) Equals(String, String)
Constructor:
String(Char, Int32)
Operadores:
+, ==, != (+, =, and <> in Visual Basic)
No implementado
Métodos que toman o generan una matriz de char.
Métodos que toman un objeto CultureInfo/StringComparison/IFormatProvider.
Estático (compartido en Visual Basic):
Copy(String str) Compare(String, String, Boolean) Compare(String, String, StringComparison) Compare(String, String, Boolean, CultureInfo) Compare(String, Int32, String, Int32, Int32) Compare(String, Int32, String, Int32, Int32, Boolean) Compare(String, Int32, String, Int32, Int32, StringComparison) Compare(String, Int32, String, Int32, Int32, Boolean, CultureInfo) CompareOrdinal(String, String) CompareOrdinal(String, Int32, String, Int32, Int32) Join(String, ArrayOf String [,...]) All Join version with first three args
Instance:
ToUpperInvariant() Format(String, Object) + overloads IndexOf(String, Int32, StringComparison) IndexOfAny(ArrayOf Char) Normalize() Normalize(NormalizationForm) IsNormalized() Split(...) StartsWith(String, StringComparison) ToCharArray() ToUpper(CultureInfo) TrimEnd(ParamArray Char) TrimStart(ParamArray Char)
Restricciones/diferencia de .NET
SQL usa intercalaciones para determinar la igualdad y el orden de las cadenas. Se pueden especificar en una instancia de SQL Server, una base de datos, una columna de tabla o una expresión.
Las traducciones de las funciones implementadas hasta ahora no cambian la intercalación ni especifican una intercalación diferente en las expresiones traducidas. Por lo tanto, si la intercalación predeterminada no distingue mayúsculas de minúsculas, las funciones como CompareTo o IndexOf pueden dar resultados que difieren de lo que proporcionarían las funciones de .NET (distingue mayúsculas de minúsculas).
Los métodos StartsWith(str)/
EndsWith(str) asumen que el argumento str es una constante o una expresión que se evalúa en el cliente. Es decir, actualmente no es posible usar una columna para str.
System.Math
Métodos estáticos implementados
- Todas las firmas:
- Abs, Acos, Asin, Atan, Atan2, BigMul, Ceiling, Cos, Cosh, Exp, Floor, Log, Log10, Max, Min, Pow, Sign, Sinh, Sqrt, Tan, Tanh o Truncate.
No implementado
- IEEERemainder.
- DivRem tiene un parámetro out, por lo que no se puede usar en una expresión. Las constantes Math.PI y Math.E se evalúan en el cliente, por lo que no necesitan una traducción.
Diferencia de .NET
La traducción de la función de .NET Math.Round es la función SQL ROUND. La traducción solo se admite cuando se especifica una sobrecarga que indica el valor de enumeración MidpointRounding . MidpointRounding.AwayFromZero es el comportamiento de SQL y MidpointRounding.ToEven indica el comportamiento de CLR.
System.Convert
Implemented
- Métodos de forma a<Type1>(<Type2> x) donde Type1, Type2 es uno de los siguientes:
- bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 o string.
- El comportamiento es el mismo que una conversión:
- Para ToString(Double) hay código especial para obtener la precisión completa.
- Para la conversión Int32/Char, LINQ to SQL usa la función NCHAR UNICODE
/
de SQL. - De lo contrario, la traducción es convert.
No implementado
ToSByte, UInt16, 32, 64: estos tipos no existen en SQL.
To<integer type>(String, Int32) ToString(..., Int32) any overload ending with an Int32 toBase IsDBNull(Object) GetTypeCode(Object) ChangeType(...)
Versiones con el parámetro IFormatProvider .
Métodos que implican una matriz (To/FromBase64CharArray
,
To/FromBase64String).
System.TimeSpan
Implemented
Constructores:
TimeSpan(Long) TimeSpan (year, month, day) TimeSpan (year, month, day, hour, minutes, seconds) TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
Operadores:
Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic +, -
Métodos estáticos (compartidos en Visual Basic):
Compare(t1,t2)
Métodos o propiedades no estáticos (Instance):
Ticks, Milliseconds, Seconds, Hours, Days TotalMilliseconds, TotalSeconds, TotalMinutes, TotalHours, TotalDays, Equals, CompareTo(TimeSpan) Add(TimeSpan), Subtract(TimeSpan) Duration() [= ABS], Negate()
No implementado
ToString()
TimeSpan FromDay(Double), FromHours, all From Variants
TimeSpan Parse(String)
System.DateTime
Implemented
Constructores:
DateTime(year, month, day) DateTime(year, month, day, hour, minutes, seconds) DateTime(year, month, day, hour, minutes, seconds, milliseconds)
Operadores:
Comparisons DateTime – DateTime (gives TimeSpan) DateTime + TimeSpan (gives DateTime) DateTime – TimeSpan (gives DateTime)
Métodos estáticos (compartidos):
Add(TimeSpan), AddTicks(Long), AddDays/Hours/Milliseconds/Minutes (Double) AddMonths/Years(Int32) Equals
Métodos o propiedades no estáticos (Instance):
Day, Month, Year, Hour, Minute, Second, Millisecond, DayOfWeek CompareTo(DateTime) TimeOfDay() Equals ToString()
Diferencia de .NET
Los valores datetime de SQL se redondean a .000, .003 o .007 segundos, por lo que es menos preciso que los de .NET.
El intervalo de fecha y hora de SQL comienza el 1 de enero de 1753.
SQL no tiene un tipo integrado para TimeSpan. Usa diferentes métodos DATEDIFF que devuelven enteros de 32 bits. Uno es DATEDIFF(DAY,...), que da el número de días; otro es DATEDIFF(MILLISECOND,...), que proporciona el número de milisegundos. Se produce un error si dateTimes tiene más de 24 días de diferencia. Por el contrario, .NET usa enteros de 64 bits y mide timeSpans en tics.
Para acercarse lo más posible a la semántica de .NET en SQL, LINQ to SQL traduce TimeSpans en enteros de 64 bits y usa los dos métodos DATEDIFF mencionados anteriormente para calcular el número de tics entre dos fechas.
Datetime
UtcNow se evalúa en el cliente cuando la consulta se traduce (como cualquier expresión que no implique datos de base de datos).
No implementado
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
Capacidad de depuración
DataContext proporciona métodos y propiedades para obtener el CÓDIGO SQL generado para las consultas y el procesamiento de cambios. Estos métodos pueden ser útiles para comprender LINQ to SQL funcionalidad y para depurar problemas específicos.
Métodos DataContext para obtener SQL generado
Member | Propósito |
---|---|
Log | Imprime SQL antes de ejecutarlo. Trata los comandos de consulta, inserción, actualización y eliminación. Uso: C#
Visual Basic
|
GetQueryText(query) | Devuelve el texto de consulta de la consulta sin ejecutarla. Uso: C# Console.WriteLine(db.GetQueryText(db.Customers));
Visual Basic
|
GetChangeText() | Devuelve el texto de los comandos SQL para insertar, actualizar o eliminar sin ejecutarlos. Uso: C# Console.WriteLine(db.GetChangeText());
Visual Basic
|