En un artículo anterior, presenté varios problemas que puede encontrar al usar LINQ to SQL para operaciones de actualización. De hecho, este no es un problema que encontré solo. Cuando busqué respuestas en Internet, descubrí que muchas personas habían publicado artículos similares sobre este tema. Pero lo que no me satisface es que, aunque plantearon el problema, no realizaron un análisis detallado. Solo dieron soluciones (como agregar columnas RowVersion, eliminar asociaciones, etc.), pero no explicaron por qué debían hacerlo. . Esta es también la intención original de escribir el artículo anterior. Espero encontrar la solución al problema paso a paso a través del análisis del código fuente de LINQ to SQL. Este artículo discutirá estos métodos uno por uno.
Opción 1: Reasignación En el marco de código abierto Ezsocio de TerryLee, Anytao, Ding Xue y otros, se adopta la reasignación en algunos lugares. Dentro del método Update, obtenga las entidades en la base de datos según la clave principal y luego asigne valores a sus propiedades uno por uno con las entidades en los parámetros.
Perfil de actualización público vacío (perfil p)
{
usando (RepositoryContext db = nuevo RepositoryContext())
{
var perfil = db.GetTable<Perfil>().First<Perfil>(u => u.ID == p.ID);
perfil.Cumpleaños = p.Cumpleaños;
perfil.Género = p.Género;
perfil.Ciudad natal = p.Ciudad natal;
perfil.MSN = p.MSN;
perfil.NickName = p.NickName;
perfil.PhoneNumber = p.PhoneNumber;
perfil.QQ = p.QQ;
perfil.Estado = p.Estado;
perfil.TrueName = p.TrueName;
perfil.StateRefreshTime = p.StateRefreshTime;
perfil.Avatar = p.Avatar;
perfil.Sitio web = p.Sitio web;
db.SubmitChanges();
}
}
El hermano Yang Guo también proporcionó un método de reflexión para que esta solución lograra la copia automática de los valores de los atributos.
Pero personalmente creo que este es un esquema que evita la realidad y evita la realidad. No utiliza la API proporcionada por LINQ to SQL para las operaciones de actualización, sino que adopta una estrategia indirecta. En realidad, esto es un compromiso. ¿Es porque el método Adjuntar "no es fácil de usar", por lo que no lo usamos? jeje.
Opción 2: deshabilitar el seguimiento de objetos. En este sentido, Lea propuso que se puede lograr la actualización correcta estableciendo la propiedad ObjectTrackingEnabled de DataContext en falso.
Producto público GetProduct(int id)
{
NorthwindDataContext db = nuevo NorthwindDataContext();
db.ObjectTrackingEnabled = falso;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
Ningún otro código cambia.
¿Por qué puede actualizarse normalmente después de desactivar el seguimiento de objetos? Busquemos la respuesta en el código fuente.
bool público ObjectTrackingEnabled
{
conseguir
{
this.CheckDispose();
devolver this.objectTrackingEnabled;
}
colocar
{
this.CheckDispose();
si (this.Services.HasCachedObjects)
{
lanzar System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = valor;
si (!this.objectTrackingEnabled)
{
this.deferredLoadingEnabled = falso;
}
this.services.ResetServices();
}
}
Resulta que cuando ObjectTrackingEnabled se establece en falso, DeferredLoadingEnabled se establecerá en falso al mismo tiempo. De esta manera, al ejecutar la consulta, no se cargarán datos que requieran una consulta retrasada para la entidad, por lo que no se lanzará ninguna excepción durante Adjuntar (ver el análisis en el artículo anterior).
En MSDN también obtenemos la siguiente información útil: Establecer la propiedad ObjectTrackingEnable en falso puede mejorar el rendimiento de la recuperación porque reduce la cantidad de elementos que se deben rastrear. Esta es una característica muy tentadora.
Sin embargo, al deshabilitar el seguimiento de objetos, se debe prestar especial atención a dos puntos: (1) Debe deshabilitarse antes de ejecutar la consulta. (2) Después de deshabilitarlos, ya no se pueden llamar a los métodos Adjuntar y SubmitChanges. De lo contrario, se lanzará una excepción.
Opción 3: eliminar la asociación. En el artículo anterior se introdujo un método poco convincente, que consiste en establecer manualmente la categoría asociada con el producto en nula en el método GetProduct. Podemos extraer esta parte del código y ponerla en un método Detach. Debido a que este Detach es un método de entidad, se pueden usar clases parciales:
Producto de clase parcial pública
{
vacío público Separar()
{
this._Category = default(EntityRef<Categoría>);
}
}
categoría de clase parcial pública
{
vacío público Separar()
{
foreach (var producto en this.Products)
{
producto.Detach();
}
}
}Pero este método de definir Detach para cada entidad es demasiado engorroso. A medida que aumenta el número de entidades, las relaciones se vuelven cada vez más complejas y es fácil que aparezcan atributos faltantes. Zhang Yi propuso un método muy elegante para abstraer esta lógica mediante la reflexión:
Separación de vacío privado (entidad TEntity)
{
foreach (FieldInfo fi en entidad.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
si (fi.FieldType.ToString().Contains("EntityRef"))
{
valor var = fi.GetValue(entidad);
si (valor! = nulo)
{
fi.SetValue(entidad, nulo);
}
}
si (fi.FieldType.ToString().Contains("EntitySet"))
{
valor var = fi.GetValue(entidad);
si (valor! = nulo)
{
MethodInfo mi = value.GetType().GetMethod("Borrar");
si (mi != nulo)
{
mi.Invoke(valor, nulo);
}
fi.SetValue(entidad, valor);
}
}
}
}
Algunas personas también piensan que los eventos PropertyChanging y PropertyChanged deberían establecerse en nulos durante la separación, pero la idea general es la misma.
Opción 4: utilizar delegación. Este es el método proporcionado por ZC29 en los comentarios de mi último artículo. Personalmente creo que vale la pena aprender de él.
public void UpdateProductWithDelegate(Expresión<Func<Producto, bool>> predicado, Acción<Producto> acción)
{
NorthwindDataContext db = nuevo NorthwindDataContext();
var producto = db.Products.SingleOrDefault(predicado);
acción(producto);
db.SubmitChanges();
}
//Código de cliente
Repositorio ProductRepository = nuevo ProductRepository();
repositorio.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.ProductName = "Cambiado";
});
Utilice expresiones Lambda para incrustar la lógica GetProduct en UpdateProduct y utilice delegados para diferir la ejecución de la lógica de actualización. Esto coloca inteligentemente la búsqueda y la actualización en un DataContext, evitando así Adjuntar. Sin embargo, la API de este método es demasiado compleja y requiere niveles demasiado altos de programadores de clientes. Además, la lógica Get debe ejecutarse nuevamente en Update. Aunque la pérdida de rendimiento es mínima, siempre parece dar a la gente la sensación de que no está lo suficientemente SECO.
Opción 5: usar la declaración UPDATE En el código fuente de Ezsocio, encontré el método RepositoryBase.UpdateEntity. La unión de declaraciones SQL se realiza dentro del método y solo se actualizarán las columnas modificadas. Dado que aquí ya no se utiliza ITable y se requiere soporte completo del marco, no se harán más comentarios. Consulte el código fuente de Ezsocio para obtener más detalles.
Resumen Este artículo enumera varias soluciones que he encontrado en Internet en los últimos días. Todas tienen ventajas y desventajas. Cuál es mejor o peor depende de las opiniones de diferentes personas. En el próximo artículo, compararé el rendimiento de estos métodos para encontrar la solución óptima.