- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Reusing Query parts
Joined: 01-Feb-2006
If have a large query like this (based on PrivateClientFileEntity)...
static PrivateClientFileEntity FetchPrivateClientFile(int privateClientFileID)
{
using (var logger = ProcessLogger.CreateWithAutoCompleteHack("ContextHelper.FetchPrivateClientFile", privateClientFileID))
{
using (var adapter = logger.CreateDataAccessAdapter())
{
var qf = new QueryFactory();
var q = qf.PrivateClientFile
.Where(PrivateClientFileFields.ID == privateClientFileID)
// *** I want to reuse from here down ***
.WithPath(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true))
.WithPath(PrivateClientFileEntity.PrefetchPathFileAnswers
.WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion))
.WithPath(PrivateClientFileEntity.PrefetchPathAdviser
.WithSubPath(AdviserEntity.PrefetchPathPerson
.WithSubPath(PersonEntity.PrefetchPathPrimaryAddress)))
// Bring back the file's Clients
.WithPath(PrivateClientFileEntity.PrefetchPathClients
.WithOrdering(ClientFields.DisplayOrder.Ascending())
// For each client
.WithSubPath(ClientEntity.PrefetchPathQuestionnaireResponses
.WithOrdering(QuestionnaireResponseFields.CreatedDate.Descending())
.WithSubPath(QuestionnaireResponseEntity.PrefetchPathResponseAnswers
.WithSubPath(QuestionnaireResponseAnswerEntity.PrefetchPathResponseAnswerDetails)))
.WithSubPath(ClientEntity.PrefetchPathATRCategories)
// Bring back their home address
.WithSubPath(ClientEntity.PrefetchPathPrimaryAddress)
// And any additional addresses
.WithSubPath(ClientEntity.PrefetchPathAdditionalAddresses
.WithSubPath(AdditionalAddressEntity.PrefetchPathAddress)
)
// And their assets/asset alterations.ownerships
.WithSubPath(LegalBodyEntity.PrefetchPathOwnedAssets
.WithSubPath(AssetEntity.PrefetchPathIncome)
.WithSubPath(AssetEntity.PrefetchPathOutgoing)
.WithSubPath(AssetEntity.PrefetchPathValuations)
.WithSubPath(AssetEntity.PrefetchPathProvider)
.WithSubPath(PolicyEntity.PrefetchPathPolicyAlterations)
.WithSubPath(PolicyEntity.PrefetchPathPolicyType)
.WithSubPath(PolicyEntity.PrefetchPathComponents
.WithSubPath(ComponentFundsEntity.PrefetchPathClientFunds
.WithSubPath(ClientFundEntity.PrefetchPathValuations)))
.WithSubPath(AssetEntity.PrefetchPathOwnerships
.WithSubPath(AssetToOwnerEntity.PrefetchPathLegalBody))
)
// Incomes
.WithSubPath(LegalBodyEntity.PrefetchPathIncomeOwnerships
.WithSubPath(IncomeToOwnerEntity.PrefetchPathIncome
.WithSubPath(IncomeEntity.PrefetchPathIncomeType)
.WithSubPath(IncomeEntity.PrefetchPathAsset)
.WithSubPath(IncomeEntity.PrefetchPathOwnerships)
)
)
// Outgoings
.WithSubPath(LegalBodyEntity.PrefetchPathOutgoingOwnerships
.WithSubPath(OutgoingToOwnerEntity.PrefetchPathOutgoing
.WithSubPath(OutgoingEntity.PrefetchPathOutgoingType)
.WithSubPath(OutgoingEntity.PrefetchPathAsset)
.WithSubPath(OutgoingEntity.PrefetchPathLiability)
.WithSubPath(OutgoingEntity.PrefetchPathOwnerships)
)
)
// Liabilities
.WithSubPath(LegalBodyEntity.PrefetchPathOwnedLiabilities
.WithSubPath(LiabilityEntity.PrefetchPathValuations)
.WithSubPath(LiabilityEntity.PrefetchPathOutgoing)
.WithSubPath(LiabilityEntity.PrefetchPathProvider)
.WithSubPath(NonMortgageLiabilityEntity.PrefetchPathLiabilityType)
.WithSubPath(LiabilityEntity.PrefetchPathOwnerships
.WithSubPath(LiabilityToOwnerEntity.PrefetchPathLegalBody)))
// Trusts
.WithSubPath(LegalBodyEntity.PrefetchPathLegalBodyToTrustees
.WithSubPath(TrusteeEntity.PrefetchPathTrust
.WithSubPath(TrustEntity.PrefetchPathBeneficiaries)
.WithSubPath(TrustEntity.PrefetchPathOwnedAssets
.WithSubPath(AssetEntity.PrefetchPathIncome)
.WithSubPath(AssetEntity.PrefetchPathOutgoing)
.WithSubPath(AssetEntity.PrefetchPathValuations)
.WithSubPath(AssetEntity.PrefetchPathSubAssets)
.WithSubPath(PolicyEntity.PrefetchPathPolicyAlterations)
.WithSubPath(PolicyEntity.PrefetchPathPolicyType)
.WithSubPath(PolicyEntity.PrefetchPathComponents
.WithSubPath(ComponentFundsEntity.PrefetchPathClientFunds
.WithSubPath(ClientFundEntity.PrefetchPathProviderFund
.WithSubPath(ProviderFundEntity.PrefetchPathValuations))
.WithSubPath(ClientFundEntity.PrefetchPathValuations)))
.WithSubPath(AssetEntity.PrefetchPathOwnerships
.WithSubPath(AssetToOwnerEntity.PrefetchPathLegalBody))
)))
.WithSubPath(LegalBodyEntity.PrefetchPathLegalBodyToBeneficiaries
.WithSubPath(BeneficiaryEntity.PrefetchPathTrust
.WithSubPath(TrustEntity.PrefetchPathBeneficiaries)
.WithSubPath(TrustEntity.PrefetchPathOwnedAssets
.WithSubPath(AssetEntity.PrefetchPathIncome)
.WithSubPath(AssetEntity.PrefetchPathOutgoing)
.WithSubPath(AssetEntity.PrefetchPathValuations)
.WithSubPath(AssetEntity.PrefetchPathSubAssets)
.WithSubPath(PolicyEntity.PrefetchPathPolicyAlterations)
.WithSubPath(PolicyEntity.PrefetchPathPolicyType)
.WithSubPath(PolicyEntity.PrefetchPathComponents
.WithSubPath(ComponentFundsEntity.PrefetchPathClientFunds
.WithSubPath(ClientFundEntity.PrefetchPathProviderFund
.WithSubPath(ProviderFundEntity.PrefetchPathValuations))
.WithSubPath(ClientFundEntity.PrefetchPathValuations)))
.WithSubPath(AssetEntity.PrefetchPathOwnerships
.WithSubPath(AssetToOwnerEntity.PrefetchPathLegalBody))
)))
)
)
// And client association
.WithSubPath(LegalBodyEntity.PrefetchPathAssociations
.WithSubPath(AssociationEntity.PrefetchPathTarget
)
)
.WithSubPath(LegalBodyEntity.PrefetchPathTags)
.WithSubPath(LegalBodyEntity.PrefetchPathAssetOwnerships
.WithSubPath(AssetToOwnerEntity.PrefetchPathAsset
.WithSubPath(NonPolicyAssetEntity.PrefetchPathNonPolicyAssetType)))
)
.WithPath(PrivateClientFileEntity.PrefetchPathDependents
.WithSubPath(DependentEntity.PrefetchPathPerson));
var result = adapter.FetchFirst(q);
return result;
}
}
}
...and I now want to reuse the Prefetch bits in another query like this...
var qf = new QueryFactory();
var query = qf.SuitabilityReport
.Where(SuitabilityReportFields.ID == suitabilityReportID)
.WithPath(SuitabilityReportEntity.PrefetchPathClientFile
*** What can I add here?? ***
...how can I split the original query into a second method so it can be reused in multiple queries?
I really don't want to copy/paste - just keep one version in one central place.
Why not create functions/methods which produce the prefetch paths for you? This way you can re-use them without re-using objects which might contain state from a previous query?
Joined: 01-Feb-2006
That's basically what I meant to say. But what would the signature for a function/method that only adds prefetch paths look like?
In the two example I gave above, there is a different starting point for the PrivateClientFileEntity.
The first is
var qf = new QueryFactory(); var q = qf.PrivateClientFile .Where(PrivateClientFileFields.ID == privateClientFileID)
but the second is
var qf = new QueryFactory();
var query = qf.SuitabilityReport
.Where(SuitabilityReportFields.ID == suitabilityReportID)
.WithPath(SuitabilityReportEntity.PrefetchPathClientFile
So the first is a 'direct' additional of WithPath but the second already has a WithPath to get the PrivateClientFileEntity.
Would something like this work....
public static EntityQuery<T> AddPrivateClientFullPrefetchPaths<T>(EntityQuery<T> source) where T: IEntityCore
{
return source
.WithPath(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true))
..... etc.....
.. or is the order in the hierarchy from the EntityQuery<T> relevant?
.WithPath adds the element(s) specified to the existing prefetch path already there (as a new node). So if the path specified has to be part of the existing path already there, then it goes wrong. If it can be added as a separate node from the root, then your method is OK.
Joined: 01-Feb-2006
Not sure to be honest.
Personally, I think it is a reasonable scenario for a feature enhancement but you might not.
I also think I might be out of my depth trying to suggest how to make it work - I don't know the internals well enough.
Internal methods can be got at with Reflection but since it is in a generic class, that makes it more difficult. Even if it became public, I'm not sure that would help.
How far are you willing to go? Just help me hack it as a one-off or add a feature enhancement that can do it?
Off the top of my head, (and assuming for now you might be willing to consider a new feature), I'm thinking along the lines of a WithPathSet() method and two overloads. I have a vague notion of how this might work from the callers point of view and could flesh it out if you think it might be a good new feature.
Joined: 01-Feb-2006
OK, I've had a bit of a think about how it could work
1) Add a new class called "PrefetchPathSet" - Stores the IPrefetchPathElementCore elements in the correct hierarchy but doesn't do anything else with them (there are 5 top-level elements in the top message sample) - Maybe is generic? - PrefetchPathSet<TEntity> to indicate the top level Entity type for which the prefetch paths are for - or that might hinder rather than help
2) PrefetchPathSet has .WithPath() chaining method using exactly the same parameters as EntityQuery<T> does and returns itself - Allows exact cut & paste from an existing structure (like in the top message) for convenience - Will keep the indenting looking nice (in Resharper, when you type the semi-colon, it formats the whole query)
3) The user-created Common Method would then have a signature like public static PrefetchPathSet CreatePrivateClientFilePrefetchPathSet() - Body would look like this:- return new PrefetchPathSet() .WithPath(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true)) .WithPath(PrivateClientFileEntity.PrefetchPathFileAnswers .WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion)) .WithPath(PrivateClientFileEntity.PrefetchPathAdviser .WithSubPath(AdviserEntity.PrefetchPathPerson .WithSubPath(PersonEntity.PrefetchPathPrimaryAddress)))
etc.
4) New method on EntityQuery<T> with sig like "public EntityQuery<T> WithPathSet(PrefetchPathSet prefetchPathSet) - Takes the multiple top level Elements in the prefetchPathSet and applies them as though they were applied to the Query with .WithPath(...) - Returns EntityQuery<T> for chaining
5) Another new method overload on EntityQuery<T> with a sig like "public EntityQuery<T> WithPathSet(IPrefetchPathElement2 element, PrefetchPathSet prefetchPathSet) - Applies the element parameter as a .WithPath as normal - Then takes the multiple top level Elements in the prefetchPathSet and applies them on element as though they were .WithSubPath(...) calls - Returns EntityQuery<T> for chaining
I believe that all the other constructs available on IPrefetchPathElementCore - WithOrdering() etc. - will work as normal and, if any require a QueryFactory, well that can be passed into the Common Method as an additional parameter.
So, in summary, one way of defining a set of related prefetch paths from a common point and then two ways of inserting them in queries.
What do you think?
What's the difference between a prefetchpathset and a PrefetchPath2 object, as that already is a set of nodes?
The main reason I asked my previous question was to know whether making the prefetchpath property public or not would help, but I now see that that is not really going to help as you need the node to append the path to as a subpath.
So path P produced by your method is sometimes used as the path (so not as a subpath of some node) and sometimes as the subpath of node N, already in the path. Currently you can't look up that path as the prefetch path property is internal.
So why not create another method which either adds the parentnode + P as subpath to the query or P directly as the path to the query? That would solve your problem without much effort from anyone I think.
The main problem with extending the api with all kinds of prefetch path related classes is that it already is very complicated with the various apis. I am not very fond of adding yet another way to defined prefetch paths if it isn't absolutely necessary.
Joined: 01-Feb-2006
Otis wrote:
The main problem with extending the api with all kinds of prefetch path related classes is that it already is very complicated with the various apis. I am not very fond of adding yet another way to defined prefetch paths if it isn't absolutely necessary.
I know.
That's why I described the issue/solution in great detail. I can't say its absolutely necessary but it would seem to provide a 'final' solution if it was possible.
So why not create another method which either adds the parentnode + P as subpath to the query or P directly as the path to the query? That would solve your problem without much effort from anyone I think.
True but only for the scenario as described here - maintaining two large but virtually identical methods. But if a PrefetchPathSet-type solution was in place then I can create mutiple PrefetchPathSet creating methods. I would also be able to have parameters optionally including paths or not like I currently do with some filters. I would be able to pass PrefetchPathSet instances around in the app. Much flexibility without any (further) API change.
Also the parentNode in the "(parentNode + P)" isn't fixed - there are other places where I have an EntityQuery which prefetches a PrivateClientFile (and P). It is only the P that is common.
simmotech wrote:
Otis wrote:
The main problem with extending the api with all kinds of prefetch path related classes is that it already is very complicated with the various apis. I am not very fond of adding yet another way to defined prefetch paths if it isn't absolutely necessary.
I know.
![]()
That's why I described the issue/solution in great detail. I can't say its absolutely necessary but it would seem to provide a 'final' solution if it was possible.
No, it has the same problem: you can't append an element to an existing prefetchpathset as a subpath of a given node, which is what your problem is. If I have a path P and it has to be a subpath of a node N which is already in a prefetch path (or prefetchpathset, it's the same object, as a prefetchpath2 object is already a set like you say) I still have to specify N and look it up in the existing prefetch path, or prefetchpathset.
That's the problem here: you have a path, and you either want it to add to a prefetch path as the nodeset to use, or as a subpath of a node N, but N is already in the prefetch path.
q.WithPath(p) just does q.PrefetchPath2Object.Add(p), like you'd do in the early days with PrefetchPath2 objects.
So why not create another method which either adds the parentnode + P as subpath to the query or P directly as the path to the query? That would solve your problem without much effort from anyone I think.
True but only for the scenario as described here - maintaining two large but virtually identical methods. But if a PrefetchPathSet-type solution was in place then I can create mutiple PrefetchPathSet creating methods. I would also be able to have parameters optionally including paths or not like I currently do with some filters. I would be able to pass PrefetchPathSet instances around in the app. Much flexibility without any (further) API change. Also the parentNode in the "(parentNode + P)" isn't fixed - there are other places where I have an EntityQuery which prefetches a PrivateClientFile (and P). It is only the P that is common.
I was thinking about: (syntax might be off in details, not tested)
public EntityQuery<T> AddPath<T>(this EntityQuery<T> q, PrefetchPathElement2 parentNode, PrefetchPath2 path)
{
if(parentNode==null)
{
return q.WithPath(p);
}
else
{
return q.WithPath(parentNode.WithSubPath(path));
}
}
Which you then call as:
var qf = new QueryFactory();
var query = qf.SuitabilityReport
.Where(SuitabilityReportFields.ID == suitabilityReportID)
.AddPath(SuitabilityReportEntity.PrefetchPathClientFile, CreateBigPrefetchPath());
and
var qf = new QueryFactory();
var q = qf.PrivateClientFile
.Where(PrivateClientFileFields.ID == privateClientFileID)
.AddPath(null, CreateBigPrefetchPath());
Joined: 01-Feb-2006
Just need to clarify what you envisaged CreateBigPrefetchPath() looking like:
static PrefetchPath2 CreateBigPrefetchPath()
{
var p = new PrefetchPath2(EntityType.PrivateClientFileEntity);
p.Add(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true);
p.Add(PrivateClientFileEntity.PrefetchPathFileAnswers
.WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion));
// etc....
return p;
}
or maybe
static PrefetchPath2 CreateBigPrefetchPath()
{
var p = new PrefetchPath2(EntityType.PrivateClientFileEntity)
{
ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true),
PrivateClientFileEntity.PrefetchPathFileAnswers
.WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion)
// etc....
};
return p;
}
So I can have a PrefetchPath2 with multiple top-level items. Will your AddPath() method still work or will it only work if PrefetchPath2 has had one Add() call? (I think I am confused because although WithPath() accepts an IPrefetchPathElementCore and that indicates to me a single item, a PrefetchPath2 can have multiple items)
Assuming it does work with multiple calls to Add() then I'm very happy with your solution!
Although I would suggest:-
- Have two overloads of AddPath() - one with parentNode and one without. Its as easy to write two methods without if statements as write a single method with one. Also no need for null from the caller side.
- I still think WthPathSet() is a better name because AddPath implies singular but as long as there is such a method available I am happy.
- Completely Optional for the framework but I can write it externally so not a problem:
- Have a PrefetchPathSet object with an private PrefetchPath2 object.
- Have WithPath() chaining methods that return itself. They simply call Add() on the wrapped PrefetchPath2 object. This allows me to cut and paste the same constructs to/from EntityQuery<T> without change.
- Add an implicit conversion operator so a PrefetchPathSet can be used as the parameter in your new AddPath() method.
simmotech wrote:
Just need to clarify what you envisaged CreateBigPrefetchPath() looking like:
static PrefetchPath2 CreateBigPrefetchPath() { var p = new PrefetchPath2(EntityType.PrivateClientFileEntity); p.Add(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true); p.Add(PrivateClientFileEntity.PrefetchPathFileAnswers .WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion)); // etc.... return p; }
or maybe
static PrefetchPath2 CreateBigPrefetchPath() { var p = new PrefetchPath2(EntityType.PrivateClientFileEntity) { ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true), PrivateClientFileEntity.PrefetchPathFileAnswers .WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion) // etc.... }; return p; }
So I can have a PrefetchPath2 with multiple top-level items. Will your AddPath() method still work or will it only work if PrefetchPath2 has had one Add() call? (I think I am confused because although WithPath() accepts an IPrefetchPathElementCore and that indicates to me a single item, a PrefetchPath2 can have multiple items)
WithPath accepts multiple elements. I wrote the AddPath method just as an illustration, so it could return a path, WithPath indeed accepts IPrefetchPathElementCore elements, more than one though (you can specify as much as you like).
This means this: if you want to fetch Order and both its related Customer and Employee entities, you need: order->Customer and order->Employee.
so you create a PrefetchPath2 object and add two elements: (don't mind the names of the elements, it's an illustration)
var orderPath = new PrefetchPath2(EntityType.OrderEntity);
orderPath.Add(OrderEntity.PrefetchPathCustomer);
orderPath.Add(OrderEntity.PrefetchPathEmployee);
.WithPath() usage here is this:
qf.Order
.WithPath(OrderEntity.PrefetchPathCustomer, OrderEntity.PrefetchPathEmployee);
Which will call Add for each element specified in WithPath on the prefetchpath2 object inside the EntityQuery<T>.
The above is equal to:
qf.Order
.WithPath(OrderEntity.PrefetchPathCustomer)
.WithPath(OrderEntity.PrefetchPathEmployee);
as WithPath simply performs an Add on the prefetch path to add another node at the first level (so at the first level away from the root, which is the entity the query is for).
The AddPath method was a method you write btw, I won't add it, as it's an easy helper for your situation. I used it to illustrate what I meant what you can do to easily (if I understand it right) fix the problem of not wanting to copy/paste the large path.
PrefetchPath2 is a collectionbase element, so you can enumerate it.
So you can create the path using the method and then do (example):
foreach(IPrefetchPathElement2 element in CreateBigPrefetchPath())
{
query.WithPath(element);
}
which will add the elements at the root level. SubPath elements are inside the nodes, so they're not at that level.
Joined: 01-Feb-2006
Right. I can see how that works for scenario 1 (and have just confirmed it working
)
Scenario 2 needs a bit more thinking about because if I am looping then I can't use the parent IPrefetchPathElement2 more than once.
Will have a go tomorrow morning.
Joined: 01-Feb-2006
Thanks very much for solving this, Frans. All seems to be working well.
For anyone else requiring this, here are the extension methods I added:-
public static EntityQuery<T> WithPathSet<T>(this EntityQuery<T> query, PrefetchPath2 prefetchPathSet) where T: IEntityCore
{
foreach (IPrefetchPathElementCore rootElement in prefetchPathSet)
{
query.WithPath(rootElement);
}
return query;
}
public static EntityQuery<T> WithPathSet<T>(this EntityQuery<T> query, IPrefetchPathElement2 parentNode, PrefetchPath2 prefetchPathSet) where T: IEntityCore
{
foreach (IPrefetchPathElementCore rootElement in prefetchPathSet)
{
parentNode.WithSubPath(rootElement);
}
query.WithPath(parentNode);
return query;
}
Here is the optional PrefetchPathSet class to allow cut/paste from existing code/chaining:-
public class PrefetchPathSet
{
readonly PrefetchPath2 prefetchPath;
public PrefetchPathSet(Enum rootEntityType)
{
prefetchPath = new PrefetchPath2(rootEntityType);
}
public PrefetchPathSet WithPath(IPrefetchPathElement2 element)
{
prefetchPath.Add(element);
return this;
}
public static implicit operator PrefetchPath2(PrefetchPathSet prefetchPathSet)
{
return prefetchPathSet.prefetchPath;
}
}
Here is a cut-down snippet from the path creation method:-
return new PrefetchPathSet(EntityType.PrivateClientFileEntity)
.WithPath(ClientFileEntity.PrefetchPathFactFindNotes.WithFilter(FactFindNoteFields.Current == true))
.WithPath(PrivateClientFileEntity.PrefetchPathFileAnswers
.WithSubPath(FileAnswerEntity.PrefetchPathFileQuestion));
A snippet when using the PathSet directly on the query:-
var q = qf.PrivateClientFile
.Where(PrivateClientFileFields.ID == privateClientFileID)
.WithPathSet(CreatePrivateClientFilePrefetchPathSet());
And finally, a snippet when using the PathSet further down:-
var result = qf.SuitabilityReport
.Where(SuitabilityReportFields.ID == Task.SuitabilityReportID)
.WithPathSet(SuitabilityReportEntity.PrefetchPathClientFile, CreatePrivateClientFilePrefetchPathSet());
Joined: 01-Feb-2006
Finally, one addition to PrefetchPathSet to allow nesting:-
public PrefetchPathSet WithPathSet(IPrefetchPathElement2 element, PrefetchPath2 prefetchPathSet)
{
foreach (IPrefetchPathElementCore rootElement in prefetchPathSet)
{
element.WithSubPath(rootElement);
}
prefetchPath.Add(element);
return this;
}
Sample use:-
public static PrefetchPathSet ForSuitabilityReport()
{
return new PrefetchPathSet(EntityType.SuitabilityReportEntity)
.WithPathSet(SuitabilityReportEntity.PrefetchPathClientFile, ForPrivateClientFile())
.WithPath(SuitabilityReportEntity.PrefetchPathProposal);
}