Print Page | Close Window

Async Projections, Navigation Properties and Fake Entity Managers

Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=2679
Printed Date: 28-Jun-2026 at 8:49am


Topic: Async Projections, Navigation Properties and Fake Entity Managers
Posted By: JoeGershgorin
Subject: Async Projections, Navigation Properties and Fake Entity Managers
Date Posted: 13-May-2011 at 6:41pm
Running DevForce 6.0.9 under Silverlight 4.

I have a POCO that is roughly structured like this (it's in my Silverlight domain model project and linked in the BOS domain model project):

[DataContract]
public class PasSummary : IHasEntityAspect, IKnownType
{    
    [Key]
    public Guid Id { get; set; }
    
    [DataMember]
    public PasNumber PasNumber { get; set; }

    [DataMember]
    public int Number { get; set; }

    [DataMember]
    public string Description { get; set; }

    [DataMember]
    public string Function { get; set; }

    [DataMember]
    public Decimal TotalHours { get; set; }               
  
    [DataMember]
    public IEnumerable<Project> Projects { get; set; }

    [IgnoreDataMember]
    public EntityAspect EntityAspect { get; set; }        
}

I have an Async projection that roughly looks like this:
 var getEntriesForWeekOp = Manager.TimeSheetEntries
        .Where(p =>
        p.TimeSheet.UserId == _currentUser.Id &&
        p.TimeSheet.WeekEndingDate == date.Date)                    
        .Include("TimeSheet")
        .Include("Project.PasNumber.PasFunction")
        .GroupBy(timeSheetEntry => timeSheetEntry.Project.PasNumber)
        .Include("Projects.PasNumber.PasFunction")
        .Select(timeSheetEntries=>                        
            new PasSummary{                        
            TestObject = timeSheetEntries.ToList(),                        
            PasNumber = timeSheetEntries.FirstOrDefault().Project.PasNumber,
            Number =  timeSheetEntries.FirstOrDefault().Project.PasNumber.Number,
            Description = timeSheetEntries.FirstOrDefault().Project.PasNumber.Description,
            Function = timeSheetEntries.FirstOrDefault().Project.PasNumber.PasFunction.Name,
            Projects = timeSheetEntries.Select(entry=>entry.Project).Distinct().ToList(),            
            TotalHours = timeSheetEntries.Where(ts=>ts.ActualHoursForWeek.HasValue)
                                         .Select(tse=>tse.ActualHoursForWeek.Value).Sum()
        })                    
        .ExecuteAsync();

Problem 1: (Navigation Properties)
The PasNumber is a entity type. It has a navigation property called PasFunction. If I project PasNumber into PasSummary's PasNumber property all the navigation properties are null. Ommiting or adding the includes has no effect. However if I project the navigation property values (Such as in the above Function property) the values feeds in fine. When using a fake entity manger backing store the PasFunction navigation property is retained.

Problem 2 : Projecting collections differences when using a fake entity manager backing store:

The assignment of the "Projects" property in PasSummary in the above projection throws with the following exception when using a real entity manager:
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[ProjectVisionModel.Project] ToList[Project](System.Collections.Generic.IEnumerable`1[ProjectVisionModel.Project])' method, and this method cannot be translated into a store expression.

If I ommit the .ToList() the assignment works fine, but then when using the fake entity backing store causes the following exception:
Type 'System.Linq.Enumerable+<DistinctIterator>d__81`1[ProjectVisionModel.Project]' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  If the type is a collection, consider marking it with the CollectionDataContractAttribute.  See the Microsoft .NET Framework documentation for other supported types.

So it seems the fake backing store hates ommiting the .ToList() and the real entity manager hates it when the .ToList() is there.

Any feedback you could provide on the recommended way to deal with these issues would be appreciated, thanks.





Replies:
Posted By: DenisK
Date Posted: 17-May-2011 at 11:45am
Hi Joe;

I'm going to repro this on my machine and get back to you as soon as possible. Sorry for the delay.


Posted By: DenisK
Date Posted: 17-May-2011 at 5:11pm
Hi Joe;

Problem 1 seems to be a bug. I will file a bug report for this. For now, the workaround is to project a completely anonymous type and then create the new PasSummary POCO object from it. You also don't need to use .Include when doing projection. This is because projection query are done on the server so navigational properties can be fetched immediately.

Problem 2:

- The first LINQ to Entities exception is an EF limitation. In general, whenever you see this exception, it means that it's an EF limitation. This ToList() call works against the fake backing store because we're not working against EF in this environment.

- The second DistinctIterator exception is a bug. I will also file a bug report for this.

For now, the workaround for problem 2 is to switch between using .Distinct().ToList() and just .Distinct() between the fake and the non fake backing store. After the bug is fixed, the right way to do this is to just use .Distinct().

I hope this is clear. Please let me know if you need further clarification.


Posted By: JoeGershgorin
Date Posted: 18-May-2011 at 12:53pm
Thanks Denis, I'll probably get an opportunity to get back to this issue tomorrow and will let you know. I've already implemented if/else workaround to use/notuse ToList() depending on the entity backing store, it will be nice to have a single code base for both backing stores when the bug is fixed, thanks.


Posted By: pk55
Date Posted: 19-Jun-2011 at 12:08pm

Is this fixed in 6.1.0 or 6.1.1? I don't see anything in the release notes to indicate it was.  The reason I ask is that in 6.0.9, using the Distinct on a simple projection fails.  This is running on the Silverlight Client against cache only if that makes any difference. 

//Try this in NorthwindIB (assuming you've already loaded the orders into the EM.
// both properties are ints and is the same thing I'm doing in our data model
var testing = _entityManager.Orders.With(QueryStrategy.CacheOnly)
.Select(x => new {x.OrderID, x.EmployeeID}).Distinct();
 
// will throw the exception on the first iteration
foreach (var v in testing)
{
   if (v.EmployeeID >= 0)
    {}
}
 
Exception Type: System.InvalidCastException
Message: Unable to cast object of type '<>f__AnonymousType14`2[System.Int32,System.Int32]' to type '_IB_f__AnonymousType14`2g2sDsTuu_pDiPtoeb[System.Int32,System.Int32]'.
Data: System.Collections.ListDictionaryInternal
StackTrace Information
*********************************************
   at lambda_method(Closure , Object )
   at IdeaBlade.Core.AnonymousFns.<DeconstructMany>d__e.MoveNext()
   at IdeaBlade.Core.AnonymousFns.<ConstructMany>d__0.MoveNext()
   at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at IdeaBlade.EntityModel.EntityQuery`1.ExecuteExpression()
   at IdeaBlade.EntityModel.EntityQuery`1.ExecuteCacheQuery()
   at IdeaBlade.EntityModel.EntityQueryFinder.ExecuteFind(Guid queryGuid)
   at IdeaBlade.EntityModel.EntityQueryFinder.Execute()
   at IdeaBlade.EntityModel.EntityManager.ExecuteQueryCore(IEntityQuery query, Boolean isAsync)
   at IdeaBlade.EntityModel.EntityManager.ExecuteQuery[T](IEntityQuery`1 query)
   at IdeaBlade.EntityModel.EntityQueryExtensions.Execute[T](IEntityQuery`1 query)
   at IdeaBlade.EntityModel.EntityQuery`1.GetEnumerator()

If instead I use GroupBy, it works fine except of course, the entire entity ends up being retrieved (it's not a projection anymore):

var testing = _entityManager.Orders.With(QueryStrategy.CacheOnly).GroupBy(x => new {x.OrderID, x.EmployeeID}).Execute().ToList();

// will throw the exception on the first iteration
foreach (var v in testing)
{
   // will need to treat as key/value pair now
   if (v.Key.EmployeeID >= 0)
    {}
}
 
 


Posted By: pk55
Date Posted: 20-Jun-2011 at 10:01pm
Just upgraded to 6.1.1 and this still isn't fixed.  The Distinct iterator doesn't work even with a simple projection. 


Posted By: DenisK
Date Posted: 21-Jun-2011 at 6:58pm
Hi pk55;

The previous posts were talking about using Distinct with a POCO object on a projection. Your issue, although may sound similar, is actually different. 

I've filed a bug report for this as well. This query apparently only fails in SL CacheOnly query. It works on 2-tier, n-tier and SL DataSource query. I'm assuming you want a patch for this as well once it becomes available?


Posted By: pk55
Date Posted: 21-Jun-2011 at 7:11pm
Yes a patch would be great, thanks for looking into this.


Posted By: pk55
Date Posted: 22-Jun-2011 at 5:33pm
One last clarification; it seems that any SL CacheOnly query that uses a projection fails when trying to iterate over it or just sending it to a list.


Posted By: DenisK
Date Posted: 23-Jun-2011 at 2:15pm
pk55;

Yes you're right. I've submitted these 2 bugs with a high priority. I'll send you a patch as soon as they're available.



Print Page | Close Window