locked
RIA Service: Unable to cast object of type 'System.Data.Services.Internal.ExpandedWrapper' RRS feed

  • Question

  • Created a new RIA domain service in an LS HTML application and when using .expand(...) on an entity _SingleOrDefault() call I get the error below. If I exclude the .expand() part then it works, but I get the same error when calling screen.MyEntity.details.refresh().

    Any ideas?

    <Message>Unable to cast object of type 'System.Data.Services.Internal.ExpandedWrapper`2[Minerva.BusinessService.Main.Models.SessionModel,Minerva.BusinessService.Main.Models.SessionStatusModel]' to type 'System.Data.Services.Internal.ExpandedWrapper`2[LightSwitchApplication.Implementation.SessionModel,LightSwitchApplication.Implementation.SessionStatusModel]'.</Message>

    Here is the full error message and stack trace:

    <?xml version="1.0" encoding="utf-16"?><ExceptionInfo><Message>Unable to cast object of type 'System.Data.Services.Internal.ExpandedWrapper`2[Minerva.BusinessService.Main.Models.SessionModel,Minerva.BusinessService.Main.Models.SessionStatusModel]' to type 'System.Data.Services.Internal.ExpandedWrapper`2[LightSwitchApplication.Implementation.SessionModel,LightSwitchApplication.Implementation.SessionStatusModel]'.</Message><StackTrace> at System.Linq.Enumerable.&lt;CastIterator&gt;d__b1`1.MoveNext() at System.Linq.Enumerable.&lt;OfTypeIterator&gt;d__aa`1.MoveNext() at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataServiceImplementation`1.MergeQueryResults(IEnumerable results) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataServiceImplementation`1.&lt;&gt;c__DisplayClass2a.&lt;Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.IDataProviderHost.MergeQueryResults&gt;b__29() at Microsoft.LightSwitch.Threading.DualDispatcherObject.Mutate(IDispatcher logicDispatcher, MutatorHost host, Action mutator) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataServiceImplementation`1.Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.IDataProviderHost.MergeQueryResults(IEnumerable results) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.RiaDataProvider.DomainServiceQuery`1.Execute[TElement](IQueryable innerQuery, Expression outerExpression) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.OuterQuery`1.ExecuteInternal[TElement](IQueryable innerQuery, Expression outerExpression) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.OuterQueryExecutor`1.Execute[TElement](Expression expression) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataProvider.OuterQuery`1.GetEnumerator() at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection) at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataServiceQueryProvider.QueryableWrapper`1.&lt;&gt;c__DisplayClass10.&lt;GetEnumerator&gt;b__f() at Microsoft.LightSwitch.Threading.DispatcherExtensions.Invoke(IDispatcher dispatcher, Action action) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataService`1.LogicInvoke(Action a) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataService`1.Microsoft.LightSwitch.ServerGenerated.Implementation.IODataService.LogicInvoke(Action a) at Microsoft.LightSwitch.ServerGenerated.Implementation.DataServiceQueryProvider.QueryableWrapper`1.GetEnumerator() at System.Data.Services.Providers.BasicExpandProvider.ExpandedQueryable`1.GetEnumerator() at System.Data.Services.Providers.BasicExpandProvider.ExpandedQueryable`1.System.Collections.IEnumerable.GetEnumerator() at System.Data.Services.WebUtil.GetRequestEnumerator(IEnumerable enumerable) at System.Data.Services.QueryResultInfo.MoveNext() at System.Data.Services.Providers.DataServiceExecutionProviderWrapper.GetSingleResultFromRequest(SegmentInfo segmentInfo) at System.Data.Services.DataService`1.CompareETagAndWriteResponse(RequestDescription description, IDataService dataService, IODataResponseMessage responseMessage) at System.Data.Services.DataService`1.SerializeResponseBody(RequestDescription description, IDataService dataService, IODataResponseMessage responseMessage) at System.Data.Services.DataService`1.HandleNonBatchRequest(RequestDescription description) at System.Data.Services.DataService`1.HandleRequest()</StackTrace><ErrorInfo /></ExceptionInfo>


    Regards, Xander. My Blog



    • Edited by novascape Wednesday, January 21, 2015 2:58 AM update caption
    Sunday, October 12, 2014 7:27 AM

All replies

  • <Message>Unable to cast object of type 'System.Data.Services.Internal.ExpandedWrapper`2[Minerva.BusinessService.Main.Models.SessionModel,Minerva.BusinessService.Main.Models.SessionStatusModel]' to type 'System.Data.Services.Internal.ExpandedWrapper`2[LightSwitchApplication.Implementation.SessionModel,LightSwitchApplication.Implementation.SessionStatusModel]'.</Message>


    Hi novascape,

    According to the error message above, I can hardly give precise troubleshooting. I read Michael's blog about creating a WCF RIA Service for Visual Studio LightSwitch 2013 before, I think it will be good reference to create RIA service in Lightswitch. See: Creating a WCF RIA Service for Visual Studio LightSwitch 2013 

    If you want to refresh the detail entity in Lightswitch, here is the reference: Refreshing Data in LightSwitch in Visual Studio 2013

    Please let me know if there is anything that I can do to help.

    Best regards,

    Angie


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Edited by Angie Xu Tuesday, October 21, 2014 9:22 AM
    Wednesday, October 15, 2014 9:48 AM
  • Revisiting this issue and have created a small sample project that demonstrates this issue that can be made available.

    The small RIA domain service uses an EF 5 context, although I do not think that is the issue.

    It only happens when trying to refresh an entity that has an association and I believe this worked in earlier versions of LS. Currently using VS2013 with latest update.

    Anyone keen to have a look? Dave from the LS team?

    Thanks


    Regards, Xander. My Blog


    • Edited by novascape Tuesday, January 20, 2015 11:10 PM
    Tuesday, January 20, 2015 10:54 PM
  • In my simple example/repro it tries to cast the domain service entity to the LS implementation version and it is unable to do this cast:

    Unable to cast object of type 

    'System.Data.Services.Internal.ExpandedWrapper`2[RIADomainService.RiaPet,RIADomainService.RiaPerson]' to
    'System.Data.Services.Internal.ExpandedWrapper`2[LightSwitchApplication.Implementation.RiaPet,LightSwitchApplication.Implementation.RiaPerson]'


    Regards, Xander. My Blog

    Tuesday, January 20, 2015 11:37 PM
  • Excluding the HTML client the error can be reproduced by calling the service directly from the browser (in this example "Owner" is the property name of the Person entity association that owns the Pet):

    http://localhost:3701/RiaData.svc/RiaPets(1)?$expand=Owner

    The following (without the expand) works of course:

    http://localhost:3701/RiaData.svc/RiaPets(1)

    This must be a LightSwitch bug surely? If so, it makes RIA services totally unusable in LS in VS2013.


    Regards, Xander. My Blog


    • Edited by novascape Wednesday, January 21, 2015 2:57 AM
    Wednesday, January 21, 2015 2:54 AM
  • I have just tested this in the latest VS2015 CTP on an Azure VM (Version 14.0.22512.0 DP) and the problem is still there.

    This makes RIA services totally unusable in LS now unfortunately.

    Here is the IE screenshot from VS2015:


    Regards, Xander. My Blog

    Wednesday, January 21, 2015 7:55 PM
  • it makes RIA services totally unusable in LS in VS2013.


    Regards, Xander. My Blog



    I haven't had any problems. 

    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Wednesday, January 21, 2015 8:19 PM
  • Michael, I can send you a small repro if you wish to have a look - let me know?

    Note that it manifests if you use associations and the $expand command (the latter is automatically used when calling screen.MyEntity.details.refresh() when MyEntity has an association).

    Thanks


    Regards, Xander. My Blog

    Wednesday, January 21, 2015 8:22 PM
  • I don't doubt you are getting an error, but to say "it makes RIA services totally unusable in LS in VS2013" feels odd to me when I use it everyday :)

    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Wednesday, January 21, 2015 8:27 PM
  • I don't mean to over emphasize this, but it is a very important aspect of using RIA services. No point in using a RIA service for simple operations at first and then finding this out some way down the track (as I have) and then being in a corner and having to back out all the way again.

    I'm not sure when exactly this issue crept into LS - and I assume this is a LS issue until told otherwise - but it does really mean that you should think very carefully about using RIA services if you are planning to use associations at all.

    For me this is a show stopper.



    Regards, Xander. My Blog

    Wednesday, January 21, 2015 8:38 PM
  •  it does really mean that you should think very carefully about using RIA services if you are planning to use associations at all.

    For me this is a show stopper.



    Regards, Xander. My Blog

    I perform my associations "inside" my WCF RIA Services methods. That is what I am doing here: LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services.

    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Wednesday, January 21, 2015 8:47 PM
  • That is what I do to as you cannot declare associations outside on the LS side like you can do with a database data source.

    Here is my sample repro entities with the associations:

    public class RiaPerson
    {
    	[Key]
    	public int Id { get; set; }
    	public string Name { get; set; }
    	public string Description { get; set; }
    	[Editable(false)]
    	[Timestamp]
    	public byte[] RowVersion { get; set; }
    
    	[Association("Pet_Person", "Id", "OwnerId")]
    	public IQueryable<RiaPet> Pets { get; set; }
    }
    
    public class RiaPet
    {
    	[Key]
    	public int Id { get; set; }
    	public string Name { get; set; }
    	public string Description { get; set; }
    	[Editable(false)]
    	[Timestamp]
    	public byte[] RowVersion { get; set; }
    
    	public int OwnerId { get; set; }
    
    	[Association("Pet_Person", "OwnerId", "Id", IsForeignKey = true)]
    	public RiaPerson Owner { get; set; }
    }

    Michael, can you please try in one of your own RIA projects to refresh one of your entities (involved in an association - e.g. Order that is linked to OrderLines) on a screen by calling screen.Order.details.refresh() and watch the console and/or network traffic to see whether you get the above error?

    Thanks


    Regards, Xander. My Blog

    Wednesday, January 21, 2015 8:53 PM
  • Xander, I don't disagree that your scenario may not work. However, because the LightSwitch controls work on one entity at a time, I would combine the information I need from the entities into a single entity.

    Any business functionality that I need to implement I can do no matter how many entities are involved on the back-end. No matter how complex their associations.

    When you use the terms "unusable" and "showstopper" I just felt I needed to provide a link to an example where WCF RIA Services do work to anyone who runs across this thread :)


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Wednesday, January 21, 2015 9:00 PM
  • When RIA services are advertised to work in LS they need to work correctly. Based on my findings above they do not work correctly. Yes, you can shape the data in many ways to potentially overcome some of this, but ultimately the fundamentals either work or they don't work. 

    Associations are fundamental in RIA to me and unless someone can point out what I'm doing wrong, I will stick by the fact they they do not work correctly and anyone coming across this thread should be very much aware of that.

    Anyway Michael, we are on the same side, advocating for a better LS and this is just one of those issues that will make LS work better if resolved.

    My offer still stands that I have a small repro of this for anyone interested - specifically the LS team.


    Regards, Xander. My Blog

    Wednesday, January 21, 2015 9:09 PM
  • Xander,

    Does it help if you put [include] before the associations and specify isForeignKey=False on the first one?

    Compare to this :

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/3e2315aa-1463-4f02-838b-ba10ed77abb2/wcf-ria-in-lightswitch-problem-with-cardinality-of-association?forum=lightswitch

    HTH,

    Josh

    • Edited by joshbooker Thursday, January 22, 2015 1:12 AM iphone cant post
    Wednesday, January 21, 2015 11:13 PM
  • Josh, no unfortunately not, still the same issue. I've tried adding [Include] again just now to be sure and no luck.

    As far as I know LS does not actually use the [Include] attribute, although I could be wrong.

    Thanks anyway


    Regards, Xander. My Blog

    Wednesday, January 21, 2015 11:33 PM
  • Please let me know if there is anything that I can do to help.

    Best regards,

    Angie


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    @Angie - if you can elevate this to the LS team as a potential bug and get a report back on whether there is a work around or alternatively when we can expect a fix it will be greatly appreciated.

    Many thanks


    Regards, Xander. My Blog

    • Edited by novascape Wednesday, January 21, 2015 11:52 PM
    Wednesday, January 21, 2015 11:51 PM
  • I don't use RIA, so just trying to help by comparing to other samples.

    Does it help to put IsForeignKey = false on Person side of association? 

    Thursday, January 22, 2015 1:11 AM
  • I don't use RIA, so just trying to help by comparing to other samples.

    Does it help to put IsForeignKey = false on Person side of association? 

    No it does not, false is actually the default and not required, but I had it on there as well. Thanks for your interest in this anyway...

    Thanks


    Regards, Xander. My Blog

    Thursday, January 22, 2015 2:32 AM
  • Xander,

    What does your RIA Service query look like?  Did you try .include (in the RIA Service query) to fetch the related entity data or are you lazy loading?

    Did you try different collection types for the related RIAPets, e.g. change IQueryable<RIAPet> to IEnumerable<RIAPet>?

    I used RIA with associations in the Silverlight client with no issues (once I got it working properly), but haven't made it there yet in the html client.  Not sure if any of these suggestions will help, but maybe some things to try.  Most of my hard to solve RIA problems were related to the query not the structure of the classes involved.  I'm very interested in this because I will be doing the same thing in the very near future.



    • Edited by Hessc Thursday, January 22, 2015 3:01 PM
    Thursday, January 22, 2015 2:58 PM
  • Thanks Hessc for your contribution.

    I have tried both IEnumerable and IQueryable but it makes no difference. This is not actually a Silverlight vs HTML Client issue as you can replicate the issue by calling the service directly in the browser address bar as mentioned above. So it is a LS OData with RIA domain service issue.

    For the benefit of those interested, here is a very simple domain service class that illustrates the problem - no database is required. Do the following:

    1. Create a new HTML Client LS project
    2. Add a new class library project to contain the RIA domain service and add the class below into that project.
    3. Add a reference to the following file to the class library project: C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server\System.ServiceModel.DomainServices.Server.dll
    4. Add a new WCF RIA Service data source to the LS server project by referencing the above class library project and selecting the above domain service
    5. Add a new "common screen set" browse and view set of screens to the HTML client using the RiaPet as the entity (this will provide a browse screen and a view screen)
    6. On the view screen add a new "Refresh"command button with the following code: screen.RiaPet.details.refresh();
    7. Run the app and view one of the Pets on the view screen
    8. Open the browser developer tools window (F12 in Chrome) and view the Network or Console tab to see any errors being logged
    9. Press the above Refresh command button to refresh the RiaPet entity on the view screen and note the error in the developer tools window
    10. Repeat the test by calling the service directly in the browser address bar by entering "http://localhost:3701/RiaData.svc/RiaPets(1)?$expand=Owner" (adjust for your own scenario). Note that removing the "$expand=Owner" part works fine

    Note that this sample domain service only allows viewing and no updating or inserting.

    public class TestDomainService : DomainService
    {
    	private static List<RiaPet> _pets =
    		new List<RiaPet>
    		{
    			new RiaPet() {Id = 1, Name = "Dog", Description = "Small Dog", OwnerId = 1 },
    			new RiaPet() {Id = 2, Name = "Cat", Description = "Small Cat", OwnerId = 2 },
    			new RiaPet() {Id = 3, Name = "Horse", Description = "Small Horse", OwnerId = 3 }
    		};
    
    	private static List<RiaPerson> _persons =
    		new List<RiaPerson>
    		{
    			new RiaPerson() {Id = 1, Name = "Joe", Description = "From the South" },
    			new RiaPerson() {Id = 2, Name = "John", Description = "From the North" },
    			new RiaPerson() {Id = 3, Name = "Mary", Description = "From the East" }
    		};
    
    	[Query(IsDefault = true)]
    	public IQueryable<RiaPet> GetAllPets()
    	{
    		return from p in _pets.AsQueryable()
    			   select new RiaPet
    			   {
    				   Id = p.Id,
    				   OwnerId = p.OwnerId,
    				   Name = p.Name,
    				   Description = p.Description
    			   };
    	}
    
    	[Query(IsDefault = true)]
    	public IQueryable<RiaPerson> GetAllPeople()
    	{
    		return from p in _persons.AsQueryable()
    			   select new RiaPerson
    			   {
    				   Id = p.Id,
    				   Name = p.Name,
    				   Description = p.Description
    			   };
    	}
    
    	protected override int Count<T>(IQueryable<T> query)
    	{
    		return query.Count();
    	}
    }
    
    public class RiaPerson
    {
    	[Key]
    	public int Id { get; set; }
    	public string Name { get; set; }
    	public string Description { get; set; }
    
    	[Association("Pet_Person", "Id", "OwnerId", IsForeignKey = false)]
    	public IQueryable<RiaPet> Pets { get; set; }
    }
    
    public class RiaPet
    {
    	[Key]
    	public int Id { get; set; }
    	public string Name { get; set; }
    	public string Description { get; set; }
    	public int OwnerId { get; set; }
    
    	[Association("Pet_Person", "OwnerId", "Id", IsForeignKey = true)]
    	public RiaPerson Owner { get; set; }
    }

    Note that when you create a database with two tables as above and attach that database as a database data source to LS then it works perfectly. So this issue is specific to using a RIA domain service.

    I would love for someone to point at the above and tell me that the problem is in my code rather than LS :)


    Regards, Xander. My Blog


    • Edited by novascape Thursday, January 22, 2015 8:05 PM
    Thursday, January 22, 2015 8:01 PM
  • Here is a simple repro application to illustrate the issue (no database required):

    http://www.mhinteractive.net/downloads/RiaExpandedWrapperIssue.zip

    Steps to reproduce:

    1.        Build and run
    2.        Click on any animal tile in browse screen
    3.        On view screen click the [Refresh] button to reproduce server error OR
    4.        Enter http://localhost:43194/DomainServiceData.svc/RiaPets(1)?$expand=Owner in browser address bar to reproduce error


    Regards, Xander. My Blog

    • Edited by novascape Saturday, January 24, 2015 4:21 AM
    Saturday, January 24, 2015 4:20 AM
  • One possible work around for the .refresh() scenario seems to be to explicitly specify that you only want the main entity refreshed and not the navigation property (if that works for your scenario).

    So instead of calling: screen.RiaPet.details.refresh()

    Instead call: screen.RiaPet.details.refresh([]);

    I got this tip from the following article by Michael:

    New API For Refreshing Data in LightSwitch in Visual Studio 2013

    This does not provide a way to refresh the related child entities or use $expand but at least it allows the main entity to be refreshed which is one less hurdle.


    Regards, Xander. My Blog


    • Edited by novascape Monday, January 26, 2015 1:50 AM
    Monday, January 26, 2015 1:40 AM
  • I have confirmation that .expand() is currently not supported on custom RIA data sources and hence the above will therefore not work and the error should be expected.

    The following seems like a reasonable work-around that will allow one to use a RIA domain service with associations with the following restrictions:

    - You cannot use .expand() – resulting in a slight performance hit due to multiple server trips
    - Use screen.MyEntity.details.refresh([]) to ensure only specified entity is refreshed and not associated entities (note empty square brackets)
    - If associated entity collections need to be refreshed then you need to write code to refresh those individually by calling the visual collection .refresh()

    I will mark this as the answer.


    Regards, Xander. My Blog


    • Edited by novascape Tuesday, January 27, 2015 4:27 AM
    • Marked as answer by novascape Tuesday, January 27, 2015 4:27 AM
    • Unmarked as answer by novascape Thursday, February 5, 2015 10:55 PM
    Tuesday, January 27, 2015 4:26 AM
  • That's not good.  I wonder if it breaks if you specify unchangedOnly as the default merge option (msls.application.options.defaultMergeOption = "unchangedOnly";)? 
    Tuesday, January 27, 2015 5:18 AM
  • That's not good.  I wonder if it breaks if you specify unchangedOnly as the default merge option (msls.application.options.defaultMergeOption = "unchangedOnly";)? 
    Why is that not good?

    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Tuesday, January 27, 2015 6:01 AM
  • That's not good.  I wonder if it breaks if you specify unchangedOnly as the default merge option (msls.application.options.defaultMergeOption = "unchangedOnly";)? 

    That does not help unfortunately.

    Although not ideal, I think the workaround in the accepted answer is acceptable and will now allow me to use a RIA domain service with associations.

    It is one of those undocumented things that we just need to be aware of.


    Regards, Xander. My Blog

    • Edited by novascape Tuesday, January 27, 2015 6:10 AM
    Tuesday, January 27, 2015 6:10 AM
  • Why is that not good?


    Umm, maybe because it doesn't work and the error is not handled.  ;-)

    I don't think there's any problems with expanding associations in odata external data sources.  Since, like LightSwitch, WCF DS is dead too, prolly a recommendation to go with WebAPI Odata would be another thing for folks to be aware of.

    Tuesday, January 27, 2015 12:49 PM
  • Yes, not good because .expand() doesn't work for RIA Services.  The workaround probably solves many cases, but I am particularly concerned about when you include msls.application.options.defaultMergeOption = "unchangedOnly"; as the defaultMergeOption for the entire application.  My understanding is that alleviates the need to screen.MyEntity.details.refresh() related entities manually so that the view model is updated automatically (if necessary and only for entities where the entity state is unchanged).  This feature is a good timesaver in large applications where that is the behavior you desire.  I am wondering if that defaultMergeOption will also call .expand() on the RIA Service entities and throw the error (the way screen.MyEntity.details.refresh() does).  If so, that is not good for apps that use a combination of RIA Services and SQL Server datasources.  Perhaps my concern is off base, I don't know, that's why I stated it as a question.  Setting that aside, it is generally not good that the .expand() feature doesn't work the same for all datasources for the reasons Xander specified above.  I don't necessarily agree that it is a "showstopper" for RIA in lightswitch, but it could cause problems.
    Tuesday, January 27, 2015 4:26 PM
  • Back to this issue as it seems to be a show stopper after all, despite the potential work around above. I'm also unmarking the answer for now.

    Scenario:

    • RIA Domain Service
    • Order entity and OrderStatus entity
    • Order have an association with OrderStatus
    • You display the Order and associated OrderStatus on an HTML screen
    • You implement a refresh button to refresh the Order view should the status have changed at the backend (perhaps by another user)
    • You can only call screen.Order.details.refresh([]) as leaving out the [] to refresh everything will cause a server 500 error (already described above)
    • You load the Order in the screen to view it
    • You then update the Order Status directly in the database (simulating another user changing it)
    • You click the refresh button to reflect the updated Order
    • The old Order Status is still displayed and you have no way of refreshing that Order status -- this is a huge problem!

    You cannot call screen.Order.details.refresh(["OrderStatus"]) as that uses $expand() underneath and you get the server 500 error.

    Any ideas how to get those associations refreshed without support for $expand?  This is really a major problem in that you cannot use RIA services with associations in LS!


    Regards, Xander. My Blog

    Thursday, February 5, 2015 10:55 PM
  • Sorry about your trouble, Xander.  So much for RIA being the 'you can do anything' solution.

    Do you have a screen.Order.getOrderStatus() method?  These 'getter' methods are typically added by LS to for defered loading of navigation properties and return a promise that when completed you can .then refresh your OrderStatus on the screen.

    I know it sounds like a lot of code for something that should work, but might be a workaround.

    HTH,

    Josh

    Thursday, February 5, 2015 11:05 PM
  • Thanks Josh. All the time that I save with LS is being eroded with issues like this where it should just work, but doesn't... Grrrr.

    I've tried the following that unfortunately does not work (changed my real code to use Order example here):

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function (order) {
            screen.Order.getOrderStatus().then(function(status) {
            });
        });
    };
    I mean really... :(



    Regards, Xander. My Blog

    Thursday, February 5, 2015 11:10 PM
  • Does this work?

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function (order) {
            screen.Order.getOrderStatus().then(function(status) {
                screen.Order.OrderStatus = status;
            });
        });
    };
    

    If not, then maybe dataBind or addChangeListener is needed(?)

    HTH,

    Josh

    Thursday, February 5, 2015 11:21 PM
  • Unfortunately not.... very weird.

    The Status that is passed in from the getOrderStatus() promise is indeed the new Status but after setting it to the Order the Order still  has the old Status.

    I'm going to create a new screen altogether to test this just to make sure I've not got something else somewhere that is affecting this.


    Regards, Xander. My Blog

    Thursday, February 5, 2015 11:40 PM
  • Correct to last post by me above: the Status that is passed in from the getorderStatus() promise is indeed the OLD Status.

    I find the same behavior on another simple newly created edit screen.


    Regards, Xander. My Blog

    Thursday, February 5, 2015 11:56 PM
  • That doesn't make sense, unless getOrderStatus in not sending another request to the server.  Can you confirm whether this is ideed sending two requests?

    I wonder if this makes any diff:

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function (order) {
            order.getOrderStatus().then(function(status) {
                order.OrderStatus = status;
            });
        });
    };

    Friday, February 6, 2015 12:05 AM
  • The refresh() promise actually returns an object that contains an array, so to get the order one needs to do this:

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function (orderData) {
            var order = orderData.results[0]; // correctly updated order retrieved with call to server
            order.getOrderStatus().then(function(statusData) {
                var status = statusData.results[0]; // incorrectly retrieved old status with no call to server
                order.OrderStatus = status;
            });
        });
    };

    But despite both promise functions being called, this does not work either and importantly, only a single call is made to the server to fetch the updated Order.


    Regards, Xander. My Blog

    • Edited by novascape Friday, February 6, 2015 12:40 AM
    Friday, February 6, 2015 12:38 AM
  • I'm guessing this is by design.  Since you are specifically requesting not to update nav props with .refresh([]), the old value remains.  I believe getOrderStaus only issues another request if the deferred prop is not yet loaded.

    You could resort to manual refresh using a separate dataWorkspace like Huy does here:

    https://social.msdn.microsoft.com/Forums/en-US/786d9d6f-ba7c-476c-b9ff-efb4f4b653c7/value-on-screen-doesnt-match-value-in-the-entity?forum=lightswitchhtml

    myapp.AddEditOrder.Refresh_execute = function (screen) {
        var order = screen.Order;
    
        // Can only safely do this if the order is not modified.
        if (order.details.entityState !== msls.EntityState.unchanged) {
            return msls.showMessageBox(
                "Cannot refresh the order because it was changed.",
                {
                    title: "Cannot refresh"
                });
        }
    
        // Get the current Order ID
        var orderID = screen.Order.OrderID;
    
        // Construct a new data workspace that is isolated from the
        // application's data workspace and load a separate instance
        // of this Order from server.
        var dataWorkspace = new myapp.DataWorkspace();
        return dataWorkspace.NorthwindData
            .Orders_SingleOrDefault(orderID)
            .execute().then(function (result) {
    
                // Query succeeded. If the Order still exists on
                //  server, merge it.
                var serverOrder = result.results[0];
                if (serverOrder) {
                    // Replace the raw JSON object representing the
                    // Order.
                    order.details._ = serverOrder.details._;
    
                    // Raise changes notification for all storage
                    // properties, so the UI can update.
                    order.details.properties.all().forEach(
                    function (prop) {
                        if (prop instanceof 
                            msls.Entity.Details.StorageProperty) {
                            // The property's value.
                            prop.dispatchChange("value");
                            // The entity's property value.
                            order.dispatchChange(prop.name);
                        }
                    });
                }
    
            }, function (error) {
                msls.showMessageBox(error, {
                    title: "Refresh failed"
                });
            });
    };
    

    This is the solution to merge changes from before the refresh() method was introduced.  It uses a lower level by replacing the raw JSON Object representing the entity then raises dispatchChange on all the properties.

    Grrrr, indeed.

    HTH,

    Josh


    • Edited by joshbooker Friday, February 6, 2015 1:15 AM
    Friday, February 6, 2015 1:13 AM
  • Thank you Josh for that escape hatch - I might just have to use it.

    I can understand that $epand() might not be supported on RIA services, but the consequence is that you cannot refresh a RIA entity with a visible associated entity... which to smells more like a bug than a not-supported / missing feature...

    You have to wonder at what point it might be better to move away from LS with all these work-arounds and potentially architectural limitations. Perhaps therein lies the reason that the current version of LS will no longer evolve - to take it further they have to start from scratch... just a thought as we don't know for sure of course.

    Thanks again for you contribution.


    Regards, Xander. My Blog

    • Edited by novascape Friday, February 6, 2015 1:56 AM
    Friday, February 6, 2015 1:55 AM
  • Hi Xander,

    I would say that the .refresh() issuing an $expand call is the bug.

    LS seems to handle the inability to 'include' related entities from RIA source by issuing a separate $filter request when a screen with such a navprop loads.  For example, in your Person-Pet scenario, on ViewPet screen display Owner as a summary control and you'll notice a separate call to:

    /RiaPersons?$filter=Id%20eq%201

    This is an alternative to $expand resulting in an extra request, but it works.

      The bug is that refresh should do the same thing - issue a separate request, instead of try to use $expand.  Your workaround to prevent the 500 error (.refresh([]) unfortunately specifically excludes refreshing the nav props.

    So you're indeed stuck by this bug.

    PS I noticed some other places LS tries to prevent includes for RIA data like you cannot 'Manage Included Data'.  I guess refresh() this is one place they forgot to handle the deficiency.

    I would suggest logging a bug on connect site.

    Josh

    Friday, February 6, 2015 8:27 PM
  • Hi Josh,

    I tried the suggested code above from Huy yesterday but that did not work either. The navigational properties do not appear to be of the StorageProperty type, so one has to remove that check. That still doesn't work though. At that point I decided it was time to switch off the computer for the day...

    An interesting observation you made with the summary property. I'm not sure that lsml knows that the data source is a RIA data source to be able to make refresh() work differently by not issuing the $expand. They've obviously decided to use $expand with refresh() for performance reasons as it batches the refreshed data in one call with one single result.

    Do you think it would be possible to create a monkey patch of sorts that implements a new refreshWithoutExpand() method that works like the refresh when you're using summary property...? I'm willing to pay for such a method :)

    Thanks again



    Regards, Xander. My Blog

    Friday, February 6, 2015 9:46 PM
  • An interesting observation you made with the summary property. I'm not sure that lsml knows that the data source is a RIA data source to be able to make refresh() work differently by not issuing the $expand. They've obviously decided to use $expand with refresh() for performance reasons as it batches the refreshed data in one call with one single result.

    The javascript runtime doesn't know that's it's a RIA, but the generated viewModel.js and possibly data.js are different, I think.  Those diffs are put there by LS at build time and they create the second query for nav props on a RIA source.  Not 100% sure, but that's my theory anyway.

    Question:  Are you 100% sure that getOrderStatus() returns the old status and doesn't make a request?  Can you confirm there is no '304 Not Modified' response using fiddler? 

    Refresh first sends a request with eTag to see if the entity has been changed on server, if no change it doesn't even send the data back in the response - instead it sends 304 NOT MODIFIED.  In that case it might be a concurrency checking problem.

    I still think it's most likely a bug where they field to handle RIA include deficiency.

    Friday, February 6, 2015 10:33 PM
  • Question:  Are you 100% sure that getOrderStatus() returns the old status and doesn't make a request?  Can you confirm there is no '304 Not Modified' response using fiddler? 

    I'm 100% sure that the getorderStatus() returns the old Status.

    However, screen.Order.details.refresh([]) returns the correctly update Order and at that point the screen.Order also contains the correctly updated Order, both with the updated StatusId in the order.details._.StatusId field.

    So it is only the order.OrderStatus object that is stale. I need a way to refresh that associated object...

     

    Regards, Xander. My Blog

    Saturday, February 7, 2015 4:59 AM
  • The following works, but is ugly...

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function () {
            screen.Order.details.__OrderStatus.query._filter = "Id eq " + screen.Order.details._.OrderStatusId;
            screen.Order.details.__OrderStatus.query.execute();
        });
    };

    This could perhaps be converted into a generic helper method, where you pass a list of association names, PK & FK property names, but it feels more like a kludgy workaround than anything else.

    A better way would be to find a way to invalidate that screen.Order.details.__OrderStatus object so that it rebuilds based off the underlying screen.Order.details._.OrderStatusId value and do so in a generic manner.


    Regards, Xander. My Blog

    Saturday, February 7, 2015 6:02 AM
  • Hi Xander,

    The StatusId could be helpful in making Huys code work like so:

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function (orderData) {
            var order = orderData.results[0]; // correctly updated order retrieved with call to server
            var dataWorkspace = new myapp.DataWorkspace();
        return dataWorkspace.NorthwindData
            .OrderStatus_SingleOrDefault(order.details._.StatusId)
            .execute().then(function (result) {
                var serverStatus = result.results[0];
                if (serverStatus) {
                    // Replace the raw JSON object representing the
                    // OrderStatus.
                    order.OrderStatus.details._ = serverStatus.details._;
                    order.dispatchChange("OrderStatus");
                }
            });
        });
    };

    Change 'NorthwindData' to your DS name and let me know if this gets any closer. 

    The code I'm unsure of is inside the if(serverStatus).  I would put a breakpoint there and inspect to confirm serverStatus is indeed the new status and then see if the lines that follow succeed in replacing the raw JSON object & raise the changes to the UI properly.

    HTH,

    Josh

    Saturday, February 7, 2015 6:14 AM
  • Just read your most recent reply.   Glad you found something that works, although I'm unclear how .execute() on the query causes the UI to update.  There must be some build in listeners doing that(?)

    Assuming you've found a reliable way to populate and dispatch the change to the UI, I agree - a generic helper using the model would be nice next step.

    This would be so much simpler if someone who understood the internals could chime in.  Like I've said before, I miss Huy.  Without him, all we got are kludgy workarounds. ;-|


    • Edited by joshbooker Saturday, February 7, 2015 6:29 AM
    Saturday, February 7, 2015 6:27 AM
  • Yes, I'm also unclear how re-executing the query updates the UI but it seems to work reliably. I've tested this quite a bit now, including when the OrderStatus is null and also to ensure that concurrency checking works as expected when you update the Order, etc. and it all seems good, so for the moment I will go with this (same as code above, just repeating for clarity):

    myapp.OrderEdit.Refresh_execute = function (screen) {
        screen.Order.details.refresh([]).then(function () {
            screen.Order.details.__OrderStatus.query._filter = "Id eq " + screen.Order.details._.OrderStatusId;
            screen.Order.details.__OrderStatus.query.execute();
        });
    };

    I plan to wrap the above into a generic function tomorrow (need a break now :)) and will update this thread with the final generic function.

    Thanks again for your support and interest in this Josh, I really appreciate it.


    Regards, Xander. My Blog

    Saturday, February 7, 2015 6:52 AM
  • You're welcome. I think I found the msls code that refreshes the navprop query:

        function refreshNavigationPropertyQuery(
            details, entityData, entry,
            navigationPropertyData, isVirtual) {
    
            if (navigationPropertyData.query) {
                navigationPropertyData.query = null;
            }
    
            var keyBasedQuery;
    
            if (isVirtual || details.entitySet.dataService.details.getModel().dataProvider !== "AstoriaDataProvider" || !isCollectionNavigationProperty(entry)) {
                var fromKeyProperties,
                    toKeyProperties,
                    toKeyPropertyName,
                    fromKeyProperty,
                    fromKeyPropertyName,
                    fromKeyPropertyType,
                    keyValue,
                    primitiveType,
                    filters = [],
                    targetEntitySet = getNavigationPropertyTargetEntitySet(
                        details, navigationPropertyData);
    
                msls_iterate(navigationPropertyData.associationSet.ends).each(function () {
                    if (this.name === navigationPropertyData.model.fromEnd.name) {
                        fromKeyProperties = this.properties;
                    } else {
                        toKeyProperties = this.properties;
                    }
                });
    
                if (Array.isArray(toKeyProperties) &&
                    Array.isArray(fromKeyProperties) &&
                    toKeyProperties.length === fromKeyProperties.length) {
    
                    toKeyProperties.forEach(function (toKeyProperty, index) {
                        toKeyPropertyName = getNavigationStoragePropertyName(
                            toKeyProperty, isVirtual);
                        fromKeyProperty = fromKeyProperties[index];
                        fromKeyPropertyName = getNavigationStoragePropertyName(
                            fromKeyProperty, isVirtual);
                        fromKeyPropertyType = getNavigationStoragePropertyType(
                            fromKeyProperty, isVirtual,
                            details, fromKeyPropertyName,
                            toKeyPropertyName, targetEntitySet);
    
                        if (fromKeyPropertyType) {
                            keyValue = entityData[fromKeyPropertyName];
    
                            primitiveType = msls_getUnderlyingTypes(fromKeyPropertyType).primitiveType;
    
                            filters.push(toKeyPropertyName + " eq " + msls_toODataString(keyValue, primitiveType.id));
                        }
                    });
                }
    
                if (filters.length > 0 && filters.length === toKeyProperties.length) {
                    keyBasedQuery = targetEntitySet.filter(filters.join(" and "));
                }
            }
    
            if (keyBasedQuery) {
                navigationPropertyData.query = keyBasedQuery;
    
            } else {
                if (!isVirtual) {
                    var result = entityData[entry.serviceName],
                        deferred = result && result.__deferred,
                        queryUri = deferred ? deferred.uri : (entityData.__metadata.uri + "/" + entry.serviceName);
    
                    navigationPropertyData.query = new _DataServiceQuery(
                        {
                            _entitySet: getNavigationPropertyTargetEntitySet(details, navigationPropertyData)
                        },
                        queryUri);
                }
            }
    
            msls_setProperty(navigationPropertyData.query, "_afterQueryExecuted", function (
                query, queryResult) {
                afterNavigationQueryExecuted(
                    details, entry, navigationPropertyData, isVirtual,
                    query, !!keyBasedQuery, queryResult);
            });
        }

    I think there is evidence of excluding RIA data provider:

    .dataProvider !== "AstoriaDataProvider"

    Wonder what would happen if you removed that bit(?)

    Notice it also sets a handler afterNavigationQueryExecuted which is prolly responsible for UI update.

    If nothing else, this may give you idea how to get the query from the model for generic refresh.

    HTH,

    Josh

    Saturday, February 7, 2015 2:29 PM
  • Josh, thanks for the information above, but I'm going for a simple solution right now to move forward. I may revisit this in future again when I have more time.

    Here is the solution with a reusable refreshNavigationProperty() helper function that I'm going with now and it seems to work well:

    // Generic function to refresh a RIA entity navigation property
    // Params:-
    // entity : the LS entity
    // propertyName : name of the navigation property
    // fkName : name of underlying FK property on entity
    // pkName : name of PK on related entity
    function refreshNavigationProperty(entity, propertyName, fkName, pkName) {
        entity.details["__" + propertyName].query._filter = pkName + " eq " + entity.details._[fkName];
        entity.details["__" + propertyName].query.execute();
    }
    
    // Example usage to refresh OrderStatus on Order
    myapp.OrderEdit.Refresh = function (screen) {
        // Must use [] with RIA entity to ensure only entity is refreshed, otherwise results in server error caused by $expand
        screen.Order.details.refresh([]).then(function () {
            // refresh navigation property once entity refreshed (call for each navigation property needing refresh)
            refreshNavigationProperty(screen.Order, "OrderStatus", "StatusId", "Id");
        });
    };
    

    Hope this proves to be useful to someone else using RIA entities.


    Regards, Xander. My Blog

    • Edited by novascape Monday, February 9, 2015 9:44 AM
    Monday, February 9, 2015 9:41 AM
  • When I query for "RiaPerson" ,how can I get related "RiaPet" items at the same time?

    THANKS!!!

    Tuesday, April 21, 2015 1:53 PM