StrataFrame Forum

BBS DataRelation <> Report Sharp Shooter

http://forum.strataframe.net/Topic20681.aspx

By Philipp Guntermann - 11/10/2008

Hi,

i am using the express version of report sharpshooter. i can create simple reports with no parent/child relations by just dropping a BBS onto the form and setting it up. Tho i dont see a possibility to set up a parent-child relationship between two bbs objects in a way that report sharpshooter understands.

any ideas ?

thanks.

By Trent L. Taylor - 11/11/2008

Yes, look at the StrataFlix sample.  It shows you how to do this and is the purpose of these samples.  You will see that the child BBS is exposed through the parent BO.  In the exposed child BO property on the parent BO, you will set the filter of the child through the exposed property versus setting a relationship like you do when the controls are dropped on a form.  Please refer to the StrataFlix sample as this was the intention of all of the report samples, to help show how to implement a BBS as well as the children BBS in a report environment.
By Philipp Guntermann - 11/11/2008

hi trent,

thanks for your reply. can you give me a hint where in the strataflix example i should be looking, since its a quite big example and i am kind of on a time contraint.

thanks.

By Dustin Taylor - 11/11/2008

The Movie Profile BO used by the Movie Profile Report is an example of this. The BO is at the following location within the solution:

StrataFlix.Reporting -> Reports -> Movie Profile Report -> MoviesBO.vb

If you edit the BO, you will see a code file matching the attatched image. Within it you can see several child BOs declared as privates and exposed using a custom property that filters on the current row of the parent (with an associated reflection property descriptor for each custom property.) I've highlighted the three pieces that expose the MovieCast child item in the attatched image.

If you then look at the MovieProfileDataSource.vb, you'll notice that the data source is inheriting off of MoviesBBS, and the FillMultipleDataTables statement is filling the data tables of the child BOs by way of the public properties that have been exposed within the source business object (MoviesBO).

By Dustin Taylor - 11/11/2008

Sorry, the first line of that reply should read "The MoviesBO used by the Movies Profile Report...".
By Philipp Guntermann - 11/11/2008

Thanks for the reply. i been looking at it now, but to be honest i dont get it.

I start off by adding 2 buiness objects to my project, say "OrdersBO" And "OrderPositionsBO".

I then build them through the Buisness Object Mapper.

Next i build the project and put an instance of each BO onto the form. Then I provide a fill method for the OrdersBO.

And then, what do i do to set them up correctly ?

What i did and what worked for non child/parent relating simple reports was to add a Buissness Binding Source to the form trough the Toolbox and set the BuisnessObject Property.

thanks.

By Trent L. Taylor - 11/11/2008

Next i build the project and put an instance of each BO onto the form. Then I provide a fill method for the OrdersBO. And then, what do i do to set them up correctly ?

As I tell my kids when they are running towards the street, "Errrck...Stop!" BigGrin

Creating the BOs that you use within your application is the first step.  But we recommend and have created a tool for reporting BOs.  You will want to create a Custom Business Binding Source (it is a template when you add a new item).  You will then want to select the BO you want to inherit from and create a BBS as well.  Be sure to change the namespace to match the report you are writing so that you do not have any ambiguous names.

Then you will use the CustomBBS classes that you created here within your report.  The StrataFlix sample shows this as well as the docs.  Look up the Custom BBS in the docs for more information as well.

By Philipp Guntermann - 11/11/2008

what you mean by "within your report" ? i have a report sharpshooter reportmanager control on the form. upon right-clicking that i choose "run editor" i then switch to the datasources tab and tell it the datasource.

?

By Ivan George Borges - 11/11/2008

Hey Philipp.

I went ahead and changed once more Trent's report sample... there are 2 reports being generated now. One simple just dropping BO and BBS on the form, as you were doing, and a Master-Detail one, following the StrataFlix concept. Have a look at them and see if it gets clearer to you.

By Philipp Guntermann - 11/11/2008

Hi,

thanks for providing that.

unfourtanatly i am currently at home where i dont have the full development environement and thus cannot look at it right now.

but i will study it tommorow at work.

By Philipp Guntermann - 12/7/2008

Hi,

i now made a little test project, but i am still not quite pulling this off.

This is how my project looks:

i believe the RechnungenBO.cs code is pretty correct now, but i cannot select the Datasource in the ReportManager:

I have uploaded my source code here:

http://www.ready3.net/ReportSS.zip

By Ivan George Borges - 12/7/2008

Hi Philipp.

You need to get an instance of your BBS onto your form. One way of doing it is to make your BBS show up in the toolbox and drop it on the form. For that, add the ToolboxItem attribute to your BBS code:

Then, rebuild your project, which will make your BBS appear in the toolbox. Drop it on your form and you will be able to choose it from the ReportManager Data Sources list:

By Philipp Guntermann - 12/7/2008

Hi Ivan,

thanks, that solved this issue. however, i am now having the issue, that the child-related information is not accessible within the report designer.

what i get is this:

But what i really WANT to get is something like this:

Any Idea ?

Thanks !!

By Ivan George Borges - 12/7/2008

Have you had a look at the sample I sent you last month?

http://forum.strataframe.net/FindPost20704.aspx

You will basically create a new property in your parent BO that will represent your child BBS:

By Philipp Guntermann - 12/7/2008

Yes, i did that, its in the RechnungenBO.cs:

#region Private Fields

private static RechpositionenBBSBBS _Rechpositionen = new RechpositionenBBSBBS();

#endregion

#region Protected Methods

protected override MicroFour.StrataFrame.Business.FieldPropertyDescriptor[] GetCustomBindablePropertyDescriptors()

{

//-- Establish Locals

List<FieldPropertyDescriptor> r = new List<FieldPropertyDescriptor>();

//-- Include all of the base descriptors

try

{

r.AddRange(base.GetCustomBindablePropertyDescriptors());

}

catch (Exception)

{

}

//-- Add the custom descriptors

r.Add(new ReflectionPropertyDescriptor("Rechpositionen", typeof(RechpositionenBO)));

//-- Return results

return r.ToArray();

}

#endregion

#region Public Propertiers

public RechpositionenBBSBBS Rechpositionen

{

get

{

//-- Filter out the child records that match the current movie record

if (this.Count > 0)

{

_Rechpositionen.SourceBO.Filter = "RechkopfID = " + this.ID.ToString();

}

//-- Return the child items

return _Rechpositionen;

}

}

#endregion

By Philipp Guntermann - 12/8/2008

i noticed i build my test project different from the samples, so i created a new one which is now in-line to how the samples were build:

The one with the red underline is where i put the custom property for Rechpositionen.

Now when i run the project, i get a null-reference exception at the line:

r.Add(new ReflectionPropertyDescriptor("Rechpositionen", typeof(RechpositionenBO)));

"The Object reference not set to an instance of an object."

 

I have uploaded the new source code here:

http://www.ready3.net/ReportSS2.zip

By Ivan George Borges - 12/8/2008

It looks like you forgot to override the AreCustomerDescriptorsEvaluated property:

    Private Shared _AreCustomDescriptorsEvaluated As Boolean = False

/// <summary>
/// Determine if the custom descriptors are going to be evaluated
/// </summary>
/// <remarks></remarks>
    protected override bool AreCustomDescriptorsEvaluated {
    get {
        //-- Establish Locals
        bool r = _AreCustomDescriptorsEvaluated;
       
        //-- Do not eval the desciptors again
        _AreCustomDescriptorsEvaluated = true;
       
        //-- Return results
        return r;
    }
}

By Philipp Guntermann - 12/8/2008

Ivan George Borges (12/08/2008)
It looks like you forgot to override the AreCustomerDescriptorsEvaluated property:

    Private Shared _AreCustomDescriptorsEvaluated As Boolean = False

/// <summary>
/// Determine if the custom descriptors are going to be evaluated
/// </summary>
/// <remarks></remarks>
    protected override bool AreCustomDescriptorsEvaluated {
    get {
        //-- Establish Locals
        bool r = _AreCustomDescriptorsEvaluated;
       
        //-- Do not eval the desciptors again
        _AreCustomDescriptorsEvaluated = true;
       
        //-- Return results
        return r;
    }
}

Actually i allready have that:

namespace ReportSS2.ReportBinding

