Answered by:
How to discard changes in a DataContext

Question
-
Hi all,
I have a typical parent/child relationship between two tables in my database. I receive batches of transactions in a file via ftp and I parse those transactions into the database.
The parent table, Batchs, holds a unique id plus things like the filename, date received, etc. The child table, Transactions, holds the specifics on each transaction in the file. Transactions has a foreign key to Batches.
My code basically does this:
Code Snippetdim myBatch as New Batch
... set various properties in myBatch
myDbContext.Batchs.InsertOnSubmit(myBatch)
Do While ... I have transactions in the file
Dim myTransaction as New Transaction
... set various properties in myTransaction
myBatch.Transactions.Add(myTransaction)
Try
myDbContext.SubmitChanges()
Catch ex As Exception
.. Report the error
End Try
Loop
If there are no errors, this all works fine. However, sometimes the data coming in is invalid. For instance, a particular data field is too long. When this happens, the SubmitChanges call throws an exception. What I want to do is to report the error and then ignore that particular transaction.
So, my question is, "What can I do in the Catch block to get myDbContext to discard the changes made since the last call to SubmitChanges?"
I have tried a refresh of the myBatch object which doesn't work.
I have tried removing myTransaction from the myBatch.Transactions collection. That causes another exception ("An attempt was made to remove a relationship between a Batch and a Transaction. However, one of the relationship's foreign keys (Transaction.BatchID) cannot be set to null.") the next time SubmitChanges is called.
Thanks,
David
Friday, April 25, 2008 5:04 PM
Answers
-
The only way to 'undo' the state is to throw away the DataContext and start over.Friday, April 25, 2008 5:59 PM
All replies
-
The only way to 'undo' the state is to throw away the DataContext and start over.Friday, April 25, 2008 5:59 PM
-
I was afraid that might be the answer. Thank you for responding.
David
Friday, April 25, 2008 6:06 PM -
First of all, I agree that UndoChanges should be an integrated part of the DataContext class. Like so many other features you will need in real-life applications...
However, it's not there. So I started to create an extension method UndoChanges that does the trick. Most difficult part is the undo of inserts and deletes. That's because associations need to be undone or re-done.
Anyway, here's the code so far. Feel free to use it but let me state that I myself am not yet confident that that's a good idea...
using
System;using
System.Collections.Generic;using
System.Linq;using
System.Text;using
System.Data.Linq;using
System.Reflection;using
System.Data.Linq.Mapping;using
System.Linq.Expressions;namespace
Tjip.Data.Linq{
public static class DataContextExtensions{
public static void UndoChanges(this DataContext dc){
ChangeSet changes = dc.GetChangeSet(); foreach (object objInsert in changes.Inserts){
// undo insertdc.GetTable(objInsert.GetType()).DeleteOnSubmit(objInsert);
// clear associationsClearAssociations(objInsert);
}
foreach (object objDelete in changes.Deletes){
// undo deletedc.GetTable(objDelete.GetType()).InsertOnSubmit(objDelete);
// undo field/property changes + redo associationsUndoMemberChanges(dc, objDelete);
}
foreach (object objUpdate in changes.Updates){
// undo field/property changesUndoMemberChanges(dc, objUpdate);
}
}
private static Dictionary<Type, Dictionary<string, List<MemberInfo>>> typeAssociations = new Dictionary<Type,Dictionary<string,List<MemberInfo>>>(); private static Dictionary<string, List<MemberInfo>> GetTypeAssociations(Type type){
if (!typeAssociations.ContainsKey(type)){
Dictionary<string, List<MemberInfo>> result = new Dictionary<string, List<MemberInfo>>(); foreach (MemberInfo mi in type.GetMembers()){
object[] attrs = mi.GetCustomAttributes(typeof(AssociationAttribute), true); if (attrs.Length > 0){
AssociationAttribute attr = attrs[0] as AssociationAttribute; List<MemberInfo> members = null; if (result.ContainsKey(attr.ThisKey)){
members = result[attr.ThisKey];
}
else{
members =
new List<MemberInfo>();result.Add(attr.ThisKey, members);
}
members.Add(mi);
}
}
typeAssociations.Add(type, result);
return result;}
return typeAssociations[type];}
{
Dictionary<string, List<MemberInfo>> associations = GetTypeAssociations(obj.GetType()); foreach (List<MemberInfo> members in associations.Values){
foreach (MemberInfo mi in members){
SetMemberValue(obj, mi,
null);}
}
}
private static void UndoMemberChanges(DataContext dc, object obj){
Dictionary<string, List<MemberInfo>> associations = GetTypeAssociations(obj.GetType()); ModifiedMemberInfo[] mmis = dc.GetTable(obj.GetType()).GetModifiedMembers(obj); if (mmis.Length > 0){
// Get GetTable method MethodInfo miGetTable = dc.GetType().GetMethod("GetTable",System.Reflection.
BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); foreach (ModifiedMemberInfo mmi in mmis){
if (associations.ContainsKey(mmi.Member.Name)){
// do not undo modified member but redo it's assoications // that will automatically restore the modified member List<MemberInfo> members = associations[mmi.Member.Name]; foreach (MemberInfo mi in members){
Type associatedType = GetMemberType(mi); // Get generic GetTable method MethodInfo miGenericGetTable = miGetTable.MakeGenericMethod(new Type[] { associatedType }); // Invoke to get table IQueryable table = miGenericGetTable.Invoke(dc, null) as IQueryable; // Create condition to single out associated object ParameterExpression p1 = System.Linq.Expressions.Expression.Parameter(associatedType, "p1"); LambdaExpression exprId = System.Linq.Expressions.Expression.Lambda(System.Linq.Expressions.
Expression.Equal(System.Linq.Expressions.
Expression.Property(p1, // TODO: name of this method should be found in AssociationAttributeassociatedType.GetMethod(
"get_Id")),
System.Linq.Expressions.
Expression.Constant( // associated object's ID(
long)mmi.OriginalValue, typeof(long)),
false, typeof(long).GetMethod("op_Equality")),
new ParameterExpression[] { p1 });
// create method call expression MethodCallExpression exprCall = System.Linq.Expressions.Expression.Call( typeof(Queryable), "Single", new Type[] { table.ElementType },table.Expression, exprId);
object associatedObj = table.Provider.Execute(exprCall); // Set associated objectSetMemberValue(obj, mi, associatedObj);
}
}
else{
// undo change by setting modified member to OriginalValueSetMemberValue(obj, mmi.Member, mmi.OriginalValue);
}
}
}
}
public static Type GetMemberType(MemberInfo mi){
switch (mi.MemberType){
case MemberTypes.Field: return ((FieldInfo)mi).FieldType; case MemberTypes.Property: return ((PropertyInfo)mi).PropertyType;}
throw new Exception("unknown member type");}
public static void SetMemberValue(object obj, MemberInfo mi, object value){
switch (mi.MemberType){
case MemberTypes.Field:((
FieldInfo)mi).SetValue(obj, value); break; case MemberTypes.Property:((
PropertyInfo)mi).SetValue(obj, value, null); break; default: throw new Exception("can not set member value");}
}
}
}
Wednesday, May 14, 2008 11:55 AM