New Posts New Posts RSS Feed: InvokeServerMethodAsync is calling the Server Method twice...why?
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

InvokeServerMethodAsync is calling the Server Method twice...why?

 Post Reply Post Reply
Author
skingaby View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 23-Apr-2008
Location: United States
Posts: 146
Post Options Post Options   Quote skingaby Quote  Post ReplyReply Direct Link To This Post Topic: InvokeServerMethodAsync is calling the Server Method twice...why?
    Posted: 13-Jul-2009 at 3:04pm
I have a Silverlight Unit Test project with this class:

[TestClass]

public class DealTesting : SilverlightTest
{
     [TestMethod]
     [Asynchronous]
     public void PromoteADeal()
     {
          _em = new DomainModel.DomainModelEntityManager();
          EnqueueCallback(() => _em.LoginAsync(null, LoginCallbackForPromoteADeal, null));
     }
     #region PromoteADeal
     private void LoginCallbackForPromoteADeal(LoginEventArgs args2)
     {
          LocalEntityManager.DefaultManager = _em;
          EnqueueCallback(() => Deal.GetADealAsync(args => GetADealCallback(args)));
     }

     public void GetADealCallback(InvokeServerMethodEventArgs args)
     {
          if (args.Error != null)
               throw new Exception("GetADealAsync Method Failed", args.Error);

          Deal deal = (Deal)args.Result;
          EnqueueTestComplete();
     }
}


The class under test has this code in it:

public partial class Deal

{
     public static void GetADealAsync(AsyncCompletedCallback<InvokeServerMethodEventArgs> callback)
     {
          var manager = LocalEntityManager.DefaultManager;
          ServerMethodDelegate mydelegate = new ServerMethodDelegate(GetADealServer);
          Guid myToken = Guid.NewGuid();
          manager.InvokeServerMethodAsync(mydelegate, callback, myToken);
     }

     [AllowRpc]
     public static Deal GetADealServer(IPrincipal principal, EntityManager em, params object[] args)
     {
          var deal = GetADealCore();
          Logging.LogMessage(String.Format("GetADealServer: Deal ID {0}", deal.DealId));
          return deal;
     }

     public static Deal GetADealCore()
     {
          Deal deal = Deal.Create(SampleUser);
          return deal;
     }
}


When I run this test, the client method Deal.GetADealAsync is called once, but the server method Deal.GetADealServer is called twice. Why? This has the effect of creating 2 Deals, not 1.

Also, instead of Deal.GetADealServer returning the deal in the args of the GetADealCallback, I get a server error: "The remote server returned an error: NotFound". How can I find out what the real error is? And how do I debug this?
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post Posted: 13-Jul-2009 at 10:51pm
Deal.GetADealServer is being called twice because of the error - the NotFound error indicates a CommunicationException, and DevForce will attempt a retry of the service call before failing. 

Check the server's debuglog to see if any additional error information was logged, in addition to the InnerException and/or RemoteException properties on the EntityServerException returned to the client.  The Deal type should be both serializable and a "known type".  To make it a known type, implement the marker interface IKnownType or adorn with DiscoverableType(DiscoverableTypeMode.KnownType).
Back to Top
skingaby View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 23-Apr-2008
Location: United States
Posts: 146
Post Options Post Options   Quote skingaby Quote  Post ReplyReply Direct Link To This Post Posted: 14-Jul-2009 at 7:44am
Woohoo. I added the IKnownType interface to the Deal class and it is now being returned to the client test. However, ALL of its properties, including Entity properties, for example, Deal.Contact (which is an Entity type Contact), and normal properties, for example, Deal.DateEntered (a date) or Deal.Comment (a string), are throwing a NullReferenceException.
The property inspector in the watch window shows all properties of Deal are now showing "'deal.xxxx' threw an exception of type 'System.NullReferenceException' where xxxx is the name of the property.

Do you have any hints as to how I can fix this so the Deal is coming back to the Client with all of its properties? Thanks, Simon.
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post Posted: 14-Jul-2009 at 8:35am

