Call WithPath now, add additional path later

Posts   
 
    
worldspawn avatar
worldspawn
User
Posts: 321
Joined: 26-Aug-2006
# Posted on: 21-Jul-2009 08:38:54   

Hi,

in my never ending quest to cut down on all the code repetition that (I feel) LINQ causes I am trying to write a method that will add the 'standard' prefetches for my entity.

An example: I have Project, referencing Project is Activity. Now for my various Project queries that prefetch Activity I find myself rewriting the same prefetches over and over, (the Prefetch, the OrderBy, the FilterOn etc). I want this logic defined in a single location that various queries can reuse whenever they prefetch Activity.

To accomodate this I came up with this method:


internal static IPathEdgeParser<T, ActivityEntity> PrefetchCore<T>(ref IQueryable<T> query, Expression<Func<T, object>> expression) where T:EntityBase2
        {
            IPathEdgeParser<T, ActivityEntity> prefetch = null;
            query = query.WithPath(p => (prefetch = p.Prefetch<ActivityEntity>(expression).
                                                            OrderBy(a => a.DueDate).OrderBy(a => a.Name)).
                                                            SubPath(a => a.Prefetch<UserEntity>(t => t.UserUsingAssignedTo)));
            if (!Thread.CurrentPrincipal.IsInRole("Can_Access_Internal_Activity") && prefetch != null)
                    prefetch.FilterOn(p => p.IsInternal == false);

            return prefetch;
        }

Calls look like:


Activity.PrefetchCore(ref project, p => p.ActivityUsingProjectId);

"project" is an IQueryable<ProjectEntity>.

This all works. However it blows away any prefetches I may have already added to the IQueryable, also if I try to add more after the call it blows away what was added in my PrefetchCore method call.

I understand that WithPath replaces what was created before. So is it possible to get a reference to the existing prefetch and add to it??

worldspawn avatar
worldspawn
User
Posts: 321
Joined: 26-Aug-2006
# Posted on: 21-Jul-2009 10:08:17   

After having a squiz at the runtime source code I came up with this: (thanks for public methods Frans simple_smile )


internal static void PrefetchCore<T>(PathEdgeRootParser<T> parser, Expression<Func<T, object>> expression) where T:EntityBase2
        {
            var path = parser.Prefetch<ActivityEntity>(expression).
                                                            OrderBy(a => a.DueDate).OrderBy(a => a.Name).
                                                            SubPath(a => a.Prefetch<UserEntity>(t => t.UserUsingAssignedTo));
            if (!Thread.CurrentPrincipal.IsInRole("Can_Access_Internal_Activity") && path != null)
                    path.FilterOn(p => p.IsInternal == false);
        }

called like:


PathEdgeRootParser<ProjectEntity> parser = new PathEdgeRootParser<ProjectEntity>(LinqUtils.GetElementCreator(project));
parser.Prefetch<DepartmentEntity>(d => d.DepartmentUsingDepartmentId);

Activity.PrefetchCore(parser, p => p.ActivityUsingProjectId);
project = project.WithPath(parser.RootEdges.ToArray());

It's not quite as glamourous due to the need to create the parser etc. But it's bearable; might be able to figure out a way to kill of a line or 2 of code.

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 21-Jul-2009 10:21:23   

Thanks for the feedback. simple_smile

worldspawn avatar
worldspawn
User
Posts: 321
Joined: 26-Aug-2006
# Posted on: 21-Jul-2009 11:09:42   

The sharing doesn't stop, yet.

I wasn't happy with the mess of managing my own PathEdgeRootParser so...


public class Fetcher<T> : IDisposable where T:EntityBase2
    {
        public Fetcher(ref IQueryable<T> source)
        {
            parser = new PathEdgeRootParser<T>(LinqUtils.GetElementCreator(source));
            this.source = source;
        }

        PathEdgeRootParser<T> parser;
        IQueryable<T> source;

        public PathEdgeRootParser<T> Parser
        {
            get{ return parser; }
        }
    
        #region IDisposable Members

        public void Dispose()
        {
           source =  source.WithPath(parser.RootEdges.ToArray());
        }

        #endregion
    }

    public static class Extensions
    {
        public static Fetcher<T> BeginPrefetch<T>(this IQueryable<T> source) where T : EntityBase2
        {
            return new Fetcher<T>(ref source);
        }
    }

Sample use:


using (var f = project.BeginPrefetch())
                    {
                        f.Parser.Prefetch<DepartmentEntity>(d => d.DepartmentUsingDepartmentId)
                        .Prefetch<UserEntity>(u => u.UserUsingManagerId).Prefetch<ClientEntity>(c => c.ClientUsingClientId)
                        .SubPath(cu => cu.Prefetch<ClientUserEntity>(c => c.ClientUserUsingClientId))
                        .Prefetch<DocumentEntity>(d => d.DocumentUsingProjectId).OrderByDescending(d => d.ModifiedDate)
                        .SubPath(dt => dt.Prefetch<DocumentTypeEntity>(d => d.DocumentTypeUsingDocumentTypeId))
                        .Prefetch<ProjectNoteEntity>(n => n.ProjectNoteUsingProjectId);

                        Activity.PrefetchCore(f.Parser, p => p.ActivityUsingProjectId);
                    }

This seems to work. It modifies the reference to the IQueryable that BeginPrefetch was called from... i think.

One caveat is you cannot modify the IQueryable reference inside the using block. Well you could but you would end up with a new reference that wouldn't have any of the prefetches applied. (i think,need to test).

Reason for putting this here was I wondering if there was another way to "alter" a previous call to WithPath?

**Edit: Not working, the original IQueryable reference isn't updated... **

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 22-Jul-2009 11:01:54   

Edit: Not working, the original IQueryable reference isn't updated..

So did you fall back to your initial solution?

worldspawn avatar
worldspawn
User
Posts: 321
Joined: 26-Aug-2006
# Posted on: 23-Jul-2009 01:43:28   

Yeah I couldn't see a way to update that reference using the "using" approach.

My test case implementation looks like this now.


var f = project.BeginPrefetch();
                    
                    f.Parser.Prefetch<DepartmentEntity>(d => d.DepartmentUsingDepartmentId)
                    .Prefetch<UserEntity>(u => u.UserUsingManagerId).Prefetch<ClientEntity>(c => c.ClientUsingClientId)
                    .Prefetch<ProjectNoteEntity>(n => n.ProjectNoteUsingProjectId);

                    Activity.PrefetchCore(f.Parser, p => p.ActivityUsingProjectId);

                    project = f.Process();

So it's almost the same, just manually updating my iqueryable reference.