DataPortal.BeginFetch with lambda expression doesn't compile.

DataPortal.BeginFetch with lambda expression doesn't compile.

Old forum URL: forums.lhotka.net/forums/t/8720.aspx


KevinKlasman posted on Wednesday, March 24, 2010

I'm using VS2010 Beta 2, SL4, CSLA 3.8.2, Expert C# 2008 Business Objects book.

On page 433 is some sample code (similar to below) that generates a "Cannot convert lambda expression to type 'object' because it is not a delegate type" compile error.

            var dp = new DataPortal<Intellifolders>();
            dp.BeginFetch(new SingleCriteria<Intellifolders, Guid>(_userGuid),
                (o, e) =>
                {
                   
                });

Is the syntax in the book obsolete now? I admit to being very inexperienced with lambdas.

Thanks,

 

rsbaker0 replied on Wednesday, March 24, 2010

I usually solve these problems with an appropriate cast. For instance, a lambda like () => {...} can be cast to Action where a parameterless delegate is required. So, perhaps you can cast your delegate to Action<o_type, e_type> for whatever types you are expecting for o and e.

KevinKlasman replied on Thursday, March 25, 2010

I tried a somewhat different approach, based on code I found in this forum, as shown below. I get into GetSubfolders and dp.BeginFetch is executed, but I never get into the matching DataPortal_Fetch method.Ultimately the VS JIT debugger window pops up with "Unhandled error in SL app, Code 404, Category: ManagedRuntimeError, System.Reflection.TargetInvocationException: Message has been thrown by the target of an invocation. ---> " but I can't seem to get the debugger to connect.

I'm trying to lazyload SubFolders of a currently selected Folder object in a treeview control.

        internal static  void GetSubfolders(Intellifolder parent)
        {
            var dp = new DataPortal<Intellifolders>();
            dp.FetchCompleted += (o, e) =>
                {
                    if (o != null)
                    {
                    }
                };
            dp.BeginFetch(new SubFolderCriteria(_userGuid, parent.NodeID));
        }

#if !SILVERLIGHT
        private void DataPortal_Fetch(SubFolderCriteria criteria)
        {
            using (CLXIntelliFolders folderData = new CLXIntelliFolders())
            {
                using (DataSet ds = folderData.GetIntelliFoldersListADO(criteria.UserGuid))
                {
                    foreach (DataRow r in ds.Tables[0].Rows)
                    {
                        this.Add(Intellifolder.GetIntellifolder(r));
                    }
                }
            }
        }

rsbaker0 replied on Thursday, March 25, 2010

I haven't done much with Silverlight, but usually 404 when dealing with an http type request means "page cannot be found" or something similar.  You get this error if you specify a valid host name in an URL but the actual target isn't there, say http://www.lhotka.net/junk

KevinKlasman replied on Thursday, March 25, 2010

I suspect the 404 is collateral damage from the incorrect use of the lambda...if I comment out the call that causes the lambda to execute, the error goes away.

The fact that I don't get into the matching DataPortal_Fetch method is my biggest concern at this point.

KevinKlasman replied on Thursday, March 25, 2010

I'm trying the traditional event handler approach now.

And I may have misreported the error that is occurs, even with this new approach...its a 4004, not 404. And I still think this is a red-herring, in that I haven't really finished (cause I don't yet know how) the code that causes this dp.Fetch to execute.

Wait, maybe its my strategy that is incorrect. The crash occurs when my treeview SelectedItemChange method ends. Maybe the crash is occuring before the DataPortal_Fetch method returns, causing that response to be lost or otherwise screwing things up.

So, here's my current strategy. Note that I'm only worried about retrieving existing data at this point.

1. Load top level folders via a bound list object (Intellifolders) derived from EditableRootListBase. This works fine.

2. When the user clicks one of these folders, I want to get the subfolders if they haven't already been gotten. Here's the treeview event. My crash occurs when this event completes it seems (at  least it happens when I step off of the final brace). I am uncertain how to code this event, but it seems like what I have should work, despite being hacky. I'm sure I'll need to expand or collapse the treeview node here, but first things first...I just want to get the data right now.

        void MainTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            Intellifolder folder = MainTreeView.SelectedItem as Intellifolder;
            if (folder != null)
            {
                if (folder.SubFolders != null)
                {

                    // ???
                }
            }
        }

3. The " if (folder.SubFolders != null)" statement in the event handler above causes the Intellifolder.Subfolder get property to fire, which in turn causes the LoadProperty to fire. Once this works, it should fill the current Intellifolders object, which is then linked to the parent Intellifolder object and life will be good. I get into the GetSubfolders factory method fine, but as I said before, the SubFolders get property and treeview event finish next and the crash happens.

What seem wonky to me is that to get the LoadProperty method in parent class to compile, the factory method GetSubFolders cannot be void, which isn't how the other factory methods are declared. (I'm sure I really don't understand how the factory methods work.) This is probably where this strategy is breaking down. My incomplete understanding of the async behavior is likely the cause.

       private static PropertyInfo<Intellifolders> SubFoldersProperty = RegisterProperty<Intellifolders>(c => c.SubFolders);
       public Intellifolders SubFolders
       {
           get
           {
               if (!FieldManager.FieldExists(SubFoldersProperty))
               {
                   if (this.IsNew)
                   {
                       //LoadProperty(SubFoldersProperty, Intellifolders.NewIntellifolder());
                   }
                   else
                       LoadProperty<Intellifolders>(SubFoldersProperty, Intellifolders.GetSubfolders(this));

               }
               return GetProperty(SubFoldersProperty);
           }
           set { SetProperty(SubFoldersProperty, value); }
       }