{

public class RechnungenBO : ReportSS2.RechnungenBO

{

#region Private Fields

private static bool _AreCustomDescriptorsEvaluated = false;

private static RechpositionenBBS _Rechpositionen = new RechpositionenBBS();

#endregion

#region Protected Methods

/// <summary>

/// Determine if the custom descriptors are going to be evaluated

/// </summary>

/// <remarks></remarks>

protected override bool AreCustomDescriptorsEvaluated

{

get

{

//-- Establish Locals

bool r = _AreCustomDescriptorsEvaluated;

//-- Do not eval the desciptors again

_AreCustomDescriptorsEvaluated = true;

//-- Return results

return r;

}

}

By Ivan George Borges - 12/8/2008

Hi there.

I'm facing some difficulties over here, since I don't speak neither German nor C#. Hehe

But I got to make your project work. Had to delete the instance of your RechungenBBS from the form, as there was some error there, I guess with namespaces or such. Also repaired some code and inheritance of BO, and it looks like working now:

By Philipp Guntermann - 12/8/2008

Hi Ivan,

first off, thanks alot, this looks as intended. Can u provide some details of what you actually changed ? For example how you were able to rename RechnungenBO.cs to RechnungenReportBO.cs without breaking everything ?

I will try to create it from scratch again. I really need to know the way of getting there.

I used CustomBuisnessBindingSource to create the BBS and BO('s) within the ReportBinding namespace.

Thanks alot for your help.

By Trent L. Taylor - 12/8/2008

WOW...long post.  I will let Ivan respond.  Let me know if you have any other issues. Wink
By Philipp Guntermann - 12/8/2008

ok, i got it working now by building it from scratch again and comparing to your corrected code.

Not sure tho, if i fully understand the procedure. And since i'll have to explain that to someone in future, i'am trying to summarize:

1) Creation of a "ParentBO" and a "ChildBO". For convenience put in a Namespace called "BuisnessObjects".

2) Mapping of the BO's in the BOM.

3) Creation of a BuissnessBindingSource for each BO using the CustomBuisnessBindingSource, for convenience put in a Namespace called "ReportBinding" or something like that.

4) Creation of a BuisnessObject based upon ParentBO but with a different Name (for example: "ParentReportBO" using the CustomBuisnessBindingSource put in the "ReportBinding" Namespace.

5) Correcting the Name in "ParentReportBO", because the CustomBuisnessBindingSource created code by default puts the name of "ParentBO".

6) Changing of ParentBBS so it inherits from "ParentReportBO" instead of "ParentBO". Also adding

[System.ComponentModel.ToolboxItem(true)], so it appears within the toolbox.

7) Adding the Custom Property to expose "ChildBBS" to "ParentReportBO" and adding a 'Reflection Property" for "ChildBBS", for some reason (that i dont really get) of type "ParentReportBO" instead of "ChildBO".

8) Making sure "ChildBBS" is public.

9) Adding of the "ParentBBS" from the Toolbox to the form and using that as source for the ReportManager.

By Ivan George Borges - 12/8/2008

You're welcome Philipp, great you got it working! Cool

Your steps sound good. Creating an XYZReportBO is just for convinience, so if you need to add properties or make any customization exclusive for the report's sake, you won't be messing with your original BO.

As for the Reflection Property Descriptor, you are adding a property descriptor to your parent BO, so that is why you will need to make it a typeof(YourCurrentParentBO).

You might be confusing it because the property in question is of the type of your ChildBO, but it could be an Integer, a Boolean or whatever, and when you added the custom descriptor, you would still add the descriptor as the type of YourCurrentParentBO.

If I mixed all up, I'm sure SuperMan will jump in and make things right. BigGrin

By Edhy Rijo - 12/8/2008

Ivan George Borges (12/08/2008)
If I mixed all up, I'm sure SuperMan will jump in and make things right. BigGrin

Hummm, guess you meant SuperTrent Smile

By Ivan George Borges - 12/8/2008

Hey, don't give up his secret identity!!! Smooooth
By Trent L. Taylor - 12/9/2008

LOL....you guys need to tell my WIFE that BigGrin  When I am sitting on the couch watching football I don't get the same kudos w00t

Let me know if are still stuck, Phillip. Smile

By Philipp Guntermann - 12/11/2008

Sorry, but i need to warm up this topic again. Kinda saw it comming tho Smile

Since the structural part is now in place, i need to actually fill the BO's. From the StrataFlix Sample i got the impression that i need to use FillMultipleDataTables for this purpose.

In my application i have a list of double which are the primary keys of the ParentBO datasets.

So i could do something like this:

string SQLRechkopf, SQLRechpos;

SQLRechkopf = "SELECT * FROM Rechkopf WHERE 1=1";

foreach (double _ix in _indexes)

{

 SQLRechkopf += " AND (ID = " + _ix.ToString() + ")";

}

SQLRechpos = "SELECT * FROM Rechpositionen WHERE 1=1";

foreach (double _ix in _indexes)

{

 SQLRechpos += " AND (RechkopfID = " + _ix.ToString() + ")";

}

System.Data.Common.DbCommand dbc = new System.Data.SqlClient.SqlCommand();

dbc.CommandText = SQLRechkopf + ";" + SQLRechpos;

and then

MicroFour.StrataFrame.Business.BusinessLayer.FillMultipleDataTables(dbc,ParentBO,ChildBO);