Oh sorry, I didn't realize that Deal was an entity sub-type.  Entities cannot be directly serialized, yet.  You can work around this by returning an EntityCacheState holding the Deal and Contact entities.  In your sample, I'm not sure in which EntityManager Deal and Contact exist, but you can do something like this as the return value from GetADealServer -

     [AllowRpc]
     public static EntityCacheState GetADealServer(IPrincipal principal, EntityManager em, params object[] args)
     {
          var deal =  GetADealCore();
          Logging.LogMessage(String.Format("GetADealServer: Deal ID {0}", deal.DealId));

          return deal.EntityManager.CacheStateManager.GetCacheState();
     }

On the client side, you'll need to use the EntityCacheState.Merge(EntityManager, RestoreStrategy) method to merge these entities into a client-side EntityManager.
 
(You can remove IKnownType from the Deal definition.)
Back to Top
tj62 View Drop Down
Groupie
Groupie
Avatar

Joined: 21-May-2009
Location: Iceland
Posts: 81
Post Options Post Options   Quote tj62 Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 5:28am
Hi,
I'm trying to do similar things with InvokeServerMethodAsync():
 
   Guid myToken = Guid.NewGuid();
   object[] param = {userID};
   m_mgr.InvokeServerMethodAsync("C2NetDomainModel.EntCustomer,C2NetDomainModel" ,
                                                         "GetCustomers",GotCustomers,myToken, userID);
 
The server method "GetCustomers" is called successfully where I do this:

   [AllowRpc]
   public static EntityCacheState GetCustomers( IPrincipal pPrincipal ,
               EntityManager eMgr ,
               params Object[] args )
   {
    string userID = (string)args[0];
    C2NetDomainModelEntityManager mgr = (C2NetDomainModelEntityManager)eMgr;
    ...
    var customerQ = mgr.EntCustomerSet
        .OrderBy( c => c.p_name )
        .WhereIn( c => c.p_customerID , (from d in customerIDs
                 select d.customerID).ToArray() );

   // return customerQ;
   // Actualy I need to return all EntCustomer returned from customerQ
   // However for test purpose I start returning the first
    EntCustomer firstCustomer = customerQ.FirstOrDefault();
    return firstCustomer.EntityManager.CacheStateManager.GetCacheState();
   }

This method works fine. But when the receiver method
public void GotCustomers(InvokeServerMethodEventArgs args)
receives the result there is an error in args.Error.Message saying:

Could not load file or assembly 'IdeaBlade.EntityModel.Edm, Version=5.2.2.0, Culture=neutral, PublicKeyToken=287b5094865421c0' or one of its dependencies. The system cannot find the file specified.
 
The assembly IdeaBlade.EntityModel.Edm is referenced by the web application and I have tried to add it also to the assembly containing the server method (C2NetDomainModel.EntCustomer.GetCustomers). But I still get the same error.
What is the reason for that? 

Second question is; If it is still not possible to return Entities from the server method (Dev Force 5.2.2.0), is there any workaround to return multiple entities similar to the one I use for returning single entity in my example above? 
Back to Top
skingaby View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 23-Apr-2008
Location: United States
Posts: 146
Post Options Post Options   Quote skingaby Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 11:39am
Two things:
1) I think it may be a lot easier to call query.ExecuteAsync instead of using this technique; and
2) It sounds like the Silverlight/non-Silverlight versions of the Ideablade dlls may be mixed up.

1) It looks like the ASync method is not doing any server side processing. In my situation, I had a method that called a server side proc that created records which I needed to get back to the client somehow. Even in that situation, we ended up sending an ID back from the Async method and then calling a query.ExecuteAsync in the client side callback.

C2NetDomainModelEntityManager mgr = (C2NetDomainModelEntityManager)eMgr;
    ...
    var customerQ = mgr.EntCustomerSet
        .OrderBy( c => c.p_name )
        .WhereIn( c => c.p_customerID , (from d in customerIDs
                 select d.customerID));
    customerQ.ExecuteAsync(GotCustomers, myToken);

