New Posts New Posts RSS Feed: Bizzare error filtering cached entities
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Bizzare error filtering cached entities

 Post Reply Post Reply
Author
jsobell View Drop Down
Groupie
Groupie
Avatar

Joined: 02-Apr-2009
Location: Australia
Posts: 80
Post Options Post Options   Quote jsobell Quote  Post ReplyReply Direct Link To This Post Topic: Bizzare error filtering cached entities
    Posted: 25-Jun-2009 at 10:55pm
The code below is a workaround we are trying to use for the entity security issues I mentioned in the past, where '.Include'ed entities must be filtered in the IEntityFetching server-side process.
As you can see, I'm basically chaining a series of async fetches to populate the client cache with all of the entities in my hierarchy, then the intention was to call the fetch with a .Include() and in theory the whole thing should be retrieved from the entity cache (which of course now contains a permission filtered set of entities).
However, if the code is used with the statement ".With(QueryStrategy.CacheOnly)" then the OnFetching() fails when it tries to apply a filter collection to the original query with the following error:

The type or method has 2 generic parameter(s), but 1 generic argument(s) were provided. A generic argument must be provided for each generic parameter.

   at System.RuntimeType.SanityCheckGenericArguments(Type[] genericArguments, Type[] genericParamters)
   at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
   at IdeaBlade.EntityModel.EntityQueryFilterVisitor.ModifyExpression(MemberExpression memberExpression, Expression fqExpression)
   at IdeaBlade.EntityModel.EntityQueryFilterVisitor.VisitMemberAccess(MemberExpression me, Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpressionCore(Expression e)
   at IdeaBlade.Linq.TransformExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.LocalizingExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpression(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpressionCore(Expression e)
   at IdeaBlade.Linq.TransformExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.LocalizingExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpression(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpressionCore(Expression e)
   at IdeaBlade.Linq.TransformExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.LocalizingExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpression(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.<VisitExpressions>b__0(Expression e)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpressions(IEnumerable`1 expressions)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpressionCore(Expression e)
   at IdeaBlade.Linq.TransformExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.LocalizingExpressionVisitor.VisitExpressionCore(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.VisitExpression(Expression expr)
   at IdeaBlade.Linq.ExpressionVisitor.Visit(Expression expr)
   at IdeaBlade.Linq.LocalizingExpressionVisitor.Visit(Expression expr)
   at IdeaBlade.EntityModel.EntityQueryFilterVisitor.FilterQuery(EntityQuery query)
   at IdeaBlade.EntityModel.EntityQueryExtensions.Filter[TQuery](TQuery query, EntityQueryFilterCollection filters)
   at QuestManager.Web.BOSExtensions.EntityRestrictionFilter.OnFetching(EntityServerFetchingEventArgs args) in D:\Source\QuestManager\QuestManager.Web\BOSExtensions\EntityRestrictionFilter.cs:line 71
   at IdeaBlade.EntityModel.Server.EntityServer.OnFetching(SessionBundle sessionBundle, IEntityQuery& query, Object& context)
   at IdeaBlade.EntityModel.Server.EntityServer.Fetch(SessionBundle sessionBundle, IEntityQuerySurrogate surrogate)
   at SyncInvokeFetch(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)


Replace the cache fetch with "QueryStrategy.Normal" and the error disappears, so I suspect there's something failing in the filter application code.

Code for server-side filtering is as follows:

        public void OnFetching(EntityServerFetchingEventArgs args)
        {
            var manager = new DomainModelEntityManager(EntityManager.DefaultManager);

            if (args.Principal == null)
                throw new ApplicationException("Attempt to fetch data with an unauthenticated user");

            int uid = GetUserId(args.Principal);

            var eqFilters = new EntityQueryFilterCollection();

            Debug.WriteLine("==== OnFetching {0}", args.Query.ToString());

            eqFilters.AddFilter((IQueryable<CustomerAccount> custs) =>
                    (from e in custs
                     join perm in manager.CachedPermissions
                     on e.CustomerAccountID equals perm.ElementID
                     where (perm.PermissionBits != 0) && perm.UserProfileId == uid
                        && e.DeleteDate == null
                     select e).AsQueryable());

            eqFilters.AddFilter((IQueryable<Client> clis) =>
                    (from e in clis
                     join perm in manager.CachedPermissions
                     on e.ClientID equals perm.ElementID
                     where (perm.PermissionBits != 0) && perm.UserProfileId == uid
                        && e.DeleteDate == null
                     select e).AsQueryable());

(etc...)

            var original = args.Query as EntityQuery;
            args.Query = original.Filter(eqFilters);
        }



Code for fetching the entities is as follows:


        private void GrabEntities<T>(IEntityQuery<T> qry, Action<List<T>> callback) where T:Entity
        {
            List<T> results = null;
            Debug.WriteLine("1 - Fetching records from {0}", typeof (T).Name);
            _dom.ExecuteQueryAsync<T>(qry,
                                args =>
                                {
                                    if (args.Error != null)
                                        throw args.Error;
                                    else
                                    {
                                        results = args.Result.ToList();
                                        Debug.WriteLine("2 - Fetched {0} records of type {1}", results.Count,
                                                        typeof (T).Name);
                                        callback(results);
                                    }
                                }, null);
        }


        private void ReloadCustomers(Guid customerAccountid)
        {
            LoadCustomers(customerAccountid);
        }


        private void LoadCustomers(Guid customerAccountid)
        {
            if (customerAccountid == Guid.Empty)
                return;

                   GrabEntities<CustomerAccount>(from a in _dom.CustomerAccounts select a,
            cba => GrabEntities<Client>(from c in _dom.Clients select c,
            cbc => GrabEntities<QuestionnaireProject>(from p in _dom.QuestionnaireProjects select p,
            cbp => GrabEntities<QuestionnaireWave>(from w in _dom.QuestionnaireWaves select w,
            cbw => GrabEntities<Questionnaire>(from q in _dom.Questionnaires select q,
            cbq => ShowTree())))));
        }

        private void ShowTree(){
            Debug.WriteLine("*** Displaying results from cache for Account!");

            var qry = from c in _dom.CustomerAccounts.Include("Clients.QuestionnaireProjects.QuestionnaireWaves.Questionnaires")
                                                                .With(QueryStrategy.Normal)
                      where c.CustomerAccountID == _customerId
                      select c;

            _dom.ExecuteQueryAsync<CustomerAccount>(qry,
                                args =>
                                {
                                    if (args.Error != null)
                                        throw args.Error;
                                    else
                                    {
                                        CustomerAccount = args.Result.First();
                                        Debug.WriteLine("*** CustomerAccount retrieved!");
                                    }
                                }, null);
        }


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: 26-Jun-2009 at 8:34pm
This is especially bizarre because when QueryStrategy.CacheOnly is used the query should not ever be sent to the server at all, nor the IEntityServerFetching handler invoked, yet your stack trace indicates this happening.  Are you certain the query being executed was the one from ShowTree() when this exception occurred? 
 
Also, none of the filters you show in your abbreviated version of OnFetching should ever cause the IdeaBlade.EntityModel.EntityQueryFilterVisitor.ModifyExpression method to be invoked, as shown by the stack trace.  So I'm quite puzzled.  Needless to say, I was unable to reproduce this error when testing against the tutorial database and DevForce v5.1.1.
 
As a side note, the AsyncSerialTask and/or AsyncParallelTask classes could be useful to you in building up a number of queries to be run, as you do here.
Back to Top
jsobell View Drop Down
Groupie
Groupie
Avatar

Joined: 02-Apr-2009
Location: Australia
Posts: 80
Post Options Post Options   Quote jsobell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Jun-2009 at 8:47pm
I actually had difficulty identifying when and where this query was triggered, and initial tests suggested that it was when some properties were being read from the cache returned entities.  Could it be related to lazy-loading in the cached entities?  The subsequent code at the client end then simply fires on the setting of the CustomerAccount property, then walks through the tree accessing fields for display purposes.
 
The query code shown above runs fine, and the entities appear in the cache correctly, but it seems to be some subsequent requests that cause this error.  The fact that we experience a 5 second delay before the error appears is even more confusing, which was why I posted the stack trace in the hope that you might recognise where this is triggered internally and the area in which I should concentrate my investigations.
 
Cheers,
 Jason
Back to Top
jsobell View Drop Down
Groupie
Groupie
Avatar

Joined: 02-Apr-2009
Location: Australia
Posts: 80
Post Options Post Options   Quote jsobell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Jun-2009 at 8:48pm
Oh, and I'll check out the AsyncParallelTask class!  That sounds very useful indeed.
 
Cheers,
 Jason
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: 27-Jun-2009 at 8:02am
Lazy loading could be involved here.  Check the debuglog if you haven't already, it may have some clues.   A client-side Fetching handler (on the EntityManager) can also help in tracking down problems where a query is going out to the server but shouldn't be, since this event fires just before the message is sent to the server.
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: 27-Jun-2009 at 6:13pm
I was able to reproduce this error, so no need for you to investigate further.  The problem is due to lazy loading - the RelatedEntityList involved causes problems when filtering.  I don't yet know a workaround for you - other than possibly to set the default query strategy to CacheOnly once you've loaded all data so that nothing leaks out to the server.  (Why queries are still going to the server when they seemingly shouldn't is possibly also an issue, which we'll look at too.)
 
We've opened a bug report for the problem.  Thanks for finding this.
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down