none
WCF RIA Services?

    Question

  • How does this technology integrate with WCF RIA Services?  Does it just "Work?" :)

    Thanks,

    Michael

    Wednesday, December 08, 2010 11:42 AM

All replies

  • Michael,

     

    Check out this post on using Code First with RIA Services: http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/0e741f7f-700f-4efd-b10c-98f050f76c85

     

    The post is for CTP4 but should work with CTP5 with some changes.  One change is the way you access the underlying ObjectContext which is now exposed by DbContext on the explicitly implemented IObjectContextAdapter interface.  The ObjectContext property from the post could now be implemented as:

     

            public ObjectContext ObjectContext

            {

                get

                {

                    return ((IObjectContextAdapter)this).ObjectContext;

                }

            }

     

    We will be creating further guidance on using RIA Services with CTP5 in the near future.  I will reply again to this thread with a link when that guidance is posted.

     

    Thanks,

    Arthur

    Wednesday, December 08, 2010 6:14 PM
  • Excellent!  Thank you so much!  I will give this a shot and see how it goes.
    Monday, December 13, 2010 7:08 PM
  • Hi,

    I got RIA Services working with CTP5 as follows:

        [EnableClientAccess()]
        public class BookDomainService : DomainService
        {
            public BookDomainService()
            {
                var settings = ConfigurationManager.ConnectionStrings["BookModelContainer"];

                var connection = new EFTracingConnection
                {
                    ConnectionString = @"wrappedProvider=" + settings.ProviderName + ";" + settings.ConnectionString
                };


                this.Container = new BookModelContainer(connection, true);

                this.Container.Configuration.LazyLoadingEnabled = false;
            }

            private BookModelContainer Container { getset; }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    this.Container.Dispose();
                }

                base.Dispose(disposing);
            }

            protected override bool PersistChangeSet()
            {
                this.Container.SaveChanges();
                return base.PersistChangeSet();
            }

            public IQueryable<Book> GetBooks()
            {
                return this.Container.Books;
            }

            public void InsertBook(Book book)
            {
                this.Container.Insert(book);
            }

            public void UpdateBook(Book currentBook)
            {
                this.Container.Update(currentBook, this.ChangeSet.GetOriginal(currentBook));
            }

            public void DeleteBook(Book book)
            {
                this.Container.Delete(book);
            }
       }

    where BookModelContainer is my DbContext derived class. I use the EFTracingConnection from http://code.msdn.microsoft.com/EFProviderWrappers.

    I also created the following DbContext extension methods. 

        public static class DbContextExtensions
        {
            public static void Insert<T>(this DbContext context, T entity) where T : class
            {
                Contract.Requires(context != null);
                Contract.Requires(entity != null);

                var entry = context.Entry(entity);
                if (entry.State != EntityState.Detached)
                {
                    entry.State = EntityState.Added;
                }
                else
                {
                    context.Set<T>().Add(entity);
                }
            }

            public static void Update<T>(this DbContext context, T entity) where T : class
            {
                Contract.Requires(context != null);
                Contract.Requires(entity != null);

                var entry = context.Entry(entity);
                if (entry.State == EntityState.Detached)
                {
                    context.Set<T>().Attach(entity);
                }

                entry.State = EntityState.Modified;
            }

            public static void Update<T>(this DbContext context, T current, T original) where T : class
            {
                Contract.Requires(context != null);
                Contract.Requires(current != null);
                Contract.Requires(original != null);

                var entry = context.Entry(current);
                if (entry.State == EntityState.Detached)
                {
                    context.Set<T>().Attach(current);
                }
                else
                {
                    entry.State = EntityState.Modified;
                }

                entry.OriginalValues.SetValues(original);

                var properties = TypeDescriptor.GetProperties(typeof(T));
                var attributes = TypeDescriptor.GetAttributes(typeof(T));

                foreach (var propertyName in entry.CurrentValues.PropertyNames)
                {
                    var descriptor = properties[propertyName];
                    if (descriptor != null &&
                        descriptor.Attributes[typeof(RoundtripOriginalAttribute)] == null &&
                        attributes[typeof(RoundtripOriginalAttribute)] == null &&
                        descriptor.Attributes[typeof(ExcludeAttribute)] == null)
                    {
                        entry.Property(propertyName).IsModified = true;
                    }
                }

                if (entry.State != EntityState.Modified)
                {
                    entry.State = EntityState.Modified;
                }
            }

            public static void Delete<T>(this DbContext context, T entity) where T : class
            {
                Contract.Requires(context != null);
                Contract.Requires(entity != null);

                var entry = context.Entry(entity);
                if (entry.State != EntityState.Detached)
                {
                    entry.State = EntityState.Deleted;
                }
                else
                {
                    var set = context.Set<T>();
                    set.Attach(entity);
                    set.Remove(entity);
                }
            }
        }

    This makes use of the new change tracking api in CTP5. The code is inspired by using Reflector on the ObjectContextExtensions you find in System.ServiceModel.DomainServices.EntityFramework. Although I did not quite understand all the bits to do with the TypeDescriptor in the Update method and I wonder if that is still needed since with code first there is no need for metadata classes / ugly buddies (isn't that great?). I also added extension methods for inserts and deletes. I hope if the RIA Services team was to build DbContextExtensions they add extensions for Insert and Delete as well as I don't like seeing the "if (state != EntityState.Detached) ChangeObjectState else " pattern repeated in all the Insert and Delete methods that are created with a LinqToEntitiesDomainService.

    Currently I still have to add Key and Association attributes to my model classes , but I hope the RIA Services team is working with the EF team to build a DomainServiceDescriptionProvider / TypeDescriptor for DbContext that will add these Key and Association attributes automatically by interpreting the EF DbModel or ModelBuilder. Although I understand that EF uses ForeignKey and InverseProperty attributes instead of the Association attribute, if RIA Services requires the Association attribute I assume that this DomainServiceDescriptionProvider can add these automatically by interpreting the EF DbModel or ModelBuilder. Could anybody from the RIA Services or EF team confirm my understanding of this?

    kind regards

    Remco

    Thursday, December 23, 2010 2:46 PM
  • Remco,

     

    Thanks for posting this; I’m sure it will be very useful to other people.

     

    I made some updates to your extension methods below.  I haven’t tested these but there is now less code and I think they should work the same.

     

    I will follow up with people on the RIA Services questions.

     

        public static class DbContextExtensions

        {

            public static void Insert<T>(this DbContext context, T entity) where T : class

            {

                Contract.Requires(context != null);

                Contract.Requires(entity != null);

     

                // Note: changing the state to Added on a detached entity is the same as calling

                // Add on the DbSet.

                context.Entry(entity).State = EntityState.Added;

            }

     

            public static void Update<T>(this DbContext context, T entity) where T : class

            {

                Contract.Requires(context != null);

                Contract.Requires(entity != null);

     

                // Note: changing the state to Modified on a detached entity is the same as

                // calling Attach and then setting state to Modified.

                context.Entry(entity).State = EntityState.Modified;

            }

     

            public static void Update<T>(this DbContext context, T current, T original) where T : class

            {

                Contract.Requires(context != null);

                Contract.Requires(current != null);

                Contract.Requires(original != null);

     

                var entry = context.Entry(current);

                entry.State = EntityState.Unchanged;

     

                entry.OriginalValues.SetValues(original);

     

                var properties = TypeDescriptor.GetProperties(typeof(T));

                var attributes = TypeDescriptor.GetAttributes(typeof(T));

     

                foreach (var propertyName in entry.CurrentValues.PropertyNames)

                {

                    var descriptor = properties[propertyName];

                    if (descriptor != null &&

                        descriptor.Attributes[typeof(RoundtripOriginalAttribute)] == null &&

                        attributes[typeof(RoundtripOriginalAttribute)] == null &&

                        descriptor.Attributes[typeof(ExcludeAttribute)] == null)

                    {

                        entry.Property(propertyName).IsModified = true;

                    }

                }

     

                // Not sure what is going on here.  If you get to this point and the state is not Modified,

                // then it means all the values of current are the same as those of original and no property

                // was set to Modified explicitly in the loop above.  This means that there should be nothing

                // to write to the database.  If you then set the state to Modified it means all the properties

                // will be marked as Modified which means they will ALL be written to the database.  So it seems

                // likely that the following lines should not be here.

                if (entry.State != EntityState.Modified)

                {

                    entry.State = EntityState.Modified;

                }

            }

     

            public static void Delete<T>(this DbContext context, T entity) where T : class

            {

                Contract.Requires(context != null);

                Contract.Requires(entity != null);

     

                context.Entry(entity).State = EntityState.Deleted;

            }

        }

     

    Thanks again.

    Arthur

    Friday, December 24, 2010 2:14 AM
  • Great post, I am attempting CTP5 and RIA myself and this has been extremely useful.

    What i'm finding though, is that if the DomainService methods are not attributed with [Query] they don't get auto-generated on the client.

    Trouble is, if i try to attribute the Insert/Update/Delete methods, i get a compilation error claiming that the input parameter must be one of the predefined serializable types.

    Any ideas?

    Friday, January 07, 2011 3:16 PM
  • Please provide the signature of one of the problematic CUD methods, as well as the definition of the entity type you're attempting to use. For CUD methods the signature must take a single entity Type. So we need to verify that your signature conforms, that your entity type has a member marked with KeyAttribute, etc.
    Mathew Charles [MSFT]
    Friday, January 07, 2011 6:58 PM
  • I also meant to respond to Remco's question above "I hope the RIA Services team is working with the EF team to build a DomainServiceDescriptionProvider / TypeDescriptor for DbContext". Yes we'll be investigating first class support for this, including metadata support as you pointed out.
    Mathew Charles [MSFT]
    Friday, January 07, 2011 8:48 PM
  • My entity:

    public class Country
    {
     [Key]
     public int ID { get; set; }
     public string Name { get; set; }
    }
    

    My DbContext:

     

    public class MyDbContext : DbContext
    {
     public ObjectContext ObjectContext
     {
      get
      {
       return ((IObjectContextAdapter)this).ObjectContext;
      }
     }
    
     public DbSet<Country> Countries { get; set; }
    }
    

     

    The domain service: (in this version the Insert method does not auto-generate on the client - the get method works fine)

     [EnableClientAccess()]
     public class MyDomainService : DomainService
     {
      private MyDbContext context { get; set; }
      public ObjectContext ObjectContext { get { return ((IObjectContextAdapter)this).ObjectContext; } }
    
      public MyDomainService ()
      {
       this.context = new MyDbContext();
      }
     
      public IQueryable<Country> GetCountries()
      {
       return context.Countries;
      }
    
      public void InsertCountry(Country country)
      {
       context.Insert(country);
      }
    }
    
    

     

     

    Note that i am using the DbContextExtensions class from the post above

     

     

    If i add the [Query] attribute:

     

    [Query]
    public void InsertCountry(Country country)
    {
      context.Insert(country);
    }
    

     

    then i get this compile error:

     

    Parameter 'country' of domain operation entry 'InsertCountry' must be one of the predefined serializable types.


     

     

    Monday, January 10, 2011 6:46 AM
  • Hi Mathew,

    Great to hear RIA Services is investigating first class support for EF Code First, including metadata support. Can we expect this with the RTM of EF Code First in Q1 of 2011? Will a release of RIA Services (SP1) coincide with RTM of EF Code First? Also, after reading Rowan Miller's post here http://blogs.msdn.com/b/adonet/archive/2011/01/10/ef-feature-ctp5-pluggable-conventions.aspx where he mentions the limitation that the RTM of Code First does not allow you to read information about the shape of your model, I was left wondering if RIA Services will still be able to provide this first class support, including medata support for RTM of EF Code First. Or can you read information about the model from ((IObjectContextAdapter)DbContext).DataContext.MetadataWorkspace? Can you tell me what we can expect for RTM regarding first class RIA Services support and when?

    kind regards

    Remco

    Tuesday, January 11, 2011 9:35 AM
  • Any ideas on my issue above?
    Wednesday, January 12, 2011 9:26 AM
  • This is really great stuff!  Thanks for posting it.  It would be nice to get this in future CTP. :)
    Saturday, January 22, 2011 5:56 PM
  • Hello Arthur:

    I am trying to use the pattern that you have laid out in your post (directly above), but I am running into problems.  The problem occurs if I submit an update.  When I call ChangeSet.GetOriginal(theEntity), it returns an empty entity (correct type, but all values are null/default value).  As a result, the change detection logic determines that every non-null/non-default field is a modification (even if I really only modified 1 field)

    I think that this is occurring because my DomainService instance is being disposed between every call (so the update connects to a different instance of the domain service (and therefore with a different instance of my DbContext) than the original query.  (I have determined that the dispose is occuring by setting a breakpoint on the dispose method)

    It looked as though the code really intended to detect modifications of individual fields, (and I need that, because I am auditing changes).  Is there a way to get individual field changes? 

    Wednesday, February 02, 2011 8:33 PM
  • If you're not seeing the values you're expecting in the original instance returned from GetOriginal, it's likely because your entity members are not marked with RoundtripOriginalAttribute. This attribute can be applied at the type or individual property level, and indicates that you want those members to be roundtripped back from client to server on update. By default, only key and other members are roundtripped.
    Mathew Charles [MSFT]
    Thursday, February 10, 2011 1:14 AM
  • I've posted up a sample application / framework that ties these two technologies together for a simple Entity Management Solution.

    You can find that here: http://www.common-framework.com

    (Authenticating will display profile and administration sections which show off the Code-First + Ria Services functionality).

    Monday, February 14, 2011 5:57 AM
  • Now that the RTW of EF 4.1 DbContext API & Code First has been announced by the ADO.Net team, see http://blogs.msdn.com/b/adonet/archive/2011/03/02/ef-4-1-is-coming-dbcontext-api-amp-code-first-rtw.aspx, I am desperate for an anouncement from the WCF RIA Services team on when they will release support for EF 4.1, specifically a DomainServiceDescriptionProvider / TypeDescriptor for DbContext that can read the metadata from your EF DbModel and automatically add Key and Association attributes to your POCO classes, whereas right now we still have to add these Key and Association attributes manually. Mathew Charles said earlier in this thread "Yes we'll be investigating first class support for this, including metadata support" in this thread. What's the status?
    Friday, March 04, 2011 8:36 AM
  • I added a suggestion to the RIA Services forum on uservoice.com to have a DomainServiceDescriptionProvider built for EF DbContext / COde First automatic Key and Association attribute decoration. See http://dotnet.uservoice.com/forums/57026-wcf-ria-services/suggestions/1579271-ef-dbcontext-code-first-domainservicedescription. Please vote for it.

    Thursday, March 10, 2011 7:32 PM
  • Hello Remco:

     

    I am in the process of using RIA with Code first. I did go thru your code posted above but am not able to figure out where do you apply DBContextExtension class. no where in your code sample DbContextExtension class is being used. This might seem a silly Qs, but I am new to RIA, thats why this Qs.

     

     

    Thanks,

    Ankur

    Friday, March 11, 2011 5:54 AM
  • Hi Ankur,

    The DbContextExtension class contains extension methods. The first DbContext parameter in the methods has the 'this' keyword to indicate so. That means you can invoke those methods on the DbContext class directly making it appear they are methods of DbContext itself, like so:

    this.Container.Update(currentBook, this.ChangeSet.GetOriginal(currentBook));
            

    Friday, March 11, 2011 8:39 AM
  • Perfect. Thanks Remco
    Friday, March 11, 2011 2:34 PM
  • I just received an email that the suggestion on uservoice to implement this DomainServiceDescriptionProvider for EF DbContext / Code First has been started. That's great news.
    Tuesday, April 19, 2011 8:59 AM
  • And the suggestion has been completed, you can get it from NuGet.

    More details on Vanunpuranik's blog or Jeff Handley's blog


    MCP TFS 2010
    Professional Scrum Developer Trainer - Scrum.org
    Friday, July 01, 2011 10:00 AM