i have no idea where to put this code tho (in the ParentReportBO, in the BuisnessObjects.ParentBO, in any of the BBS ???). Also putting ParentBO and ChildBO in FillMultipleDataTables doesnt seem to work either.

 

By Philipp Guntermann - 12/15/2008

got it working now Hehe

tho what i dont like is, that the child bo holds all records from all parentbo datasets. could become an issue when working with

alot of data. is there a possible approach to have the childbo only hold the records relative to the parent bo at any time ?

i am not sure wether report sharp shooter acutally communicates back to the bo, so that one could use the event when the current row changes to 'dynamically' fill the childbo with only the relevant data ?

By Ivan George Borges - 12/15/2008

Hi Philipp.

Glad you got it working.

is there a possible approach to have the childbo only hold the records relative to the parent bo at any time ?

Have you tried filling your child BO by the Parent pk, for example:

_CustomerNotes.SourceBO.FillByParentPrimaryKey(cust_pk)

instead of filtering by the current parent?

By Edhy Rijo - 12/15/2008

Philipp Guntermann (12/15/2008)
is there a possible approach to have the childbo only hold the records relative to the parent bo at any time ?

Hi Philipp,

I handle that scenario by creating an SQL statement that will allow me to get the child records for the selected parent PK.  Here is one of my method for a Service Call Profile report:

''' <summary>

''' Retrieves all of the recods that will be used within the Service Call Profile Report (rptServiceCallProfile.rst)

''' </summary>

''' <param name="PK_ServiceCalls"></param>

''' <param name="includeServiceCallRecord"></param>

''' <returns></returns>

