IUpdateable for Linq To Sql


IUpdatable for Linq to Sql

I have had an implementation of IUpdateable for Linq to Sql about half done for quite some time now. Yesterday I decided to bite the bullet and finish it up.

Below I have included that code. I’ve done some light testing, but I am sure there are some bugs – and perhaps some perf fixes that can be made. My plan is to get this out on CodePlex as soon as I get a moment.

 Update – 11/06/08, I actually ended up putting it on CodeGallery here.

Update 2 – 12/02/08, I found I had implemented ResetResource incorrectly to set the property values to the original values instead of the default values.  Fixed this and uploaded the file to CodeGallery.

Update 3 – 12/11/08 – Found another set of fixes for ResetResource.  Fixed this and uploaded the file to CodeGallery

Note: Going forward all updates to the code will be exclusively on Code Gallery and will not posted to this blog.

A couple of notes about this code:

1) I haven’t implemented ClearChanges() yet. As far as I can tell there is really no straight forward to do this with Linq to Sql. It isn’t that big of deal because ClearChanges is only required by Astoria when processing batches with multiple ChangeSets. Something that is not supported by the Astoria .NET client.

2) The easiest way to use this is to define a partial class for your DataContext type and provide the implementation there. i.e.

public partial class nwDataContext : IUpdatable

That way you don’t need to change your generated context class.

Next step – implementing IExpandProvider

    public partial class myDataContext : IUpdatable
    {
        /// <summary>
        /// Creates the resource of the given type and belonging to the given container
        /// </summary>
        /// <param name="containerName">container name to which the resource needs to be added</param>
        /// <param name="fullTypeName">full type name i.e. Namespace qualified type name of the resource</param>
        /// <returns>object representing a resource of given type and belonging to the given container</returns>
        public object CreateResource(string containerName, string fullTypeName)
        {
            Type t = Type.GetType(fullTypeName);
            Debug.Assert(t != null);  // assume can find type
            ITable table = (ITable)this.GetType().GetProperty(containerName).GetValue(this, null);
            object resource = Activator.CreateInstance(t);
            table.InsertOnSubmit(resource);
            return resource;
        }

        /// <summary>
        /// Gets the resource of the given type that the query points to
        /// </summary>
        /// <param name="query">query pointing to a particular resource</param>
        /// <param name="fullTypeName">full type name i.e. Namespace qualified type name of the resource</param>
        /// <returns>object representing a resource of given type and as referenced by the query</returns>
        public object GetResource(IQueryable query, string fullTypeName)
        {
            object resource = null;

            foreach (object o in query)
            {
                if (resource != null)
                {
                    throw new Exception("Expected a single response");
                }
                resource = o;
            }

            // fullTypeName can be null for deletes
            if (fullTypeName != null && resource.GetType() != Type.GetType(fullTypeName))
                throw new Exception("Unexpected type for resource");
            return resource;
        }
        /// <summary>
        /// Resets the value of the given resource to its default value
        /// </summary>
        /// <param name="resource">resource whose value needs to be reset</param>
        /// <returns>same resource with its value reset</returns>
        public object ResetResource(object resource)
        {
            Type t = resource.GetType();
            Debug.Assert(t != null);
            object newResource = Activator.CreateInstance(t);
            MetaTable table = this.Mapping.GetTable(t);
            foreach (var member in table.RowType.IdentityMembers)
            {
                object keyValue = member.MemberAccessor.GetBoxedValue(resource);
                member.MemberAccessor.SetBoxedValue(ref newResource, keyValue);
            }
            return newResource;
        }
        /// <summary>
        /// Sets the value of the given property on the target object
        /// </summary>
        /// <param name="targetResource">target object which defines the property</param>
        /// <param name="propertyName">name of the property whose value needs to be updated</param>
        /// <param name="propertyValue">value of the property</param>
        public void SetValue(object targetResource, string propertyName, object propertyValue)
        {
            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
            if (pi == null)
                throw new Exception("Can't find property");
            pi.SetValue(targetResource, propertyValue, null);
        }

        /// <summary>
        /// Gets the value of the given property on the target object
        /// </summary>
        /// <param name="targetResource">target object which defines the property</param>
        /// <param name="propertyName">name of the property whose value needs to be updated</param>
        /// <returns>the value of the property for the given target resource</returns>
        public object GetValue(object targetResource, string propertyName)
        {
            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
            if (pi == null)
                throw new Exception("Can't find property");
            return pi.GetValue(targetResource, null);
        }

        /// <summary>
        /// Sets the value of the given reference property on the target object
        /// </summary>
        /// <param name="targetResource">target object which defines the property</param>
        /// <param name="propertyName">name of the property whose value needs to be updated</param>
        /// <param name="propertyValue">value of the property</param>
        public void SetReference(object targetResource, string propertyName, object propertyValue)
        {
            this.SetValue(targetResource, propertyName, propertyValue);
        }

        /// <summary>
        /// Adds the given value to the collection
        /// </summary>
        /// <param name="targetResource">target object which defines the property</param>
        /// <param name="propertyName">name of the property whose value needs to be updated</param>
        /// <param name="resourceToBeAdded">value of the property which needs to be added</param>
        public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
        {
            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
            if (pi == null)
                throw new Exception("Can't find property");
            IList collection = (IList) pi.GetValue(targetResource, null);
            collection.Add(resourceToBeAdded);
        }

        /// <summary>
        /// Removes the given value from the collection
        /// </summary>
        /// <param name="targetResource">target object which defines the property</param>
        /// <param name="propertyName">name of the property whose value needs to be updated</param>
        /// <param name="resourceToBeRemoved">value of the property which needs to be removed</param>
        public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
        {
            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
            if (pi == null)
                throw new Exception("Can't find property");
            IList collection = (IList)pi.GetValue(targetResource, null);
            collection.Remove(resourceToBeRemoved);
        }

        /// <summary>
        /// Delete the given resource
        /// </summary>
        /// <param name="targetResource">resource that needs to be deleted</param>
        public void DeleteResource(object targetResource)
        {
            ITable table = this.GetTable(targetResource.GetType());
            table.DeleteOnSubmit(targetResource);
        }

        /// <summary>
        /// Saves all the pending changes made till now
        /// </summary>
        public void SaveChanges()
        {
            this.SubmitChanges();
        }

        /// <summary>
        /// Returns the actual instance of the resource represented by the given resource object
        /// </summary>
        /// <param name="resource">object representing the resource whose instance needs to be fetched</param>
        /// <returns>The actual instance of the resource represented by the given resource object</returns>
        public object ResolveResource(object resource)
        {
            return resource;
        }

        /// <summary>
        /// Revert all the pending changes.
        /// </summary>
        public void ClearChanges()
        {
        }
    }