The callback will get a collection of Customer entities in the GotCustomers args.Result property.

2) Make sure the client side Silverlight project is using the .SL versions of the Ideablade dll's and that the server side project is using the regular dll's. I've also had problems with this error when some other project in the solution has mixed up the references. We also had this in the unit tests because although the files were there, the projects in the test run folder weren't finding them. We had to make sure the right files were being copied to the unit test in its deployment configuration.


Edited by skingaby - 28-Sep-2009 at 11:41am
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 1:47pm
Regarding the return of multiple entities -- if that's what you end up wanting to do, using the EntityCacheState is still the best approach.  The call entityManager.CacheStateManager.GetCacheState() will return an ECS containing all entities in that EntityManager's cache, not just a single entity.  There's also an overload to determine the list (one or more) entities to be included. 
 
Entities can now be directly serialized, but it looks like there are still some issues in getting them returned correctly from an RPC method.
Back to Top
tj62 View Drop Down
Groupie
Groupie
Avatar

Joined: 21-May-2009
Location: Iceland
Posts: 81
Post Options Post Options   Quote tj62 Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 3:00pm
First of all Skingaby and Kimj, thank you for responding.
Regarding "1" from Skingaby, you are not right as I replaced lot of code for the "..." to shorten text and leave things that are not of importance in the problematics here. This is code to evaluate the customerIDs array that is quite complex in my case needing many LINQ queries that can not be combined in one qurery. Therefore I'm doing it such to minimize client/server roundtrips. Secondly I have to learn how this function as I need to use the InvokeServerMethodAsync in other "pits" of my system.

Regarding "2" from Skingaby, I have gone through all references to check that those are set correctly at client side as well as server side, both by checking the references in the projects as well as cheking the DLLs in folders.
Yes only .SL references are at client side but none-.SL references on the server side. However there does not exist any any IdeaBlade.EntityModel.Edm.SL to set client side. Should it exist somewhere?
 
Those are the references at my client side:
IdeaBlade.Core.SL.dll
IdeaBlade.EntityModel.SL.dll
IdeaBlade.Linq.SL.dll
IdeaBlade.Validation.SL.dll
...all found in the Bin\Debug of the client projects.

Note that if I simplify my server method to return somethign simple like integer or string everything works fine.

Greetings from bankrupt Iceland :-)
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 4:09pm
I apologize tj62, but it looks like a bug was introduced a few releases ago affecting the serialization of the EntityCacheState in Silverlight applications.  Right now, your best option is probably to return a list of entity IDs, and then issue a query for those IDs once back on the client.   This problem will be fixed in the mid-October release.
 
As for the SL assemblies -- the four listed above are the only DevForce assemblies you need "client" side.
Back to Top
skingaby View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 23-Apr-2008
Location: United States
Posts: 146
Post Options Post Options   Quote skingaby Quote  Post ReplyReply Direct Link To This Post Posted: 28-Sep-2009 at 4:49pm
Ah. I see. With regard to the references, sorry I wasn't helpful. One other place we had problems was at deployment. Everything worked fine on the Dev box and the build worked, but the web site on the server was missing some of the needed DLL's. I had to add a bunch of references to the Web Server project to pull in those DLL's. Once that was done, the site started working properly. Not sure if that will help either.

As for the other. We created a Service class that manages the Async stuff and handles the client-server conversation. In this sample, the Deal class can be Promoted. On the client side, the UI Command initiates the deal.Promote(user) method, which instantiates the DealPromotionService and kicks that off. Back on the server, the [AllowRpc} ends up Fetching the Deal with the passed DealId, tells that deal to promote itself, server side, and then returns back to the Service's callback, where the Service then executes an AsyncQuery to refresh the client cache, when that calls back, the Service raises a PromotionCompleted Event that the client side Deal was watching for so that it can do raise the PropertyChanged event that will update the grid/UI.

