WPF DataBinding issue with remoting

Posts   
 
    
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 03-Nov-2010 22:43:33   

LL 3,Oracle, Modified adapter templates for remoting for client/server app

In my remoting templates/api, I generate save calls that pass the entity as a ref since it's possible the entity will get updated at the server with changes like sequence id, etc. So the result is that I'm essentially getting back a new instance of the entity. So I need to rebind the form. So I set the DataContext to this new instance. Sometimes it works, sometimes it doesn't.

What's happening is that DataContext is calling Equals and I'm guessing LL templates are overriding this and sometimes returning true if nothing changed after the save/refetch. And thus sometimes my form is being left bound to the original entity and the user can no longer save since all changes are going to the wrong instance of the object.

Found this blog post: http://kentb.blogspot.com/2007/03/beware-datacontext-and-equals.html

I tried that fix of setting DataContext to null and for the most part it works, but it has a really bad side effect. Now, when navigating from record to record on the same screen constantly giving it a new record, it's like databinding is not keeping up, and not all my fields get updated on the screen. It seems like the databinding events fire afterwards and me setting it twice is getting the system mixed up.

1) Does LL generate an Equals override that is possibly returning true if the instances are different but the properties are the same? 2) Can I easily turn that off or do I need to remove it from a template? 3) If I do remove it, are there going to be other side effects? 4) Any ideas on how I can solve this issue without having to set the DataContext to null?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Nov-2010 06:39:34   

LLBLGen version and runtime library version?

Also, you can serialize both instances (original and new) and compare the serialized versions. If no change, just don't rebind the form.

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 13:16:47   

daelmo wrote:

Also, you can serialize both instances (original and new) and compare the serialized versions. If no change, just don't rebind the form.

That is the fix I essentially implemented. But I wasn't serializing them. I just added my own check, calling Equals, and if Equals returns true, then I throw away the version returned from the remoting call.

Is comparing the serialized versions a more accurate comparison than calling Equals ?

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 13:46:44   

happyfirst wrote:

daelmo wrote:

Also, you can serialize both instances (original and new) and compare the serialized versions. If no change, just don't rebind the form.

That is the fix I essentially implemented. But I wasn't serializing them. I just added my own check, calling Equals, and if Equals returns true, then I throw away the version returned from the remoting call.

Is comparing the serialized versions a more accurate comparison than calling Equals ?

Also, I just discovered that the server is updating a timestamp field on the entity. And yet, when the call returns and I call equals on two versions, Equals returns TRUE, even though the timestamp property is different on both fields.

What's the simplest way to compare using serialization?

And why does Equals return TRUE when the two objects being compared have different ModifiedOn timestamps?

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 15:48:32   

More info. It seems the only way to properly solve my problem is to overload Equals so that it more correctly returns true or false.

I'm still stumped on why Equals returns true for two entities, before and after the remoting call, that are obviously not equal. There's also a rowVerson field the server is updating and so the before is version X, the after is version X+1, and yet Equals still returns true on those two objects.

So I implemented a custom overload in one object for now, but I fear I need to place this code into the LLBL templates (using LLBL 3.0) to auto generate my own Equals for all the objects.

This is the quick code I came up with although I'm a bit nervous using a fix that involves overloading Equals

public override bool Equals(System.Object obj) { if (obj == null) return false; if (!obj.GetType().Equals(this.GetType()) )return false; return this.Equals( ([OBJECTTYPE]) obj ); }

    public bool Equals([OBJECTTYPE] obj)
    {
        if (obj == null) return false;

        int code1 = this.GetHashCode();
        int code2 = obj.GetHashCode();
        return code1.Equals(code2);

    }

    public override int GetHashCode()
    {
        int hashCode=base.GetHashCode();            
        //
        PropertyInfo[] properties=this.GetType().GetProperties();
        foreach (PropertyInfo propertyInfo in properties)
        {
            object value=propertyInfo.GetValue(this,null);
            if (value!=null)
                hashCode = hashCode ^ value.GetHashCode();
        }
        //
        return hashCode;
    }

