none
How to discard changes in a DataContext RRS feed

  • 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 Snippet

    dim 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

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
    Moderator
  • 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 insert

    dc.GetTable(objInsert.GetType()).DeleteOnSubmit(objInsert);

    // clear associations

    ClearAssociations(objInsert);

    }

    foreach (object objDelete in changes.Deletes)

    {

    // undo delete

    dc.GetTable(objDelete.GetType()).InsertOnSubmit(objDelete);

    // undo field/property changes + redo associations

    UndoMemberChanges(dc, objDelete);

    }

    foreach (object objUpdate in changes.Updates)

    {

    // undo field/property changes

    UndoMemberChanges(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];

    }

     

    private static void ClearAssociations(object obj)

    {

    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 AssociationAttribute

    associatedType.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 object

    SetMemberValue(obj, mi, associatedObj);

    }

    }

    else

    {

    // undo change by setting modified member to OriginalValue

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