I don't know if this is a "Good" way to do this, but it is a way and dealt with some of the issues we were having with a long stack of callbacks across projects that just got too confusing and sometimes failed to ever call back.

Here's the code:

public partial class Deal
{
     public void Promote(User user)
     {
          var dps = new DealPromotionService(user, this, PromotionCompletedEventHandler);
     }

     private void PromotionCompletedEventHandler(object o, DealPromotionEventArgs e)
     {
          ...
          OnDealPromoted();
     }

     public event EventHandler<DealPromotionEventArgs> DealPromoted;

     private void OnDealPromoted()
     {
          ...
          if (DealPromoted != null)
               DealPromoted(this, new DealPromotionEventArgs(this, this.Promoted));
     }
}

public class DealPromotionService
{
     public DealPromotionService(User user, Deal deal, EventHandler<DealPromotionEventArgs> promotionCompletedEventHandler)
     {
          ...
          PromoteAsync();
     }

     public event EventHandler<DealPromotionEventArgs> DealPromotionCompleted;

     private void OnDealPromotionCompleted()
     {
          if (DealPromotionCompleted != null)
               DealPromotionCompleted(this, new DealPromotionEventArgs(this.Deal, this.Deal.Promoted));
     }

     private void PromoteAsync()
     {
          var manager = LocalEntityManager.DefaultManager;
          ServerMethodDelegate myDelegate = new ServerMethodDelegate(Promote);
          manager.InvokeServerMethodAsync(myDelegate, PromoteAsyncCallback, this.UserState, this.User.UserId, this.Deal.DealId, this.Deal.SuppressConfirmation); //can't pass entities as parameters in 5.2.1
          //calls back to PromoteAsyncCallback
     }

     [AllowRpc]
     public static string Promote(IPrincipal principal, EntityManager em, params object[] args)
     {
          User user = new User((string)args[0]);
          Deal deal = Deal.Fetch((long)args[1]);
          return Promote(user, deal);
     }

     private static string Promote(User user, Deal deal)
     {
          //server side
          deal.PromoteCore(user);
          return deal.Promoted.ToString(); //return "true" or "false" but boolean does not work as a return type in [AllowRpc]
     }

     private void PromoteAsyncCallback(InvokeServerMethodEventArgs args)
     {
          if (((Guid)args.UserState).Equals(this.UserState))
          {
               //back on the client
               bool success = Convert.ToBoolean(args.Result);
               if (success)
               {
                    //need to refresh
                    FetchAfterPromoteAsync();
               }
               else
               {
                    OnDealPromotionCompleted();
               }
          }
     }

     private void FetchAfterPromoteAsync()
     {
          var qs = new QueryStrategy(FetchStrategy.DataSourceAndCache, MergeStrategy.PreserveChangesUnlessOriginalObsolete);
          var qry = this.Deal.QueryRefreshInPromotion(qs);
          qry.ExecuteAsync(FetchAfterPromoteCallback, this.UserState);
     }

     private void FetchAfterPromoteCallback(EntityFetchedEventArgs<Deal> args)
     {
          if (((Guid)args.UserState).Equals(this.UserState))
          {
               OnDealPromotionCompleted(); //finally, raise this event as the service is finished promoting
          }
     }
}



Edited by skingaby - 28-Sep-2009 at 4:51pm
Back to Top
tj62 View Drop Down
Groupie
Groupie
Avatar