Any ideas on what's up with the default Equals ?

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 17:59:37   

Had to modify my GetHashCode as it was always returning a different value for the same object no matter what.

Now I start with int hashCode=0 and I skip the "Relations" property as I'm guessing that must alway be returning a new object.

Now it generates the same hashcode for my object.

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 19:23:06   

ok, I think I found the "issue" that's affecting me.

I downloaded your llbl pro framework source and am just looking through the code. Not actually linked and debugging it. I hope I'm looking at the right code.

I see that you do override Equals in EntityBase2.cs. And it basically passes down into the _fields.Equal. And the comments for that indicate that you will return TRUE if just the primary key values match. Ouch....

Well, I'm now confused and don't know what really to do now. I'm partly amazed that Equals was overloaded to return true based on that logic. I have a case where two objects with the same primary keys values but different other values (before, after) are being treated as the same by WPF databinding when in fact, they are NOT equal.

Why was Equals overloaded to do this?

Why isn't there just a seperate PrimaryKeysAreEqual method that you would use?

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 04-Nov-2010 21:21:38   

Frans will probably correct me if I'm wrong, but .Equals on an entity is telling you about the entity itself, not the state that it's properties are in. Two instances of an entity with the same Primary key ARE the same entity (ie they both represent the same row in the database) - the fact that one of them has been modified is immaterial.

Could you create your own extension method (.EqualsByValues ?) which does a field level compare for you...?

Matt

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Nov-2010 21:43:39   

MTrinder wrote:

Frans will probably correct me if I'm wrong, but .Equals on an entity is telling you about the entity itself, not the state that it's properties are in. Two instances of an entity with the same Primary key ARE the same entity (ie they both represent the same row in the database) - the fact that one of them has been modified is immaterial.

Could you create your own extension method (.EqualsByValues ?) which does a field level compare for you...?

Matt

Well, I disagree.

In any event, there's no point in me creating an EqualsByValues. WPF DataBinding INTERNALLY is calling Equals and WPF is really asking if they are equal, not if they are the same record in the db.

IMO, LLBL should be the one making a call to EqualsByPrimaryKeys() or IsSameDbRow() .

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 05-Nov-2010 05:07:24   

If entities are not new, it compares PK fields. If not clear what Equals should do otherwise (compare current values, db values, compare through db, compare entity properties, etc.). If entities are new, .Equals performs an instance comparison.

As I said before, you can compare serialized version of the entities, or override Equals in the CommonEntityBase at the CUSTOM_CODE_REGION in your DBGeneric project and implement your own comparison. (Note that doing this could affect the way entities are compared when they belong to collections).

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 05-Nov-2010 13:42:02   

daelmo wrote:

If entities are not new, it compares PK fields. If not clear what Equals should do otherwise (compare current values, db values, compare through db, compare entity properties, etc.). If entities are new, .Equals performs an instance comparison.

I understand people have different concepts of what equals should really mean, but to me, for the majority of situations I remember reading about, the different debates have boiled down to one of two scenarios:

1) Are they Equals() as in the SAME, pointing to the same address in memory OR 2) Are they Equals() as in two IDENTICAL copies with all their properties the same except that they are two copies, each pointing to a different place in memory.

LLBL is neither of these. You overloaded it for a reason obviously, at least one involving your collections, but those collections classes are yours and IMO, if they're needing to compare based on primary keys, then your collection classes should have been calling some other EqualsByPrimaryKeys method instead of changing the definiton of Equals for everybody and everything to mean PartiallyEquals or EqualsEnough.

Anyways, it is what it is. I've overloaded it to be more accurate and now I just have to wonder what side effects I might have from that.

It would be helpful if you can think of any other areas besides collections that might be affected by me restoring Equals back to what it should be.

daelmo wrote:

As I said before, you can compare serialized version of the entities, or override Equals in the CommonEntityBase at the CUSTOM_CODE_REGION in your DBGeneric project and implement your own comparison. (Note that doing this could affect the way entities are compared when they belong to collections).

