Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Disconnected LINQ to Entities

Blogs

Mike Taulty's Blog

Elsewhere

Taking this post here and changing from LINQ to SQL and LINQ to Entities. What would that involve?

Firstly, I add a new ADO.NET Entity Data Model to my project (call it Northwind.edmx).

I add only my Customers table and I leave the default mapping alone.

image

( I renamed the entity to Customer not Customers otherwise there's too many plurals ).

Then I returned to the two scenarios that I had in the LINQ to SQL post.

1) Where We Do Not Store The Original Values

Here's the service code that I wrote for this;

[ServiceContract]
class Service
{
  [OperationContract]
  public Customer GetCustomer(string customerId)
  {
    Customer c = null;

    using (NorthwindEntities entities = new NorthwindEntities())
    {
      c = entities.Customers.Where(cust => cust.CustomerID == customerId).First();
    }
    return (c);
  }
  [OperationContract]
  public void InsertCustomer(Customer c)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      entities.AddObject("Customers", c);
      entities.SaveChanges(true);
    }
  }
  [OperationContract]
  public void DeleteCustomer(Customer c)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      entities.AttachTo("Customers", c);     
      entities.DeleteObject(c);
      entities.SaveChanges(true);
    }
  }
  [OperationContract]
  public void UpdateCustomer(Customer c)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      entities.AttachTo("Customers", c);

      ObjectStateEntry entry = 
        entities.ObjectStateManager.GetObjectStateEntry(c);

      // Was expecting this to be enough...
      entry.SetModified();
      // But ended up with this too...
      SetEntryModified(entry);

      entities.SaveChanges(true);
    }
  }
  /// <summary>
  /// Goes through an ObjectStateEntry and sets every property other
  /// than the key properties to be modified.
  /// </summary>
  /// <param name="entry"></param>
  static void SetEntryModified(ObjectStateEntry entry)
  {
    // One way of doing it, almost certainly others...
    for (int i = 0; i < entry.CurrentValues.FieldCount; i++)
    {
      bool isKey = false;

      string name = entry.CurrentValues.GetName(i);

      foreach (KeyValuePair<string,object> keyPair in entry.EntityKey.EntityKeyValues)
      {
        if (string.Compare(name, keyPair.Key, true) == 0)
        {
          isKey = true;
          break;
        }
      }
      if (!isKey)
      {
        entry.SetModifiedProperty(name);
      }
    }
  }
  static void Main(string[] args)
  {
    ServiceHost host = new ServiceHost(typeof(Service),
      new Uri("http://localhost:9091/customerService"));

    BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);

    host.AddServiceEndpoint(
      typeof(Service),
      binding,
      string.Empty);
      
    ServiceMetadataBehavior behaviour = new ServiceMetadataBehavior();
    behaviour.HttpGetEnabled = true;

    host.Description.Behaviors.Add(behaviour);

    host.Open();

    Console.WriteLine("Waiting for messages...");
    Console.ReadLine();
  }
}

Compared to the LINQ to SQL example, this is pretty similar until we hit the update method. In the update method, we attach the entity that the client has sent to us in serialized form. This attaches it in an "Unmodified" state and so we need to do something to mark that entire entity as "modified" so that when it comes time to generate SQL to send to the database we will get an UPDATE statement that updates every column except for the key columns.

AFAIK, there's currently no single framework method that does this and the closest that we can get is by calling SetModifiedProperty on each property that has been modified by the client.

Given that we don't have any original values passed to us from the client, we don't know which properties have/haven't been modified by the client and so we have to call SetModifiedProperty on each property.

That's exactly the same as the LINQ to SQL example except in LINQ to SQL there's a convenience method which does this in a line of code whereas, right now, in LINQ to Entities there isn't so I wrote that method SetEntryModified which loops around all properties (except those that are part of the key) and sets them as modified. There's probably a better/faster way of doing this but this does seem to work ok although it's clearly a bit more code than the LINQ to SQL case.

 

2) Where We Do Store The Original Values

In the case where we do have both current values and original values passed to us by the client, we need to feed these into the framework such that when it generates UPDATE/DELETE statements it includes all the original values that we have specified as being part of our concurrency check into its WHERE clause.

LINQ to SQL facilitates this by having a DataContext.Attach method which takes the current entity values and the original entity values.

In LINQ to Entities, we've got to do a bit more of that lifting ourselves at the time of writing (i.e. beta 2).

Also, as I've written about previously with the LINQ to Entities bits, you have to switch on concurrency checking so I need to alter my .EDMX file in order to set a few properties that I want to be checked for concurrency such as CustomerID (!), CompanyName, ContactName below;