Joined: 21-May-2009
Location: Iceland
Posts: 81
Post Options Post Options   Quote tj62 Quote  Post ReplyReply Direct Link To This Post Posted: 29-Sep-2009 at 4:28am
Thank you Kimj, I followed your advice and did this in two round trips to the server, first retrieveing the customerIDs by calling InvokeServerMethodAsync and then by executing the query InvokeServerMethodAsync for the final query.
However I ran into two minor problems I want to ask you about (I'mstill a newbie, remember):
 
Probelm 1:
From the documentation this should be possible:
 
    var customerQ = m_mgr.EntCustomerSet
           .OrderBy( c => c.p_name )
           .WhereIn( c => c.p_customerID , customerIDs );
    m_mgr.ExecuteQueryAsync( customerQ , GotCustomers , null );
 
But this gives me acompilation error for the last line:
 
Error 2 Argument '1': cannot convert from 'System.Linq.IQueryable<C2NetDomainModel.EntCustomer>' to 'IdeaBlade.EntityModel.IEntityQuery' C:\Visual Studio 2005\Projects\Control2NetSilver\C2NetSilver\MainPage.xaml.cs 129 30 C2NetSilver
So as originating from the good old times of C-language I modified the last line with a type cast:   
 m_mgr.ExecuteQueryAsync( (IEntityQuery<EntCustomer>)customerQ , GotCustomers , null );
 
Then it compiled. Is this type cast realy needed all the time? It's ugly and in general dangerous doing type casts.
And how should I type cast when calling ExecuteQueryAsync() with projected queries (where only few properties of an entity is returned by the select command)?
Even worse, how can I put such a query into ExecuteQueryAsync():
   var userQ = (from user in m_mgr.EntUserSet
        where user.p_userID == userID
        select new {user, userGroups = user.p_UserGroups}).FirstOrDefault();

Whatever I try I allways get:
Error 2 Argument '1': cannot convert from 'AnonymousType#1' to 'IdeaBlade.EntityModel.IEntityQuery' C:\Visual Studio 2005\Projects\Control2NetSilver\C2NetSilver\MainPage.xaml.cs 138 30 C2NetSilver
Or something like that.
 
Problem2:
As in the documentation I created the GotCustomers() receiver method such:
 
  private void GotCustomers(EntityFetchedEventArgs args)
  {
   if (args.Error != null) {
    WriteMessage(args.Error.Message);
   }
   else {
    foreach( EntCustomer c in (System.Linq.IQueryable)args.Result )
    {
        ....
    }
  }
  }
When running this gave me an exception:
Unable to cast object of type 'System.Collections.Generic.List`1[C2NetDomainModel.EntCustomer]' to type 'System.Linq.IQueryable'.
 
After changing the foreach statement  to:

foreach( EntCustomer c in (List<EntCustomer>)args.Result )
 
everything worked as expected.
I'm currently using the newest version of IdeaBlade (version 5.2.2.0). However this problem did not exist in version 5.2.1.0. What has changed?
 
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post Posted: 29-Sep-2009 at 1:07pm
Problem 1 -
 
First, just to have something to reference, these are the two async query methods on the EntityManager:

public void ExecuteQueryAsync(IEntityQuery query, AsyncCompletedCallback<EntityFetchedEventArgs> userCallback, Object userState);
public void ExecuteQueryAsync<T>(IEntityQuery<T> query, AsyncCompletedCallback<EntityFetchedEventArgs<T>> userCallback, Object userState);
So you can use either the generic or non-generic signature, and you can pass either an IEntityQuery or IEntityQuery<T>. 
 
Part a - Casting is needed when the query has been modified by additional clauses.  In this case WhereIn returns an IQueryable<T>, and I guess we can blame the CLR type system and contravariance for requiring the cast.  Check out the PredicateBuilder -- it probably does the same thing as WhereIn but the Expression predicate created (an Expression<Func<T, bool>>) can be used with the .Where clause to return an IEntityQuery<T> so that casting is not required.
 
Part b - When querying anonymous types you'll use the non-generic overload.  If you do need to cast, again because of a clause which has changed the return type, just cast to IEntityQuery.
 
Part c - FirstOrDefault.  Unfortunately, immediate execution queries such as this don't work in async mode.  That's something we'll look at addressing in a future release. 
 
Problem 2 - The type of args.Result will depend on the signature used when calling ExecuteQueryAsync, and it will be either an IEnumerable or IEnumerable<T>.  There shouldn't be any need to cast to an IQueryable or to a List<T>.  If you used the non-generic signature you can use args.Result.Cast<T> if you know T. 
 
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down