Thanks for that tip. I was generating my Equals overload into each object but I will look into placing my overload in CommonEntityBase instead.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 05-Nov-2010 13:56:10   

Equals is implemented that way because the entity class instance is a bucket and the entity instance is the data inside it. The Equals compares entity instances not bucket instances, as that's irrelevant.

I.e. when I do: var c1 = new CustomerEntity(1); // fetch c1 var c2 = new CustomerEntity(1); // fetch c2

are these equal?, from the perspective of the entity: yes. From the perspective of the container, the entity class instance, no they're not. But what's it that you are after? Likely the equality check of the entity instance (== the data) simple_smile

There's a problem with data checking on new entities: identity pk's have identical values, so the containers are compared. This is logical, as a new entity doesn't actually exist unless it's written to the db. So if I do: var c1 = new CustomerEntity(); c1.Company = "SD"; var c2 = new CustomerEntity(); c2.Company = "SD";

are these equal? No. When I save both, I get two entity instances in the db. simple_smile So containers are compared in memory.

To overcome the confusion, a thing called 'uniquing' is used: every entity instance is stored in the same container, so var c1 = new CustomerEntity(1); // fetch c1 var c2 = new CustomerEntity(1); // fetch c2

can't happen, c1 has to be the same object (!) as c2. This is achievable through the context class.

You see even more how important entity instance comparisons are when remoting data, like you do: the entity instance is the same on both sides, but the entity container (class instance) isn't.

Hopefully this sheds some light on it. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 05-Nov-2010 14:57:01   

Otis wrote:

Hopefully this sheds some light on it. simple_smile

Partially. Regarding new entities, I understand/agree with that implementation. I'm still just confused on the implementation regarding not new entities and mostly the why.

Otis wrote:

To overcome the confusion, a thing called 'uniquing' is used: every entity instance is stored in the same container, so var c1 = new CustomerEntity(1); // fetch c1 var c2 = new CustomerEntity(1); // fetch c2

can't happen, c1 has to be the same object (!) as c2. This is achievable through the context class.

You see even more how important entity instance comparisons are when remoting data, like you do: the entity instance is the same on both sides, but the entity container (class instance) isn't.

It sounds like the LLBL framework is doing everything possible to not ever have two instances, ie copies, of the same existing valid record (fields?) in memory at the same time. Is this correct?

Are you saying that if I load, ie new, a valid existing customer record ID=1 into two seperate instances, that behind the scenes, they're really pointing to the exact same single fields instance? So I have TWO instances of a wrapper pointing to the single instance of the fields?

Regardless, what I don't understand is that if all this logic is what LLBL needed, why LLBL isn't just using it's own special name method for comparing on primary keys instead of why you overloaded it.

Basically, I'm in a bind, maybe I should care less about the why and just come up with a fix. WPF DataBinding is calling Equals, there's no getting around that, and it really is asking about the whole object. I have changes happening to other fields in the db and they don't show up when I save. Or there are no changes,but I still can't make a subsequent edit since the form is stuck bound to the original instance. There is no point in me doing my own check for equality for binding because the issue is they are different and I NEED WPF to see that they are different. So it seems my only options are to overload equals or not use databinding.

What would you do? Is overloading equals the right fix? What are all the possible side effects?

psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 05-Nov-2010 19:14:15   

Based on the messages in this thread, it seems to me that if this could be resolved via WPF (i.e. without overriding equals) this would be an acceptable workaround?

Have you tried setting the DataContext to null and then back to the entity via Dispatcher.Invoke, and maybe playing around with different dispatcher priority levels when you make that call?

Also, if that doesn't work: if you're willing to post or email a simple repro case using WPF and an entity, I'd be happy to take a look.

Phil

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 08-Nov-2010 01:48:05   

psandler wrote:

Based on the messages in this thread, it seems to me that if this could be resolved via WPF (i.e. without overriding equals) this would be an acceptable workaround?

Have you tried setting the DataContext to null and then back to the entity via Dispatcher.Invoke, and maybe playing around with different dispatcher priority levels when you make that call?

