- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
How to use "prefetch" on llblgenprodatasource
Joined: 28-Dec-2010
Hi
We encountered one problem which lead to seldom use the llblgenprodatasource. E.G
<llblgenpro:LLBLGenProDataSource2 ID="regulationBaseSource" runat="server" CacheLocation="Session"
AdapterTypeName="BusinessObjects.Adapter.DatabaseSpecific.DataAccessAdapter, Enhesa.BusinessObjects.Adapter"
DataContainerType="EntityCollection" EntityFactoryTypeName="BusinessObjects.Adapter.FactoryClasses.RegulationBaseEntityFactory, BusinessObjects.Adapter"
LivePersistence="true" EnablePaging="true" >
<SelectParameters>
<asp arameter Type="String" Name="CountryCode" DefaultValue="BE" />
<asp
arameter Type="String" Name="RegionCode" DefaultValue="_countrywide" />
</SelectParameters>
</llblgenpro:LLBLGenProDataSource2>
Every llblgenprodatasource only focus on one entity, here is RegulationBaseEntity.
But in many situation, we should present many information from all kinds of entities which have relationships each other. Such as: from r in linqMetaData.RegulationBase.WithPath(a=>a.Prefetch<RegulationTextEntity>(b=>b.RegulationText).Prefetch<RegulationCourntryRegion>(b=>b.RegulationCountryRegion>) select new RegulationInfo { RegID = r.RegID, RegTitle = r.RegulationText.single(n=>n.languagecode == "en").RegTitle, CountryCode = r.RegulationCourntryRegion.CountryCode, RegionCode = r.RegulatitonCountryRegion.RegionCode, }
As usual, we directly use the "objectdatasource" to provide the datasource of "RegualtionInfo" above.
Our difficult problem is that we didn't know how to use the pretch way on llblgenprodatatsouce. e.g, regulationDatasouce above, is a llblgenprodatasource. Becasuse regulationDatasouce was only used for RegulationBase, I want to this datasouce how to pretch RegulationText and RegulationCountryRegion, then GridView can present information from RegulationBase, RegulaitonText, RegulationCountryRegion with regulationDatasouce.
Best regards
Joined: 28-Dec-2010
Walaa wrote:
The LLBLGenProDataSource has a property PrefetchPathToUse. You can set it in code behind.
Yes, I add a pretech path fro prefetchpathToUser. But I still didn't know how to use it on the grid or devexpress control.
It doesn't work on <%# Eval("RegualtionTextEntity.RegualtionBaseEntity") %> in one field of gridview.
Could you give a good demo which use gridview + llblgenprodatasource and the llbglenprodatasource use the prefetch? Of course the gridview also use the prefetch function on some field bind.
Best regards
linzx wrote:
Could you give a good demo which use gridview + llblgenprodatasource and the llbglenprodatasource use the prefetch? Of course the gridview also use the prefetch function on some field bind.
Please check this thread for some options: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=12021&StartAtMessage=0𐒽
Joined: 02-Nov-2007
@linzx and I are working together on this but we have both independently posted in different forums:
The main problem is the 3 year old post that you guys helpfully refer to simply doesn't work anymore in option A (with the Eval(entitycollection.property) syntax . Something, somewhere is now broken or there is some black magic missing with no code anywhere to show this as a working example.
We are working in the dark and I am really worried that we are the only company in the world trying to use an LLB EntityCollection in a grid and access its Prefetch and subpaths. Gosh, I hope that isn't true!
The solution that works but seems like a workaround is to grab the datasource in the custom unbound field handler of the ASPxGridView and cast it appropriately:
protected void ASPxGridView1_CustomUnboundColumnData(object sender, ASPxGridViewColumnDataEventArgs e)
{
var regulationTextEntity = ((RegulationTextEntity)LlblGenProDataSource2.EntityCollection[e.ListSourceRowIndex]);
var regulationCountryRegionEntity = regulationTextEntity.Regulation.RegulationCountryRegion;
if (e.Column.FieldName == "RegionCode")
{
// do something with the SubPath fields
e.Value = String.Join(", ",regulationCountryRegionEntity.Select(a=>(a.RegionCode)));
}
}
That isn't exactly what we want to do, but it serves as an example to get the properties of a prefetch and subpath collection.
If anyone has working code using the Eval or DataBinder approach, rather than snippets that are supposed to work, then it would be really useful for us to see it.
btw, I have upgraded our licences to v3 but with around 100 solutions to change, I haven't been able to plan the migration yet. If v3 solves this problem, then I will make time to migrate immediately.
Joined: 02-Nov-2007
Note that the thread referred to from 3 years ago suggests creating custom properties in the designer. I fail to see why this needs to be done when the EntityCollection is already a "Field Mapped On Relations". i.e it is already available.
Is this something to do with "Browseable(false)" attribute on the class property? I saw another thread (http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=9384) where the author recompiled with the attribute and this then worked.
Otis said it had been added to prevent the collection being mapped as a field, but I guess the side affect on the related entity collection field has been to prevent its use altogether!
For first level prefetchPath, the following should work:
'<%# DataBinder.Eval(Container.DataItem, "Customer.Name")%>'
Assuming you were fetching Orders and prefetching the Customers.
Otherwise you may create a custom Property in the fetched entity to return back the dete you want to display from the prefetched entity.
Joined: 02-Nov-2007
Just to confirm that the Eval syntax on related entity collections does indeed work when recompiling with [Browsable(true)] on the related entity (Regulation in my example) in the Generic class.
So this now works:
'<%# Eval("Regulation.RegulationCountryRegion[0].RegionCode") %>
So, I would guess this browsable attibute has been made false since the 3 year old thread and no-one has noticed (or cared?) that it screws up simple databinding using related entity collections.
I don't really know what to do next on this one. Using Prefetch data in controls on an ASPX page is the main reason I want to use an OR/M in web applications. I assume that other control vendors such as Telerik will have the same issue as it seems to be a "by design" issue of LLB? Will this also be a problem if I switch to using EntityFramework?
Without this functionality, most of the goodness gets lost. For example, using the UoW to just save a full entity collection. I now have to project into a flat POCO (or will that actually work with collections as it won't have the browsable attribute?) then try and sort out what to save with the result of having loads of code. Very nasty indeed.
Joined: 02-Nov-2007
Walaa wrote:
For first level prefetchPath, the following should work:
'<%# DataBinder.Eval(Container.DataItem, "Customer.Name")%>'
Assuming you were fetching Orders and prefetching the Customers.
Otherwise you may create a custom Property in the fetched entity to return back the dete you want to display from the prefetched entity.
No. that just doesn't work at all. If it did, we wouldn't need this thread.
As I said, this only works if you recompile your LLB Adapter with [Browsable(true)] on the Customer related collection. It might have worked 3 years ago though.
Creating custom properties for the bazillion things we need isn't an option. Not all databases are as simple as Northwind We have 150 tables and rarely, if ever, can we get data from just one table. That is precisely the reason we need to use LLB objects with their paths in aspx controls.
Design-wise, in web applications it can be costy to fetch entities with multiple prefetchPaths to display data from more than one table.
Thus normally I'd recommend using flat lists (TypedList or DynamicList). As these are more light weight and right to the point. This of course comes at the cost of some code wiring in case you want to save back edited data. As you will need to map the edited row to an entity object to be saved.
Otherwise you should alter the browsable attribute as you have said, but wait.. There's a setting for that.
You might have missed this thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=18913&StartAtMessage=0𙼳
Joined: 02-Nov-2007
OK. So you think in v2.6 I should find the HideManyOneToOneRelatedEntityPropertiesFromDatabinding setting in the designer and set it to false so the Browsable attribute is set to true for related entity collections. I'll try that.
I must admit to being a bit perplexed by the advice to not use an intelligent object for databinding when saving. Your proposal would mean that we need to do a lot of coding that that the LLB object already does for us. In one or two grids, that might not be too much of a problem, but we will have dozens if not hundreds of updatable grids in our applications by the time it is finished (if ever that happens).
For read-only grids I agree with your advice, although it still means creating and maintaining a specialised DTO or TypedList.
OK. So you think in v2.6 I should find the HideManyOneToOneRelatedEntityPropertiesFromDatabinding setting in the designer and set it to false so the Browsable attribute is set to true for related entity collections. I'll try that.
if "HideManyOneToOneRelatedEntityPropertiesFromDatabinding" is set to True, then false will be set for the Browsable attribute.
I must admit to being a bit perplexed by the advice to not use an intelligent object for databinding when saving. Your proposal would mean that we need to do a lot of coding that that the LLB object already does for us. In one or two grids, that might not be too much of a problem, but we will have dozens if not hundreds of updatable grids in our applications by the time it is finished (if ever that happens).
For read-only grids I agree with your advice, although it still means creating and maintaining a specialised DTO or TypedList.
This is a trade-off you have to made, between performance and effort done in coding and its maintenance. So it's your call after all.
Joined: 02-Nov-2007
Walaa wrote:
if "HideManyOneToOneRelatedEntityPropertiesFromDatabinding" is set to True, then false will be set for the Browsable attribute. This is a trade-off you have to made, between performance and effort done in coding and its maintenance. So it's your call after all.
That didn't happen. I set HideManyOneToOneRelatedEntityPropertiesFromDatabinding to false and the browsable attribute disappears. However, now I can eval into the prefetch paths on all entities.
So the next issue might be a DevExpress one. I set a bound column and the use Eval to fill it, but when I change the value in the ui, the datasource (which, btw, is now no longer an LLB DS as I have changed to use the intrinsic DevExpress LinqServerModeDataSource) isn't changed.
<dxwgv:GridViewDataTextColumn FieldName="Answer" Name="Answer" ShowInCustomizationForm="True"
VisibleIndex="2">
<DataItemTemplate>
<dxe:ASPxRadioButtonList ID="ASPxRadioButtonList1" runat="server" ClientIDMode="AutoID"
RepeatLayout="Flow" RepeatDirection="Horizontal" Value='<%# Eval("Regulation.AnswerRegulation[0].Answer") %>'
ValueType="System.String">
<ClientSideEvents SelectedIndexChanged="function(s, e) {Panel.PerformCallback();}" />
<Items>
<dxe:ListEditItem Text='Yes' Value="Yes" />
<dxe:ListEditItem Text='No' Value="No" />
<dxe:ListEditItem Text='Unsure' Value="Unsure" />
</Items>
</dxe:ASPxRadioButtonList>
</DataItemTemplate>
</dxwgv:GridViewDataTextColumn>
I can save the dataSource in the callback handler, but the new Answer value hasn't been set yet. Also, in order to save the Linq datasource I need to make it an EntityCollection, which immediately refreshes it from database.
I need to make sure that when the user changes the radiogroup value, that the new value is updated in the datasource. Now I am lost again. In this next code, I am forcing the Answer to be changed to an arbitrary value for now. Unfortunately, that means working out what changed on every callback and manually updating the datasource. This isn't very efficient and I was expecting to simply save the object and let LLB work this out.
protected void Panel_Callback(object source, CallbackEventArgsBase e)
{
model.SaveData(GridDataSource);
}
public void SaveData(object gridDataSource)
{
var regulationTextEntities = ((IQueryable<RegulationTextEntity>)gridDataSource).AsEntityCollection();
// force to Unsure for now as I haven't worked out how to get this correct value for the Answer yet. I need to eliminate this explicit update code somehow
regulationTextEntities[0].Regulation.AnswerRegulation[0].Answer = "Unsure";
modelAdapter.SaveEntityCollection(regulationTextEntities, true, true);
}
Joined: 02-Nov-2007
I have worked more on this and I think the Eval(entity.entity.property) syntax is good for read-only databinding, now that I have removed the Browsable(false) attribute from the related entities everywhere.
I think I have 2 choices for updates:
1 Use Custom Properties. This certainly looks like a good way to do things for one-off situations. I will test it, but I have confidence it will work as I expect and remove the need for complicated update code in the application. Thanks to you guys for reminding me of Custom Properties.
2 Re-architect the user interface. It looks like we have painted ourselves into a corner by designing a very tight set of controls into a single interface fed by a single data source where fields are needed from vary parts of the object-graph. I think the user interface needs to be separated out so the read-only sections uses one data source and the update sections use their own datasources that can be automatically updated. It is rare that multiple entities really have to be updated on the same callback.
I'll work on both these approaches and see if I can get a good resolution to this.
neilx wrote:
I have worked more on this and I think the Eval(entity.entity.property) syntax is good for read-only databinding...
That's correct. Eval is for one-way, Bind is for two-way (http://msdn.microsoft.com/en-us/library/system.web.ui.ibindabletemplate.aspx).
neilx wrote:
I think I have 2 choices for updates:
1 Use Custom Properties. This certainly looks like a good way to do things for one-off situations. I will test it, but I have confidence it will work as I expect and remove the need for complicated update code in the application. Thanks to you guys for reminding me of Custom Properties.
Yes. The Custom Property in this case is the best choice, specially when you want to set a value in just one entity of the related collection (_Regulation.AnswerRegulation[0].Answer_).
neilx wrote:
I'll work on both these approaches and see if I can get a good resolution to this.
Good. Please come back here when you decide what option to go for, or simply if you need further help.
Joined: 02-Nov-2007
Custom Properties work fine thanks. The only thing I need to do in the "get" is to ensure the entities in the graph aren't null, otherwise an exception is thrown. i.e. in my example both the Regulation and Regulation.AnswerRegulation entities need to be checked for null.
btw, that is one reason the dot syntax we dicussed above isn't ideal as it doesn't do these checks.
I tried FirstOrDefault(), but then saw in the LLB 2.6 documentation that that isn't implemented. Is it implemented in v3.0? That would help with Eval and the dot syntax for withpath entity data.
neilx wrote:
I tried FirstOrDefault(), but then saw in the LLB 2.6 documentation that that isn't implemented. Is it implemented in v3.0? That would help with Eval and the dot syntax for withpath entity data.
Are you using v2.6? LLLGen collections implements IQueryable, so it implements FirstOfDefault, however the default could be null, do you know what I mean?
Joined: 02-Nov-2007
Yes. 2.6.
I think this dot syntax for Evaluate and Bind really needs the null object pattern on related collections so null exceptions are avoided. I can't see how to use it declaratively in bound fields otherwise as it will always throw an exception with a null collection.
I'm a bit lost about what exactly the issue is currently, please help me with this -> Prefetch paths issue, that's sorted, correct?
FirstOrDefault is implemented, I don't know why you thought it wasn't? (it works similar to First(), and will return null if the entity wasn't found).
To avoid null's, test for them, there's no other way around it, I'm afraid.
Joined: 02-Nov-2007
Otis wrote:
I'm a bit lost about what exactly the issue is currently, please help me with this
-> Prefetch paths issue, that's sorted, correct?
Bottom line: I think I can solve our problems by using specialized custom property implementations. Below is a fuller description of what is happening.
Otis wrote:
FirstOrDefault is implemented, I don't know why you thought it wasn't? (it works similar to First(), and will return null if the entity wasn't found).
FirstOrDefault() "FirstOrDefault. This method has the same result as First: the 'Default' part isn't implemented: if there is no result, you won't receive any result but null. If First is called on a set which is empty, null is returned "
So I can use it but it returns null. I think I might have been looking for DefaultIfEmpty(T) where I could put a null object that would avoid exceptions. I'll try it, but I don't think this is necessarily a problem for me if my custom properties implementation handles nulls.
Otis wrote:
To avoid null's, test for them, there's no other way around it, I'm afraid.
That is why I think I can't effectively use the Eval syntax to get properties from prefetch or subpaths of an entity. Null Exceptions at runtime will be a recurring problem.
The wish is to use EntityCollections with prefetch paths in LLBLGenProdataSource2s in conjunction with a grid. This is especially where the data is modified as the LLB DS kindly manages it all for us and lets us do a one line commit. We use ASPxGridView, but I think that is immaterial.
So the options are:
- project to a flat plain class which needs lots of work to commit to the database
- use Eval("Entity.PrefetchPath.property") which I haven't tried to commit yet
- Create Custom Properties on the Entity which commit just fine with the right implementation of the custom property setter
So in a write situation, in order to use e.UoW.Commit(adapter) we can use Custom Properties. I haven't tried the Eval approach in this situation (yet) as I doubt it will work for the reasons below and maybe others.
Custom Properties seem easy enough except when the related entity is null. Then the designer for the grid breaks as it gets an exception when checking the property. Commits also fail as there is no collection to update, hence the need implement this in the setter. I think I can fix this by modifying my custom properties to handle this situation and am currently working on that approach.
Using the Eval approach above throws an exception at runtime when the .PrefetchPath or SubPath is null. This means I can only use it if related entities return a Null Object (i.e. an entity with default data only) when there is no data to return. I think, as you point out, this is a non-starter.
Therefore, I am moving ahead with Custom properties for both read and write needs for databound controls. This eliminates the need to create a projection in most cases where all we want is a flat set of properties for the binding.
I am also investigating how to use the PrefetchPathToUse property on the LLB DS. Once I understand what it does, this might give me a better way to architect our views using many more databound controls, each of which bind to a specific prefetch path. That sounds pretty wonderful to me if that is what it lets me do. (UPDATE: This is nonsense. Sorry. I don't think PrefetchPathToUse is useful when you have already added them in the performSelect handler)
Joined: 28-Dec-2010
Hi All,
I am sorry to back so late. I am often be late by busy work and forget to give out feeback asap.
I explain neilx what he want to do use llblgenpro prefetch. I did a successful expirement on Northwind.
View code:
<div> <dxwgv:ASPxGridView ID="ProductsDetails" AutoGenerateColumns="false" runat="server" DataSourceID="productsDS" KeyFieldName="ProductId"> <Columns> <dxwgv:GridViewDataColumn FieldName="ProductId"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="ProductName"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="SupplierId"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="CompanyName"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="Address"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="Phone"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="OrderDetailsUnitPrice"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="Quantity"></dxwgv:GridViewDataColumn> <dxwgv:GridViewDataColumn FieldName="Discount"></dxwgv:GridViewDataColumn> <dxwgv:GridViewCommandColumn> <EditButton Visible="true"></EditButton> </dxwgv:GridViewCommandColumn> </Columns> <SettingsEditing Mode="Inline" /> </dxwgv:ASPxGridView> <llblgenpro:LLBLGenProDataSource2 ID="productsDS" runat="server" AdapterTypeName="DevExpress.Adapter.DatabaseSpecific.DataAccessAdapter, DevExpress.Adapter" CacheLocation="Session" DataContainerType="EntityCollection" EntityFactoryTypeName="DevExpress.Adapter.FactoryClasses.ProductsEntityFactory, DevExpress.Adapter" EnablePaging="True" LivePersistence="true"> <SelectParameters> <asp:P arameter Type="Int32" Name="OrderId" DefaultValue="10248" /> <asp:P arameter Type="Int32" Name="ProductId" DefaultValue="11" /> </SelectParameters> <UpdateParameters> <asp:P arameter Type="Int32" Name="OrderId" DefaultValue="10248" /> <asp:P arameter Type="Int32" Name="ProductId" DefaultValue="11" /> </UpdateParameters> </llblgenpro:LLBLGenProDataSource2> </div>View BackGround Code protected void Page_Load(object sender, EventArgs e) { IPrefetchPath2 path = new PrefetchPath2((int)EntityType.ProductsEntity); path.Add(ProductsEntity.PrefetchPathSuppliers); path.Add(ProductsEntity.PrefetchPathOrderDetails); productsDS.PrefetchPathToUse = path; }
ProductsEntity CustomProperties code namespace DevExpress.Adapter.EntityClasses { public partial class ProductsEntity { public string CompanyName { get { return HasNoSuppliers() ? null : Suppliers.CompanyName; } set { Suppliers.CompanyName = value; } }
public string Address
{
get { return HasNoSuppliers() ? null : Suppliers.Address; }
set { Suppliers.Address = value; }
}
public string Phone
{
get { return HasNoSuppliers() ? null : Suppliers.Phone; }
set { Suppliers.Phone = value; }
}
public decimal OrderDetailsUnitPrice
{
get { return HasOrderDetails() ? decimal.Zero : OrderDetails[0].UnitPrice; }
set { OrderDetails[0].UnitPrice = value; }
}
public short Quantity
{
get { return HasOrderDetails() ? short.MinValue : Convert.ToInt16(OrderDetails[0].Quantity); }
set { OrderDetails[0].Quantity = value; }
}
public float Discount
{
get { return HasOrderDetails() ? 0 : OrderDetails[0].Discount; }
set { OrderDetails[0].Discount = value; }
}
private bool HasNoSuppliers()
{
if (Suppliers == null)
return true;
else
{
return false;
}
}
private bool HasOrderDetails()
{
if (OrderDetails == null)
{
return true;
}
if (OrderDetails.Count() == 0)
{
return true;
}
return OrderDetails.Count() == 0;
}
}
}
Then my test result
How superful function of llblgenpro !!! "ProductsEntity" pretech "SuppliersEntity" and "OrderDetailsEntity", and there is so less code to present 3 tables fields. I can upate any fields of 3 entities or tables at one time. In "old period", we often use objectdatasource and many customersizing code to implement to view or update informtation of mutli -tables.
LLBLGenProDataSource2 prodcutDS, seemingly, only responsible for ProductsEntity. But the prefetch change everything. productDS can deal with ProductsEntity, SuppliersEntity and OrderDetailsEntity.
Now, I think we have been left from dark and see dawn from now on. You discussion help me so much. Thank you very much.
Hi neilx, How do you think? My solution colse to your expectation, is it right?