locked
Problem with properties on my model in RIA services RRS feed

  • Question

  •  I have a model that looks roughly like this:

    private bool IsProduct {get; set;}
    private decimal ProductPrice {get; set;}
    private decimal TimedRate {get; set;}

    public decimal SingularAmount {
    get {
    if (this.IsProduct) {
    return ProductPrice;
    }
    else {
    return TimedRate;
    }
    }
    set {
    if (this.IsProduct) {
    this.ProductPrice = value;
    }
    else {
    this.TimedRate = value;
    }
    }
    }

     I'm binding to this SingularAmount property via RIA Services to Silverlight 3 DataGrid. What I'm finding is that, when I change the property - the respective properties on the model do not get updated. When I step through the code, I can see on the client side, that  SingularAmount is set to say 5 for example, the other properties do not get updated.

    Seems like when RIA makes the client side version of the classes, this sort of functionality isn't ported over. Any ideas on how to tackle this?

    Monday, August 10, 2009 8:25 PM

Answers

  • Is your exclude in the Person class or the PersonMetadata class? It should look something like this:

     

    [MetadataType(typeof(Person.PersonMetadata))]
        public partial class Person
        {
            internal sealed class PersonMetadata
            {
                [Exclude]
                public string Desc;
            }
        }
    
     
    Sunday, August 16, 2009 11:41 PM

