Published Wednesday, December 19, 2007 10:05 AM by mtaulty

ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities

Something that's been foxing me is that I've read about ADO.NET Data Services now supporting a "provider" model such that it's no longer tied to the Entity Framework.

I hadn't done this myself until today but was reading this great post and was wondering how this stuff could work given that IQueryable isn't enough to do read/write so I thought I'd better give it a try.

So, I get going;

  1. New Web Site project.
  2. Insert a new Entity Framework model based on Northwind.
  3. Insert a new Web Data Service.
  4. Change my service to derive from WebDataService<NorthwindEntities>
  5. Open up all access to my service by changing the generated InitializeService method to allow all access.
  6. Run up the browser and I'm in business.

So, I've got entities coming back in response to URI's.

I then go and build a client. I do;

  1. New console project.
  2. Add a reference to Microsoft.Data.WebClient (I'm finding these in c:\program files\reference assemblies\...)
  3. Go and use the WebDataGen.exe tool to generate me some client classes from my service ( I found the tool in c:\program files\microsoft asp.net 3.5 extensions).

Webdatagen.exe" /uri:"http://localhost:32767/WebSite3/WebDataService.svc" /mode:ClientClassGeneration /outobjectlayer:northwind.cs

  1. Add the generated code to my project.
  2. Use it to create an entity in the back-end.
    static void Main(string[] args)
    {
      NorthwindEntities en = new NorthwindEntities(
       "http://localhost:32767/WebSite3/WebDataService.svc");
      
      Customers c = new Customers()
        {
          CustomerID = "ABCDE",
          CompanyName = "Foo Ltd"
        };
        en.AddObject("Customers", c);
        en.SaveChanges();
      
    }

Now, I want to change to LINQ to SQL. I go and add a LINQ to SQL diagram into my project, I change my WebDataService< T > to use the data context class that's generated for me by the LINQ to SQL tooling.

I re-run my queries and that's all looking good.

I go and re-run my client that does the insert and, of course, it doesn't work.

It's not enough just to be IQueryable. If you want to support the other parts of CRUD then you need to be IUpdateable as well.

So, I figure that my LINQ to SQL DataContext-derived class is a partial class and I set about extending it. Firstly, I stub it all out;

public partial class NorthwindDbDataContext : System.Data.Linq.DataContext,
  IUpdatable 
{
  public void Add(object resource, string containerName)
  {
    throw new NotImplementedException();
  }

  public void AddResourceToCollection(object propertyValue, System.Reflection.PropertyInfo propertyInfo, object resourceToBeAdded, bool update)
  {
    throw new NotImplementedException();
  }

  public object Attach(Type resourceType, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>> keyValues, string containerName)
  {
    throw new NotImplementedException();
  }

  public void AttachResourceToCollection(object propertyValue, System.Reflection.PropertyInfo propertyInfo, object resourceToBeAttached)
  {
    throw new NotImplementedException();
  }

  public void Delete(object resource)
  {
    throw new NotImplementedException();
  }

  public void SaveChanges()
  {
    throw new NotImplementedException();
  }

  public void SetReferenceProperty(object declaringResource, System.Reflection.PropertyInfo propertyInfo, object resourceToBeAdded, bool update)
  {
    throw new NotImplementedException();
  }


}

And set a few breakpoints just to see if they fire and I do see my Add method being called server-side so I figure I'll proceed with it ...

First off, I had a go at Add and hacked together a quick;

public partial class NorthwindDbDataContext : System.Data.Linq.DataContext,
  IUpdatable 
{
  public void Add(object resource, string containerName)
  {
    this.GetTable(resource.GetType()).InsertOnSubmit(resource);
  }
  public void SaveChanges()
  {
    // TODO: Failure?
    this.SubmitChanges();
  }

That seemed to work ok (in the sense of I tried it once with one insert, don't assume this works for all cases).

Moved on to Delete - note, I think this implementation is highly dubious as I'm concerned about the lifetime of the context because (here at the moment) I assume that the entity is already attached in the Delete method which can't be right in a stateless web environment. Feels like it needs checking...

public partial class NorthwindDbDataContext : System.Data.Linq.DataContext,
  IUpdatable 
{
  private ITable InternalGetTable(object resource)
  {
    return(this.GetTable(resource.GetType()));
  }
  public void Add(object resource, string containerName)
  {
    this.InternalGetTable(resource).InsertOnSubmit(resource);
  }
  public void SaveChanges()
  {
    // TODO: Failure?
    this.SubmitChanges();
  }
  public void Delete(object resource)
  {
    ITable table = this.InternalGetTable(resource);
    table.DeleteOnSubmit(resource);
  }

and my extensive test case for this was;

    static void Main(string[] args)
    {
      NorthwindEntities en = new NorthwindEntities(
       "http://localhost:32767/WebSite3/WebDataService.svc");

      en.MergeOption = MergeOption.AppendOnly;

      Customers c = (from x in en.Customers
                     where x.CustomerID == "FEGHI"
                     select x).First();

      en.DeleteObject(c);

      en.SaveChanges();      
    }

and then I wanted to try an Update so I modified my implementation to the following;

public partial class NorthwindDbDataContext : System.Data.Linq.DataContext, IUpdatable 
{
  private ITable InternalGetTable(object resource)
  {
    return (this.InternalGetTableByType(resource.GetType()));
  }
  private ITable InternalGetTableByType(Type t)
  {
    return (this.GetTable(t));
  }
  public void Add(object resource, string containerName)
  {
    this.InternalGetTable(resource).InsertOnSubmit(resource);
  }
  public void SaveChanges()
  {
    // TODO: Failure?
    this.SubmitChanges();
  }
  public void Delete(object resource)
  {
    // TODO: What if the entity isn't "there" for deleting?
    // Do we need to check for it and Attach it first?
    ITable table = this.InternalGetTable(resource);
    table.DeleteOnSubmit(resource);
  }
  public object Attach(Type resourceType, 
    System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>> keyValues, 
    string containerName)
  {
    ITable table = this.InternalGetTableByType(resourceType);

    object o = Activator.CreateInstance(resourceType);

    foreach (KeyValuePair<string,object> item in keyValues)
    {
      resourceType.GetProperty(item.Key).SetValue(o, item.Value, null);
    }

    // TODO: Unsure how I can do this without passing true as a 2nd parameter
    // here and switching off concurrency checking everywhere 
    //
    // Why? Because here I only have access to the key values so all other
    // values in the entity will be null at the time of "attach" which 
    // makes it look like the original DB record was NULL apart from the
    // key values which is almost certain not to be the case and will
    // cause the framework to do some "odd" things when it comes to
    // updates (especially for those values that are not nullable
    // in the DB).
    table.Attach(o, true);

    return (o);
  }
  public void AddResourceToCollection(object propertyValue, 
    System.Reflection.PropertyInfo propertyInfo, 
    object resourceToBeAdded, 
    bool update)
  {
    throw new NotImplementedException();
  }
  public void AttachResourceToCollection(object propertyValue, 
    System.Reflection.PropertyInfo propertyInfo, 
    object resourceToBeAttached)
  {
    throw new NotImplementedException();
  }
  public void SetReferenceProperty(object declaringResource, 
    System.Reflection.PropertyInfo propertyInfo, 
    object resourceToBeAdded, 
    bool update)
  {
    throw new NotImplementedException();
  }
}

And that seemed to work when exhaustively tested using;

    static void Main(string[] args)
    {
      NorthwindEntities en = new NorthwindEntities(
       "http://localhost:32767/WebSite3/WebDataService.svc");

      en.MergeOption = MergeOption.AppendOnly;

      Customers c = (from x in en.Customers
                     where x.CustomerID == "ABCDE"
                     select x).First();

      c.Country = "UK";

      en.UpdateObject(c);

      en.SaveChanges();      
    }

I've not implemented the other 3 methods - haven't probed yet to work out exactly how/when they're called. A word about Update though.

The Attach method is called only with the Key values for the entity and expects you to return an attached entity. So, the flow seems to go like this (illustrated with a Northwind Customer entity);

  1. Framework calls Attach with the Key Names and Values.
    1. Construct the right kind of entity. ( new Customer() ).
    2. Set the key values on the right properties. ( CustomerID = "ALFKI" )
    3. Attach to the context. (Context.Attach() - note that by default this will mean that every non-key property of the entity looks like it had an original value of NULL).
    4. Return the object.
    5. Framework sets the other property values for you (through the accessors causing change notifications - note that this will make it look like all these properties have "changed" when they may not have).
    6. Framework calls SaveChanges for you.

Hence my big comment in the Update mechanism - there's no way to provide the "original values" ( as we know from this post ) because all that happens here for an update is a PUT with the new values. That'd have an impact on how any framework you use is going to structure it's UPDATE statement.

I think this also has an impact for concurrency checking - it feels like it's only going to be possible to do "last write wins" because for frameworks like LINQ to Entities/LINQ to SQL when you attach you can provide original values but, here, there's no way of getting at any of the data values when you Attach.

Update - I've checked and it turns out that concurrency in the CTP is "last write wins" and so that's why I'm struggling so much to "do the right thing" in my fledgling Update() method above :-)


Filed Under: ,

# ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities | ok @ Wednesday, December 19, 2007 10:42 AM

PingBack from http://real-sports-news.info/adonet-data-services-using-linq-to-sql-rather-than-linq-to-entities

ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities | ok

# ADO.Net Data Services Feedback: DataContextServiceProvider Required for LINQ to SQL support @ Thursday, December 20, 2007 4:16 AM

ADO.Net Data Services Feedback: DataContextServiceProvider Required for LINQ to SQL support After writing

Guy Burstein [MVP]

# Ode To Mike Taulty at Lost In Tangent @ Thursday, December 20, 2007 10:15 AM

PingBack from http://lostintangent.com/2007/12/20/ode-to-mike-taulty/

Ode To Mike Taulty at Lost In Tangent

# kolynkhan &raquo; ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities @ Saturday, December 22, 2007 4:28 PM

PingBack from http://kolynkhan.adoption23.info/1969/12/31/adonet-data-services-using-linq-to-sql-rather-than-linq-to-entities/

kolynkhan » ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities

# ADO.NET Data Services - LINQ to SQL and Associations @ Monday, December 24, 2007 3:49 AM

One of the things that I didn't mention (because I didn't know and I'm still not 100% sure :-)) in this...

Mike Taulty's Blog

# ADO.NET Data Services - Exposing Arbitrary Data (2) @ Thursday, January 03, 2008 3:38 AM

Following on from the previous post, I wanted to look at how we can make our arbitrary data source available...

Mike Taulty's Blog

# ?????????????????? ???????????????????? ????????????, ???? ???????????????????????????? ????????&#8230; &laquo; ???????? ???????????? ?????????????? @ Monday, January 14, 2008 2:19 PM

PingBack from http://seregaborzov.wordpress.com/2008/01/15/some-programming-csharp-links/

?????????????????? ???????????????????? ????????????, ???? ???????????????????????????? ????????… « ???????? ???????????? ??????????????