.net - transact - Linq a SQL: orden de ejecución al llamar a SubmitChanges()
linq to sql vs entity framework (5)
Otra solución es adjuntar los cambios a otro contexto de datos en el orden correcto. Sin embargo, la desventaja de esta solución es que las entidades a las que se hace referencia desde el resto de la aplicación que pertenecían al contexto de datos original ahora no serán válidas :(
Intenté una ligera variación en esto, creando un segundo contexto de datos temporales para la confirmación. Pero no puedo resolver cómo hacer para que el contexto de datos original esté limpio.
Es muy frustrante :(
public void Save()
{
string connectionString = m_Db.Connection.ConnectionString;
IList<object> deletes = m_Db.GetChangeSet().Deletes;
IList<object> inserts = m_Db.GetChangeSet().Inserts;
IList<object> updates = m_Db.GetChangeSet().Updates;
m_Db.Dispose();
m_Db = new MyDataContext(connectionString);
Attach(m_Db, deletes);
m_Db.SubmitChanges();
Attach(m_Db, updates);
m_Db.SubmitChanges();
Attach(m_Db, inserts);
m_Db.SubmitChanges();
}
void Attach(DataContext context, IList<object> items)
{
foreach (object o in items)
{
context.GetTable(o.GetType()).Attach(Clone(o));
}
}
object Clone(object o)
{
object result = o.GetType().GetConstructor(Type.EmptyTypes).Invoke(null);
foreach (PropertyInfo property in o.GetType().GetProperties())
{
if (property.PropertyType.IsValueType)
{
property.SetValue(result, property.GetValue(o, null), null);
}
}
return result;
}
Tengo 2 tablas de bases de datos relacionadas que en forma simplificada se parecen a esto
Product(
product_id,
name
)
ProductSpecs(
spec_id,
product_id,
name,
value
)
La clave externa se establece a través del campo product_id y la tabla ProductSpecs tiene una restricción única en el par (product_id, name).
Ahora, en mi aplicación ASP.NET MVC, cuando el usuario edita las especificaciones del producto y guarda los datos, borro las especificaciones antiguas e inserto todas las nuevas.
Para hacerlo, primero llamo a DataContext.DeleteAllOnSubmit () y proporciono las ProductSpecs actuales (antiguas) como un parámetro, y luego agrego nuevas especificaciones a la colección Product.ProductSpecs.
Luego llamo a DataContext.SubmitChanges () y obtengo un error al violar mi restricción única.
Al observar las sentencias SQL devueltas por DataContenxt.GetChangeText (), puedo ver que INSERT se ejecutan antes de DELETE (aunque llamé a DeleteAllOnSubmit () antes de Agregar).
¿Cuál es la razón de este comportamiento y cómo solucionarlo o solucionarlo?
Gracias.
En lugar de simplemente tirar a todos los niños y recrearlos, considere tomar un poco más de esfuerzo y hacer esto en dos pasos: Agregue los elementos que aún no se han agregado y elimine los que se agregaron previamente, pero que ya no se desean. Por ejemplo, en el sitio ThinqLinq.com, tengo publicaciones que se pueden asignar a categorías. En la pantalla de edición, proporciono una lista de categorías que se pueden seleccionar y deseleccionar. Cuando recibo la publicación que contiene la lista de categorías seleccionadas, utilizo lo siguiente para eliminar y actualizar los registros apropiados:
Dim selectedCats() As String = CType(ValueProvider("categories").RawValue, String())
For Each catId In selectedCats.Except(From cp In currentPost.CategoryPosts _
Select id = cp.CategoryID.ToString())
''Add new categories
currentPost.CategoryPosts.Add(New CategoryPost With {.CategoryID = CInt(catId)})
Next
''Delete removed categories
dc.CategoryPosts.DeleteAllOnSubmit(From cp In currentPost.CategoryPosts _
Where Not selectedCats.Contains(cp.CategoryID.ToString))
Sí, por alguna razón, Linq to SQL realiza todas las eliminaciones como último recurso. Y no hay forma de cambiar eso.
O tal vez hay No he investigado en codegen DataContext para ver si podemos anular algo allí.
Puede llamar a SubmitChanges () tantas veces como desee. Mi solución cuando necesito eliminar lo primero es marcar mis eliminaciones, llamar a SubmitChanges (), luego realizar inserciones y actualizaciones, y llamar a SubmitChanges una vez más.
Puede envolver todo dentro de un TransactionScope:
var deletables =
from toDelete in db.NamedValues
where toDelete.Name == knownValue.Name
select toDelete;
using (var scope = new TransactionScope())
{
db.NamedValues.DeleteAllOnSubmit(deletables);
db.SubmitChanges();
db.NamedValues.InsertOnSubmit(knownValue);
db.SubmitChanges();
scope.Complete();
}
Una mejor solución que no requiere saber de antemano qué entidades han cambiado es utilizar los métodos parciales de DataContext para interceptar las inserciones y enviar las eliminaciones primero.
public partial class YourDataContext
{
List<EntityX> _deletedEntities = new List<EntityX>();
partial void InsertEntityX(EntityX instance)
{
// Run deletes before inserts so that we don''t run into any index violations
var deletes =
this.GetChangeSet().Deletes
.Where(d => d.GetType().Equals(instance.GetType()))
.Cast<EntityX>().ToList();
var replaced = deletes.SingleOrDefault(d => d.UniqueKeyId == instance.UniqueKeyId);
if (replaced != null)
{
DeleteEntityX(replaced);
_deletedEntities.Add(replaced);
}
this.ExecuteDynamicInsert(instance);
}
partial void DeleteEntityX(EntityX instance)
{
if (_deletedEntities.Contains(instance))
{
_deletedEntities.Remove(instance);
return;
}
this.ExecuteDynamicDelete(instance);
}
}
He estado teniendo este mismo problema. Se me ocurrió un truco para solucionarlo, lo que parece funcionar para mí: ejecuto manualmente algunos SQL para cada eliminación potencialmente problemática en el conjunto de cambios antes de llamar a SubmitChanges ():
foreach (object o in m_Db.GetChangeSet().Deletes)
{
LibraryMember m = o as LibraryMember;
if (m != null)
{
m_Db.ExecuteCommand("DELETE FROM LibraryMembers WHERE LibraryMemberId={0}", m.LibraryMemberId);
}
}
m_Db.SubmitChanges()