Also, if that doesn't work: if you're willing to post or email a simple repro case using WPF and an entity, I'd be happy to take a look.

Phil

I tried settng the DataContext to null but that causes other "issues", mainly, some controls on the screen end up getting blanked out. All I can think of is that binding events for the two different DataContext sets are firing out of order.

I will experiment with invoking using a priority but I have not had much luck in the past when trying this for other issues. What exactly were you thinking?

Now that I understand the problem, there are many scenarios that "should" be easy to duplicate. For ex, create a form, bind it to an entity. Have a refresh button to reload the record and rebind the form to the new record. Load and display the form and display a record. Change the data for that record (something besides the pks) in the database. Now hit the refresh button. I would expect your changes to NOT show up.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 08-Nov-2010 10:42:01   

**Otis wrote: ** To overcome the confusion, a thing called 'uniquing' is used: every entity instance is stored in the same container, so var c1 = new CustomerEntity(1); // fetch c1 var c2 = new CustomerEntity(1); // fetch c2

can't happen, c1 has to be the same object (!) as c2. This is achievable through the context class.

You see even more how important entity instance comparisons are when remoting data, like you do: the entity instance is the same on both sides, but the entity container (class instance) isn't.

happyfirst wrote:

It sounds like the LLBL framework is doing everything possible to not ever have two instances, ie copies, of the same existing valid record (fields?) in memory at the same time. Is this correct?

Are you saying that if I load, ie new, a valid existing customer record ID=1 into two seperate instances, that behind the scenes, they're really pointing to the exact same single fields instance? So I have TWO instances of a wrapper pointing to the single instance of the fields?

I think you have mis-understood it. What Frans is saying is that if you need uniquing, you should use teh Context class. But he didn't say this happens by default, coz it's not.

No uniquing is done by default, you have to use a Context if you want uniquing.

psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 08-Nov-2010 15:34:38   

happyfirst wrote:

I tried settng the DataContext to null but that causes other "issues", mainly, some controls on the screen end up getting blanked out. All I can think of is that binding events for the two different DataContext sets are firing out of order.

I will experiment with invoking using a priority but I have not had much luck in the past when trying this for other issues. What exactly were you thinking?

I think if you use the dispatcher and tinker with priority, it could solve the problem with binding events being fired out of order (if that is indeed the problem). I have had similar problems in the past (not with LLBL objects) and doing that solved it. BTW, my post above says to try Dispatcher.Invoke--what I really meant was BeginInvoke (async).

happyfirst wrote:

Now that I understand the problem, there are many scenarios that "should" be easy to duplicate. For ex, create a form, bind it to an entity. Have a refresh button to reload the record and rebind the form to the new record. Load and display the form and display a record. Change the data for that record (something besides the pks) in the database. Now hit the refresh button. I would expect your changes to NOT show up.

I have a bit of time to take a look if you want to email me a repro and am happy to do it, but I don't have time to set up the repro myself. My email address is in my .sig.

Another thing you could try is having a "master" entity that gets bound, and any changes to any of the other copies of the same entity simply get copied, field by field, to the master entity. You can do this pretty simply be iterating over the Fields collection of the entity. A simple, 5-10 line method that does this could be used for any/all entities in the project. It's hard to know if this is good advice without having more context around what you are trying to do.

happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 08-Nov-2010 16:29:20   

psandler wrote:

I have a bit of time to take a look if you want to email me a repro and am happy to do it, but I don't have time to set up the repro myself. My email address is in my .sig.

Another thing you could try is having a "master" entity that gets bound, and any changes to any of the other copies of the same entity simply get copied, field by field, to the master entity. You can do this pretty simply be iterating over the Fields collection of the entity. A simple, 5-10 line method that does this could be used for any/all entities in the project. It's hard to know if this is good advice without having more context around what you are trying to do.

Thanks for the offer. When I get a chance, I will setup a case.

I also already tried copying the fields into a master entity, but some llbl fields are read only and so not all the data would copy.