<EntityType Name="Customer">
          <Key>
            <PropertyRef Name="CustomerID" />
          </Key>
          <Property Name="CustomerID" Type="String" Nullable="false" MaxLength="5" FixedLength="true" 
                    ConcurrencyMode="fixed"/>
          <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" 
                    ConcurrencyMode="fixed"/>
          <Property Name="ContactName" Type="String" MaxLength="30" 
                    ConcurrencyMode="fixed"/>
          <Property Name="ContactTitle" Type="String" MaxLength="30" />
          <Property Name="Address" Type="String" MaxLength="60" />
          <Property Name="City" Type="String" MaxLength="15" />
          <Property Name="Region" Type="String" MaxLength="15" />
          <Property Name="PostalCode" Type="String" MaxLength="10" />
          <Property Name="Country" Type="String" MaxLength="15" />
          <Property Name="Phone" Type="String" MaxLength="24" />
          <Property Name="Fax" Type="String" MaxLength="24" />
        </EntityType>

So, with that in place I can go and write a service like the one below (note, I left out the Main method this time as it's the same as last time);

[ServiceContract]
class Service
{
  [OperationContract]
  public Customer GetCustomer(string customerId)
  {
    Customer c = null;

    using (NorthwindEntities entities = new NorthwindEntities())
    {
      c = entities.Customers.Where(cust => cust.CustomerID == customerId).First();
    }
    return (c);
  }
  [OperationContract]
  public void InsertCustomer(Customer c)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      entities.AddObject("Customers", c);
      entities.SaveChanges(true);
    }
  }
  [OperationContract]
  public void DeleteCustomer(Customer originalCustomer, Customer newCustomer)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      entities.AttachTo("Customers", originalCustomer);      

      ObjectStateEntry entry =
        entities.ObjectStateManager.GetObjectStateEntry(originalCustomer);

      CompareAndOverwriteProperties(originalCustomer, newCustomer, entry);

      entities.DeleteObject(originalCustomer);

      entities.SaveChanges(true);
    }
  }
  [OperationContract]
  public void UpdateCustomer(Customer originalCustomer, Customer newCustomer)
  {
    using (NorthwindEntities entities = new NorthwindEntities())
    {
      // Attach the original customer value
      entities.AttachTo("Customers", originalCustomer);

      // Perform a diff on the two objects, where we find differences
      // mark them using SetModifiedProperty. This gets much harder
      // for object graphs than just simple properties.
      ObjectStateEntry entry = 
        entities.ObjectStateManager.GetObjectStateEntry(originalCustomer);

      CompareAndOverwriteProperties(originalCustomer, newCustomer, entry);
        
      entities.SaveChanges(true);
    }
  }
  private void CompareAndOverwriteProperties(object originalCustomer, 
    object newCustomer, ObjectStateEntry entry)
  {
    // One way of doing it, almost certainly others...
    for (int i = 0; i < entry.CurrentValues.FieldCount; i++)
    {
      bool isKey = false;

      string name = entry.CurrentValues.GetName(i);

      foreach (KeyValuePair<string, object> keyPair in entry.EntityKey.EntityKeyValues)
      {
        if (string.Compare(name, keyPair.Key, true) == 0)
        {
          isKey = true;
          break;
        }
      }
      if (!isKey)
      {
        CheckDifferentAndCopyProperty(originalCustomer, newCustomer, name);
      }
    }    
  }
  private void CheckDifferentAndCopyProperty(object originalCustomer, 
    object newCustomer, string name)
  {
    Type t = originalCustomer.GetType();

    object originalValue = t.GetProperty(name).GetValue(originalCustomer, null);
    object newValue = t.GetProperty(name).GetValue(newCustomer, null);

    bool equal = true;

    if (originalValue == null)
    {
      equal = (newValue == null);
    }
    else
    {
      // Reliant on object.Equals here - not necessarily a great idea.
      equal = originalValue.Equals(newValue);
    }
    if (!equal)
    {
      t.GetProperty(name).SetValue(originalCustomer, newValue, null);
    }
  }

So, essentially, when we come to do Delete or Update we perform the following;

  1. Attach the original entity to the ObjectContext
  2. Loop around the properties of the entity, use reflection to compare the original and current value. If the values differ we copy the property value from the "new" entity to the "original" entity (which is attached and firing change notifications into the ObjectStateManager).

So, this "kind of works" but it's quite a bit more painful to achieve than it was with LINQ to SQL in that, in the first case we've got to write code that uses SetModifiedProperty and, in the second case, I had to resort to reflection to do my own differencing on original/current entity values. It'd also be a more complicated thing to do if I wasn't just returning a Customer here but perhaps something more complex like a Customer and their Orders.


Posted Wed, Sep 19 2007 2:58 AM by mtaulty