''' <remarks></remarks>

Public Shared Function ServiceCallProfileReport_AllData(ByVal PK_ServiceCalls As Integer, ByVal includeServiceCallRecord As Boolean) As SqlCommand

     '-- Establish Locals

     Dim cmd As New SqlCommand()

     '-- Determine if the Service Call record will be included

     If includeServiceCallRecord Then

          cmd.CommandText = "SELECT dbo.ServiceCalls.* FROM dbo.ServiceCalls WHERE dbo.ServiceCalls.PK_ServiceCalls = @PK_ServiceCalls;"

     End If

     '-- Add the Service Call Appliances query for the selected Service Call PK, ordered by the Appliance Service Date.

     cmd.CommandText &= "SELECT dbo.SC_Appliances.* " & _

                                    "FROM dbo.SC_Appliances " & _

                                    "LEFT JOIN dbo.ServiceCalls ON (dbo.ServiceCalls.PK_ServiceCalls = dbo.SC_Appliances.FK_ServiceCalls) " & _

                                    "WHERE (dbo.ServiceCalls.PK_ServiceCalls = @PK_ServiceCalls)" & _

                                    "ORDER BY dbo.SC_Appliances.ServicedDate;"

     '-- Add the Service Call Appliance items for the selected service all PK.

     cmd.CommandText &= "SELECT dbo.SC_AppliancesItems.* FROM dbo.SC_AppliancesItems " & _

                                    "LEFT JOIN dbo.SC_Appliances ON (dbo.SC_AppliancesItems.FK_SC_Appliances = dbo.SC_Appliances.PK_SC_Appliances) " & _

                                    "LEFT JOIN dbo.ServiceCalls ON (dbo.SC_Appliances.FK_ServiceCalls = dbo.ServiceCalls.PK_ServiceCalls) " & _

                                    "WHERE (dbo.ServiceCalls.PK_ServiceCalls = @PK_ServiceCalls)" & _

                                    "ORDER BY dbo.SC_AppliancesItems.ItemType, dbo.SC_AppliancesItems.ServicedDate;"

     '-- Create the parms

     cmd.Parameters.AddWithValue("@PK_ServiceCalls", PK_ServiceCalls).SqlDbType = SqlDbType.Int

     '-- Return the results

     Return cmd

End Function

Hope this help you out!

By Dustin Taylor - 12/15/2008

Yep, report sharp shooter references the BO whenever it access a property, so rather than loading all child records up front, you can put a query in your reporting BO to pull only the currently relevant child records, effectively streaming the report. However, this doesn't really save you any time since it makes a bunch of small trips that add up to more load on the server as a single large query would.

We find that the bottleneck when it comes responsiveness on large reports is in the report rendering engine rather than data retrieval. We can print reports with hundreds of pages of data, and the query will typically spit the full result sets back in less than a second. On exteremely long reports (several hundred pages or more) with complex queries (a lot of joins and conditions), it might take 5-10 seconds to recieve the SQL results at the most. It is the nature of the beast in reporting, which is why you typically use criteria with logical boundries to avoid exteremly large record sets. Large record sets tend bloat your report, stretch out the time to generate that report, and make the final product more unweildy for the end user.

By Dustin Taylor - 12/15/2008

After reading Edhy and Ivan's reply, I realise I missed the point somewhat. I didn't realise you were pulling ALL child records, not just those applying to all included parents. Yes, by all means, attack that first to reduce the size of your record set. You should only be pulling back data that you are actually going to print. In our medical app, we go so far as to only pull back the columns that we know we are going to print, to reduce the footprint of the recordset coming back.

So ignore the above, and do what Edhy and Ivan said BigGrin

By Edhy Rijo - 12/15/2008

Dustin Taylor (12/15/2008)
...we go so far as to only pull back the columns that we know we are going to print, to reduce the footprint of the recordset coming back.

Good point Dustin, I am not doing that yet, but will add it to my list to review, as well as convert those to use a Store Procedure instead.

By Philipp Guntermann - 12/15/2008

Hi, no i am not loading all child records. i am loading all child records from the parent records that are in the parentbo. in the application, a user can checkmark certain invoices. thoose checked invoices are loaded into the parentbo, and then the childbo loads all child records from thoose records in the parentbo.
By Philipp Guntermann - 12/15/2008

Ivan George Borges (12/15/2008)
Hi Philipp.

Glad you got it working.

is there a possible approach to have the childbo only hold the records relative to the parent bo at any time ?

Have you tried filling your child BO by the Parent pk, for example:

_CustomerNotes.SourceBO.FillByParentPrimaryKey(cust_pk)

instead of filtering by the current parent?

hi ivan, if i use fillbyparentprimarykey as you suggest, will that automaticly update ? or would i have to put some code in that once the parentbo pk changes, it would call fillbyparentprimarykey on the childbo again to update ?

thanks

By Philipp Guntermann - 12/15/2008

on a side note, does the bo expose all properties to the report, or only thoose where i add a ReflectionPropertyDescriptor for ?
By Ivan George Borges - 12/15/2008

Well, I think I haven't understood what you mean. When you det your BOs ready for printing, I assume the next step would be to run the Report. How would anything change at this time?
By Philipp Guntermann - 12/15/2008

Ivan George Borges (12/15/2008)
Well, I think I haven't understood what you mean. When you det your BOs ready for printing, I assume the next step would be to run the Report. How would anything change at this time?

My report looks like this:

1stInvoiceTheUserChecked------------------------

INVOICE# and stuff...

INVOICE POSITION 1,

INVOICE POSITION 2...

2ndInvoiceTheUserChecked------------------------

INVOICE# and stuff...

INVOICE POSITION 1,

INVOICE POSITION 2,

INVOICE POSITION 3...

and so on.

So in my ParentBO i have all the invoices the user checked for appearance on the report. In the ChildBO -currently- i have all positions of thoose checked invoices.

Instead, what i would like is that when the repot engine renders the 2nd invoice, it would trigger something on the bo, so that the child bo only ever carries the thoose child records relative to the currently rendering invoice.

I got the impression that by using FillByParentPrimaryKey as you suggested instead of using FillMultipleDataTables and then set a filter, this would be possible. But for that to work, the childbo would need to update its records once the currently-being-rendered-invoice changes.

I hope that was not too terribly explained BigGrin

By Trent L. Taylor - 12/16/2008

Phillip,

You can do this and is just a design that you need to approach on your side.  I have mentioned out here on forum posts, especially when dealing with reporting type of scenarios, that you can really move time to the left if you will download and look at the StrataFlix sample.  It shows a great structure and format to creating reporting data sources.

Now in the StrataFlix sample, we populate all of the records for each BO at the same time and then filter out the child records through the exposed child BO property.  However, you can perform a query at this point instead of applying a filter.  So let's assume that you have something like this:

Public Class MyParentBO

    Public Readonly Property MyChildBO() As MyChildBOType
        Get
              '-- Populate the internal MyChildBO at this point
        End Get
    End Property
     

End Class

If you apply this logic using the layout and format of the StrataFlix reporting samples, then you should be right on track.

By Philipp Guntermann - 12/16/2008

Hello Trent,

i did download and look at the strataflix sample. As from my posts above you may see, that i actually implemented it just like in the strataflix sample. the question in regards to fillbyparentprimarykey was in reply to a suggestion from Ivan as how to approach this with the childbo only having the records relevant to the current parentbo record that is being rendered at that time.

By Trent L. Taylor - 12/16/2008

And my previous post covers that.  You can perform a query at the point of the child BO being referenced if you want to stream load instead of front load.