Comments (7)

  1. rogerj says:

    Andy,

    Thanks very much for the IUpdatable implementation. It’s a very welcome occurrence to finally have data-source partity with Entity Framework for Astoria.

    Roger Jennings

  2. Thanks for sharing this!

    Have you done any performance test yet?

    If you had, have you tried improving it by avoiding some of the Reflection calls? because those they use to be expensive, and caching would be very straight-forward in this case:

    e.g. create a Dictionary<string,ITable> to cache this:

    (ITable)this.GetType().GetProperty(containerName).GetValue(this, null)

  3. aconrad says:

    I thought about caching the table mappings to avoid using reflection to much.  I did not for a couple of reasons:

    1)  I wanted to make the code as simple and clean as possible.

    2)  Our use of the context in ADO.NET Data Services in the server is very short lived.  For single CUD operations – there would be no saving.  For multiple changes in a batch, there might be some perf

    It might be an option to make this a static member and initialize only once.  I might try that to see if there is a perf gain.  Our experience is that this may help as a local optimization, but in the big picture (database, network, parsing) it makes very little difference.

  4. You’ve been kicked (a good thing) – Trackback from DotNetKicks.com

  5. Matthew Wills says:

    Can you give us a 5-line explanation of what this exactly is for? For example, is there something that couldn’t be done previously and now it can be? Is there a demo (zip file?) available showing the new ‘features’ this makes available / possible?