4. The factory and dataportal_fetch method look like this:

        public static Intellifolders GetSubfolders(Intellifolder parent)
        {
            var dp = new DataPortal<Intellifolders>();
            dp.FetchCompleted += new EventHandler<DataPortalResult<Intellifolders>>(dp_FetchCompleted);
            dp.BeginFetch(new SubFolderCriteria(_userGuid, parent.NodeID));
            return null; // ???
        }
       
        internal static void dp_FetchCompleted(object sender, DataPortalResult<Intellifolders> e)
        {
            if (e != null)
            {
                  // load each returned Intellifolder
            }
        }

#if !SILVERLIGHT
        private void DataPortal_Fetch(SubFolderCriteria criteria)
        {
            using (CLXIntelliFolders folderData = new CLXIntelliFolders())
            {
                using (DataSet ds = folderData.GetIntelliFoldersListADO(criteria.UserGuid))
                {
                    foreach (DataRow r in ds.Tables[0].Rows)
                    {
                        this.Add(Intellifolder.GetIntellifolder(r));
                    }
                }
            }
        }
#endif

RockfordLhotka replied on Thursday, March 25, 2010

Async factory methods must be void. They can not return a value, because the method returns (completes) before the async result comes back.

This is why async factory methods accept a callback parameter, so the caller of the async factory can provide a callback method that is invoked when the async operation does complete.

This is also why using lambda expressions is so much simpler than using a separate event handler - because you need some way to provide the callback method delegate to the async handler, which is automatic with lambdas, but requires a bunch of other manual code without lambdas.

For example, here's a factory method that does nothing except invoke the callback (which is pretty typical):

    public static void GetSearchForPosts(string searchText, EventHandler<DataPortalResult<SearchForPosts>> callback)
    {
      var dp = new DataPortal<SearchForPosts>();
      dp.FetchCompleted += callback;
      dp.BeginFetch(new SingleCriteria<string>(searchText));
    }

The calling code looks like this:

SearchForPosts.GetSearchForPosts("search term", (o, e) =>
  {
    if (e.Error == null && e.Object != null)
    {
      // process result
    }
  });

 

RockfordLhotka replied on Thursday, March 25, 2010

Dan Billingsley

Hey Rocky, since Linq and lambdas are bringing the whole concept and understanding of delegates to the mainstream, have you given any thought of applying it to the age-old issue of the Dataportal_XYZ method "magic names"? 

Yes, but in an n-tier deployment this won't work for a couple reasons. First, it is quite likely that your data access methods don't even exist on the client, so the lambda wouldn't compile. Second, lambda expressions aren't serializable, so I'd have to invent a serialization model for them - which is possible, but highly problematic due to the first issue (that the methods probably aren't there to start with).

RockfordLhotka replied on Thursday, March 25, 2010

I think what you are suggesting is something like this:

return DataPortal.Fetch<Customer>(c => c.GetTheCustomer());

Basically have the Fetch() and other methods accept a Func<T> - which could be a delegate, lambda, etc.

That could work, except that the GetTheCustomer() method would need to exist in the context where this code was compiled - which would normally be on the client. Silverlight or not, a good many people do things to prevent their data access code from existing on the client, so this code wouldn't compile.

But even if you require that the GetTheCustomer() method be implemented on the client, the problem is still not solved, because the delegate/lambda/etc is declared and instantiated on the client when this code executes. So there's still the problem of picking up the call context and transferring it to the server so the GetTheCustomer() method actually runs on the server.

There are various ways to do this now, including some clever tricks with the new Dynamic keyword in C#. That'd actually require some changes in how you interact with the data portal on the client though, because you'd need to get the object to be of type Dynamic before the magic dynamic interceptor I'd build would be able to intercept the method call before it occurred and transfer it to the server.

You are right, code isn't serialized today. And code itself wouldn't be serialized in this case - but the call context would be.

The more probable solution though, would be to use the same technique we use in RegisterProperty(), which actually reflects against the type based on the lambda expression to get the property name. The same technique could be used to reflect against the type based on a lambda expression to get the target method name, and that method name could be passed to the server as a string. The result would be pretty much the same as today, with just a few extra bytes of string data passed on every data portal call.

But again we come back to the reality that this only works in the DataPortal_XYZ model, not the object factory model. And it only works if the target method exists on the client, which it never would in Silverlight and often doesn't even in .NET.

And we can't dismiss the DP_XYZ vs object factory difference quickly either. By changing the signature of DataPortal.Fetch() to accept a lambda that would only work with DP_XYZ, we'd double the number of Fetch() overloads - only half of which would work with either data portal model. I shudder to think of the confusion that would result as people tried to use the wrong set of overloads with the wrong data portal model.

To make things worse, one of the things I ultimately want to do with the data portal is open up the set of models so people can add their own. At which time who knows what this would lead to...

All in all, it just isn't going to happen.

tmg4340 replied on Friday, March 26, 2010

Maybe not.  One of the things about partial methods is that if there's no implementation, the method is removed from the class entirely.  So, from your client-side compilation, the server-side implementation wouldn't exist, and the partial method could be removed - even though it's being referenced from your lambda expression.  Then you're back to the lambda expression not compiling.

- Scott

RockfordLhotka replied on Friday, March 26, 2010

I think it would also be possible to do this:

public void GetTheCustomer(CriteriaType criteria)
{
#if SILVERLIGHT
  throw new InvalidOperationException("GetTheCustomer");
#else
  // write implementation here
#endif
}

 

tmg4340 replied on Friday, March 26, 2010

I believe partial methods exist largely to manage the new WPF control/eventing model.  Without partial methods, the WPF way of control composition would lead to ridiculous overhead in event management.  They certainly have uses in a partial-class construct, but I think their benefit there is much less limited unless you have a situation similar to what WPF presents.

- Scott

Copyright (c) Marimer LLC