All replies

  • This can be done with RIA Services "shared code" feature.

    Monday, August 10, 2009 9:48 PM
  • Uh, how,

    For reference, this is the client side version of the property generated by RIA Services:

     

     

    [DataMember()]
            public decimal SingularAmount
            {
                get
                {
                    return this._singularAmount;
                }
                set
                {
                    if ((this._singularAmount != value))
                    {
                        this.ValidateProperty("SingularAmount", value);
                        this.OnSingularAmountChanging(value);
                        this.RaiseDataMemberChanging("SingularAmount");
                        this._singularAmount = value;
                        this.RaiseDataMemberChanged("SingularAmount");
                        this.OnSingularAmountChanged();
                    }
                }
            }
      
    Tuesday, August 11, 2009 5:05 AM
  • What you need to do is put an Exclude attribute for SingularAmount in your metadata. That will keep the code generator from creating the client side SingularAmount property. Then you change the file name of the partial class file that contains SingularAmount to end in .shared.cs. Make sure that your partial class file just says partial public class <classname> at the top without any inheritances listed.

    The result of this is that your SingularAmount property will exist on both the server and the client as you originally defined it.

    Tuesday, August 11, 2009 10:04 AM
  •  Ok, I've got the shared code side of things happening. And I can see when I update the SingularAmount property, that the correct private properties are being updated. However, when I change the value of this property on the client side and call SubmitChanges() on the RIA context, no changes are persisted.

     I can confirm HasChanges is true on the RIAContext.

    Digging further into this - if I change an entirely different property (a normal property) on this class, and call SubmitChanges(), it works fine - but as soon as I alter this SingularAmount property - then my Update method is never called.

     

    Edit: I can see the new values are correctly being posted back up over the wire.

    Sunday, August 16, 2009 1:23 AM
  • Changing the private fields won't trigger the tracking mechanism.

    You have to change a real Property. A property that is part of your entity.

    The "set" part of a property will invoke RaiseDataMemberChanged("PropertyName")

    that will change the state of the entity as long as you have called BeginEdit in it (the entity I mean).

    Or you call it yourself on the set method.

    this.RaiseDataMemberChanged("SingularAmount");

    That should do it.

    Sunday, August 16, 2009 3:29 AM
  • You have to change a real Property. A property that is part of your entity.

    That's indeed the issue I described here http://silverlight.net/forums/t/119442.aspx: "...Another drawback of course when stuffing calculations in a property and sharing it "as is": it won't be regenerated on the client, so it won't contain the notify property changes hook either..."

    Sunday, August 16, 2009 3:46 AM
  •  Although I have used private properties in the original example to try and keep things simple, in my real app, the properties are public.

     I've been spending a lot of time in the debugger. Here's what I've figured out:

     Prior to calling SubmitChanges, the property values are being updated correctly. Entity HasChanges = true.

    When you call SubmitChanges, it seems to new up a new Entity, it then copies the values over from the Entity I am trying to submit. HasChanges = false on this new entity (which would expain why it's not calling update).

    This _only_ happens if I modify this computed property. If I leave it as is, I can update other normal properties without any problems. As soon as I change the computed property, then it news up a new Entity, copies the values over - HasChanges = false... Nothing is persisted.

     

     

     

     

     

     

     

    Sunday, August 16, 2009 3:53 AM
  • Try adding a property that sets your calculated property and calls notify change for the calculated property. See what happens in that case...

    Sunday, August 16, 2009 4:09 AM
  •  I've just started up a new application to try and sandbox this issue, and it pretty much worked straight away.

    So now I just need to figure out why it isn't working in the full app.

    Sunday, August 16, 2009 6:51 AM
  •  Ok I've narrowed it down, here is a simple reproduceable case:

     

    I've created a simple Person Class:

     

        public partial class Person {

    [Key]
    public int ID { get; set; }
    public string Description { get; set; }

    }
      

     I've a RIA PersonDomainService:

     

        [EnableClientAccess()]
    public class PersonDomainService : DomainService {

    public Person GetPerson() {
    var person = new Person();
    person.ID = 1;
    person.Description = "stage one ";
    return person;
    }

    public void UpdatePerson(Person person) {

    }
    }
      I've set a breakpoint on UpdatePerson to know when it's being called.

     On the silverlight side of the fence, it looks like this:

     

     

            PersonDomainContext PDC;

    public MainPage() {
    InitializeComponent();
    PDC = new PersonDomainContext();
    LoadOperation LoadOp = PDC.Load(PDC.GetPersonQuery());
    LoadOp.Completed += new EventHandler(LoadOp_Completed);
    }

    void LoadOp_Completed(object sender, EventArgs e) {
    Person p = PDC.Persons.First();
    p.Description = "second stage";
    PDC.SubmitChanges();
    }

    This fires off to RIA, gets the person, changes the description and submits changes. This all works entirely fine. My breakpoint is hit on UpdatePerson

     

    Now I add a Person.shared.cs file, and extend my partial class with this:

     

     

        public partial class Person {

    public string Desc {
    get {
    return "woooo!";
    }
    }
    }
      

    And again, this works fine. If I change it however to:

       

        public partial class Person {

    public string Desc {
    get {
    return this.Description;
    }
    }
    }

     

     

    Then UpdatePerson breakpoint is never reached. I've tried adding a RaisePropertyChanged("Desc") in the OnDescriptionChanged method on a client side Person partial class, but still no dice.
     I would really appreciate if someone could perhaps confirm that this is the same behaviour on their machine. At the moment my thoughts are that I've either overlooked something stupid, or I'm hitting a bug.
     
     
     
     
     

     

    Sunday, August 16, 2009 10:35 PM
  • I am going to guess that the problem is that the Desc property is read only but you don't have metadata saying that. When the entity comes back to the DomainService it is trying to deserialize the property and failing on the read only.

    Have you tried attaching a metadata class to Person with an [Exclude] on Desc? I think that would fix this.

    Sunday, August 16, 2009 10:46 PM
  • Have you tried attaching a metadata class to Person with an [Exclude] on Desc? I think that would fix this.
     

     

     Then it won't be available on the client side - which is the whole dilemma :) I've also tried adding ReadOnly attribute and that didn't help either.

     

     

     

     

    Sunday, August 16, 2009 11:08 PM
  • Have you tried attaching a metadata class to Person with an [Exclude] on Desc? I think that would fix this.
     

     

     Then it won't be available on the client side - which is the whole dilemma :)

    Sure it will, it will get there from the shared file.

    Sunday, August 16, 2009 11:16 PM
  • Sure it will, it will get there from the shared file.
     

     

    Ok, perhaps I'm not understanding this correctly.

    But I cannot have :

     
    Person.cs:

    [Exclude]
    public string Desc....

    Person.shared.cs

    public string Desc...
     It won't compile,  "already contains definition for Desc..."

    EDIT: Quick follow up. Instead of using the shared code feature - I simply created a partial Person class on the client/silverlight side and stuffed my Desc property in there. And it worked. Obviously there's a big issue here in that I have to maintain my model in two separate places - which I thought was the whole idea of shared code!
    Sunday, August 16, 2009 11:27 PM
  • Is your exclude in the Person class or the PersonMetadata class? It should look something like this:

     

    [MetadataType(typeof(Person.PersonMetadata))]
        public partial class Person
        {
            internal sealed class PersonMetadata
            {
                [Exclude]
                public string Desc;
            }
        }
    
     
    Sunday, August 16, 2009 11:41 PM
  •  Colin, I could hug you right now. Using the separated Metadata class and flagging the properties that were in the shared.cs as [Exclude] did the trick, and I've now got things working.

     

     

    Monday, August 17, 2009 12:34 AM