StrataFrame Forum

How do I filter a business object by a custom property?

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

By Bill Cunnien - 1/28/2008

When I attempt to filter a BO on a custom property, the debugger tells that the specified column does not exist.  It is true that it cannot be found in the DB, but it is on the BO.  Is there a trick to filtering on a custom property?

Thanks,
Bill

By Trent L. Taylor - 1/28/2008

You cannot filter on a custom property as this property does not exist in the CurrentDataTable.  The Filter and Sort properties just pass this on to the CurrentDataTable.DefaultView (or MyBO.CurrentView).  So if the DataView cannot see the field it will give you this error.  You have to filter or sort on a logical field.  Hope that helps Smile
By Bill Cunnien - 1/28/2008

Trent L. Taylor (01/28/2008)
You cannot filter on a custom property

Not good.  In another post today, I explored the issue of limiting my data being presented to the user because of bandwidth constraints. One way for me to do this easily would be to simply set a filter on one of the custom properties of the BO.  Since I cannot do this, my problem has become gargantuan.  Since the property is there, there must be a way to do this kind of filtering.  I cannot give up, yet.

Let me make sure I get this....if I had a customer business object with a really cool custom property like TotalOrders, it is impossible for me using that custom property to display all customers with more than 100 orders.  Is that right?

By Greg McGuffey - 1/28/2008

Let me make sure I get this....if I had a customer business object with a really cool custom property like TotalOrders, it is impossible for me using that custom property to display all customers with more than 100 orders. Is that right?




It depends... BigGrin It really has nothing to do with the custom property, but everything to do with the columns in the underlying DataTable (CurrentDataTable). This is controlled by the Fill method used (i.e. by the SQL statement or sproc used to fill the DataTable). The DataTable is not limited to the columns defined as strongly typed properties of the BO...it can have extra columns if needed (it also doesn't have to have all columns needed by the strongly typed properties either, you can leave some out too). If you have a custom property that gets its data from an extra column that you loaded into the DataTable, then you could appear to use that custom property to filter the DataTable.



However, what you seem to desire is a way to limit the data retrieved by the user from the database. In this case you don't actually want to use the BO.Filter. It limits what is shown to the user after all the data has been retrieved (disconnected data model of .NET). So, instead you have to limit the data retrieved within a Fill method.



To limit the data retrieved, you provide Fill methods that do filtering for you, via a SQL statement or Sproc. In the case of filtering on the TotalOrders, you'd need a Fill method that accepted the criterion (and optionally the comparison operator) and build the SQL statement/command. This can get exciting if you have lots of possible filtering fields/options. I use this extensively as my users are all remote, so network latency is a big issue.



If the data retrieved isn't really the issue, then you can use the BO filter options, along with a custom property by using a Fill method that returns the extra columns, then the BO's Filter will work. I.e. your fill SQL might look like this:

Select

CustomerID

,CustomerName

,(Select Count(*)

From Orders

Where o.CustomerID = Customers.CustomerID) As TotalOrders

From Customers




Then, have your custom property use the data in the data table...

return (integer)this.CurrentRow("TotalOrders")




Now, if I'm correct, setting the Filter of the BO to "TotalOrders>100" should work. However, this does not limit the data on the wire. All the data is pulled and it only filters what the user sees. So, if there are 10,000 customers, but only 10 with TotalOrders > 100, this pulls 10,000 from the database and then user can choose to see only 10 of them.
By Bill Cunnien - 1/28/2008

Greg McGuffey (01/28/2008)
It really has nothing to do with the custom property, but everything to do with the columns in the underlying DataTable

I am starting to see that.  Nevertheless, I am still quite confused about the custom property thing.  To me, the business object is an ideal place for some of these things.  The database is not the best location.  You and Trent are suggesting that I need to relocate the business logic back into the database.

Here is an example of some custom properties on the Parts business object that I have (declarations/tags/comments removed for brevity):

public int OnWorkOrder
{
   
get
   
{
       
string mPartindex = this.partindex.ToString();
       
return (int)ExecuteScalar("spx_GetAmountOnWorkOrder '" + mPartindex + "'");
    }
}

public int OnSalesOrder
{
   
get
   
{
       
string mPartindex = this.partindex.ToString();
       
return (int)ExecuteScalar("spx_GetAmountOnSalesOrder '" + mPartindex + "'");
    }
}

public int RequiredQty
{
   
get
   
{
       
int mReqQty = (this.minstocklevel - this.onhandqty) + (this.OnSalesOrder - this.OnWorkOrder);
       
if (mReqQty < 0) mReqQty = 0;
       
return mReqQty;
    }
}

As you can see, the logic becomes rather complex.  Easily handled in the business object.  The stored procedure that would handle this same stuff would be atrocious...doable, but a near nightmare to construct.  I really do not want to put all this back into the DB.  If I do, then these custom properties become unneeded.  In fact, I cannot see a good use for custom properties, since all of this can be done in the DB.

Bill

By Bill Cunnien - 1/28/2008

A custom property shows up in the field list when creating a browse dialog.  I receive the following error when trying to execute a search on a custom field:

ArgumentException
  An item with the same key has already been added.

Source     : mscorlib

Stack Trace:
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at MicroFour.StrataFrame.UI.Windows.Forms.BrowseDialogSearchingEventArgs..ctor(BrowseDialog Browse, WhereStatementCollection WhereStatements)
   at MicroFour.StrataFrame.UI.Windows.Forms.BrowseDialogwindow.ExecuteSearch()
   at MicroFour.StrataFrame.UI.Windows.Forms.BrowseDialogwindow.tsiSearch_Click(Object sender, EventArgs e)
   at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
   at System.Windows.Forms.ToolStripButton.OnClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
   at System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met)
   at System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met)
   at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
   at System.Windows.Forms.ToolStrip.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativewindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativewindow.WndProc(Message& m)
   at System.Windows.Forms.Nativewindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Thought I had something there...I was going to plop a browsedialog on the form and force the user to work through that (with the custom property set to not visible).  Not sure what that error means, but I assume it is saying, "Uh uh uh!".  That won't work, I guess.

By Peter Jones - 1/28/2008


Hi Bill,

While our apps are not 'net based I think our general approach could be useful to you:

1) We have a BO for a particualar table. In this exaple the PUN (Packing Unit) table.

2) This BO has number of methods that are used to retrieve data. In the example below it is calling a proc that returns data just for a specific Station Type (which isn't part of the PUN table) and if the PUN is in stock or not and the order in which we want to data to be returned. In our case stp_boPUN is very generic and extracts and sorts data in various ways depending what parameters are passed in

    ' Populate the BO with all in stock PUNs for a specific station type.
    Public Sub GetPUNInStock(ByVal StationType As String)

        Dim ProcParam(2) As System.Data.SqlClient.SqlParameter
        ProcParam(0) = New System.Data.SqlClient.SqlParameter
        With ProcParam(0)
            .ParameterName = "@INSStationType"
            .DbType = Data.DbType.String
            .Direction = Data.ParameterDirection.Input
            .Value = StationType
        End With
        ProcParam(1) = New System.Data.SqlClient.SqlParameter
        With ProcParam(1)
            .ParameterName = "@InStock"
            .DbType = Data.DbType.Boolean
            .Direction = Data.ParameterDirection.Input
            .Value = 1
        End With
        ProcParam(2) = New System.Data.SqlClient.SqlParameter
        With ProcParam(2)
            .ParameterName = "@OrderBy"
            .DbType = Data.DbType.Int16
            .Direction = Data.ParameterDirection.Input
            .Value = 1
        End With

        Dim cmd As New SqlCommand
        cmd.CommandType = Data.CommandType.StoredProcedure
        cmd.CommandText = "stp_boPUN"
        cmd.Parameters.AddRange(ProcParam)

        Me.TMSFillUsingCommand(cmd)
        cmd.Dispose()
    End Sub

3) Me.TMSFillUsingCommand(cmd) is executed to fill the PUN BO with just the data we want.

4) Me.TMSFillUsingCommand is just a wrapper we have for all our DAL code that does things like error handling / retries etc. The main point is that, in the end, it executes the standard SF fill method using a command string.

5) In our form we would execute Me.boPUN.GetPUNInStock(StationType) and that's it. If the BO was the basis for a grid (we use DevExpress) the grid is automatically refreshed with the new data set.

Bottom line is that we use specific stored procs to do the work and only return the data we want.

Cheers, Peter

By Bill Cunnien - 1/28/2008

Peter,

I really appreciate the response.  The approach you have outlined is quite good.  I would employ a similar approach if I am forced to utilize a bank of stored procedures to create every data property of my business object.  I shiver at the prospect.  Smile

I am valiantly trying to get a custom property to work in my favor.  Perhaps I am not handling the business object in the right fashion and expecting way too much from it.  It would be nice to not have to deal with the data layer when working with the existing business objects.  I am hoping that there is still a way to handle custom properties without resorting to stored procedures and views.

Thanks,
Bill

By Greg McGuffey - 1/28/2008

You and Trent are suggesting that I need to relocate the business logic back into the database.




I'm not exactly suggesting that and I doubt Trent is (but he'll have to speak up for himself of course).



The SF framework is very flexible. It doesn't force you to do much of anything...though there are many things that are much easier if you do it certain way. In this case, it is really a matter of what you need.



You can put all the logic in the BO. You just need to understand that the Filter property works with the underlying DataTable (er...DataView actually) of the BO. If the data isn't in that DataTable, it can't work. So, you have to get the data in there somehow. A bunch of ways to do that, including Fill methods that add the extra data. Or you could write code to add the columns, then set the data for each row. However, if you want the BO to filter or perform some other logical action (like calculating a total) on the records, it has to have all the data it needs. Which usually means more data over the wire. If this is OK, then great. If it isn't (as is my case)....



Then you need to get the DB to do some work for you. You could use sprocs, views or you can build the SQL in the app (which is putting the logic in the BO...but the work is on the db server). The db needs to filter the data for you. Generally, this will be faster than .NET code...db servers are optimized for set based actions (like select queries). Sprocs/views are going to be faster that SQL your app generates because there is already a query plan in place (it's compiled, sql style), so if speed is really important, I use those. Most of the time I just do SQL (using a SqlCommand object), until it obvious I need to do something else.



I'd like to address your posted code. First, you are using the db, when you call the sprocs in the OnWorkOrder and OnSalesOrder properties. If you just relocated this logic to the query you use to do the fill, which is defined in the BO, these column could then be within the data table. Likewise, the RequiredQty could be moved to the query also. It just a matter of were you want the work to be done and how many calls to the db you want. Currently, you'd have a call to fill the BO, a call each for OnWorkOrder and OnSalesOrder, plus another two calls for RequiredQty (it references OnWorkOrder and OnSalesOrder, so the db would get called again) for every row, whenever the row is accessed. This might be fine, depending on the connection of the client and how much data is being pulled across and how often the custom properties are being accessed. If you cached the custom properties after the first retrieval, you would reduce the db hits significantly. However, if you just let the DB do the work, you'd have one call and the values would already be cached. BigGrin Note, the logic to get the data is still in the BO if you're using SQL there, but the work is done on the db.



So, I'm not suggesting that you have to put your logic in any particular place. Rather, I'm trying to help you understand what you can do and why you might do it that way. I'm sure I've just touched on some of the things done by the various SF developers out there. In this particular case, you just need to understand that the Filter property works with data that is already on the client and it must be in the DataTable of the BO. If you do it this way, then any data needed for any calculated fields must be pulled before the calculations can happen. This could be slow if the client network connection is slow and/or there is a lot of data. Give these constraints, there are other ways to accomplish the same thing. I hope this is starting to make sense to you. If not, keep the posts coming! w00t






P.S. While writing this Peter posted a nice example of way that this can be done. I like the generic sproc approach, which is likely quite fast. I also like the wrapper around the fill to handle retries, error handling. As to the concern about sprocs for each custom property, you certainly don't need this. Here are some options:



- As you're doing, use a sproc

- Use SQL in the custom property (logic in BO).

- Use another BO...i.e. if you have a customers and an order BO and you want a custom property on the customers bo that indicates the total of outstanding orders, create a function in the order bo that does the work. This method could use a sproc or SQL to do the work

- Calculate result based on values in current row

- Calculate result based on values in other rows that are in BO currently, which could be the limited to the current view or the entire data table.

- Add caching, so you retrieve the value once, the cache it. Retrieve using favorite method (sproc, sql, another bo, walking exiting data in current bo)

- Mix and match for even more fun! BigGrin



Good luck!
By Bill Cunnien - 1/28/2008

Guys, can I do this?

foreach (DataRow dr in this.CurrentDataTable.Rows)
{
   
if (this.RequiredQty = 0)
    {
       
this.CurrentRow.Delete();
    }
}

I would run this immediately after I perform the FillDataTable method on the BO.  The Delete method would only delete the row from the CurrentDataTable...it wouldn't mark it for deletion and drop it from the database, right?

I could see an initial speed hit at the time of filling the BO, but not when the user is performing their own sorts, groupings and such on the corresponding DX datagrid.  Would this work?

Thanks,
Bill

By Bill Cunnien - 1/28/2008

Ah, Greg, much food for thought.  Thanks a ton for the explanation. 

The sp's in my code were placed in there for initial testing and I never removed them.  My goal was to replace them with calls to the corresponding business objects (which were not created at the time--they exist now). 

I am following you.  Really.  The thing that I am tripping up on is that once I give the DB the job of creating my custom properties (which are not custom properties to the business object anymore...they are actually columns in the remote data table) there remains little benefit to be derived from a custom property in the business object itself.

Well, getting late.  Time to go home.  Got an early and long day tomorrow.

Take care,
Bill

By Peter Jones - 1/28/2008

Hi Bill

if I am forced to utilize a bank of stored procedures to create every data property of my business object.

No need to do this - the proc can return whatever you want. I my example the StationType was just part of the WHERE clause but the value being checked was in an associated table not the PUN table itself.

In our PUN table for example we have quite a number of custom fields:

    Protected Overrides Function GetCustomBindablePropertyDescriptors() As MicroFour.StrataFrame.Business.FieldPropertyDescriptor()

        Dim boPUNType As System.Type = Me.GetType()

        Return New MicroFour.StrataFrame.Business.FieldPropertyDescriptor() { _
             New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "OperatorName", boPUNType), _
             New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "INIDescription", boPUNType), _
             New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "INIMaxHides", boPUNType), _
             New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "INIMaxWeight", boPUNType), _
             New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "INIMaxArea", boPUNType), _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "PKBCode", boPUNType), _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "ReceiveBatchCode", boPUNType), _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "FlesherBatchCode", boPUNType), _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "ShortTermBatchCode", boPUNType), _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor( _
            "TanningBatchCode", boPUNType)}
    End Function

#End Region

Our stored procedures return these values as needed. If we need to add more custom fields we can without breaking any existing code.

Regarding the other question - correct it wouldn't actually delete the rows until you did a BO.Save so you would want to carefull.

The best approach is not to return the data at all.

Cheers, Peter

By Charles R Hankey - 1/28/2008

Peter

Thanks so much for an very very useful example.  Coming from a VFP and Visual FoxExpress background I'm very used to thinking in terms of dynamic view parameters and only bringing as many records over the wire as absolutely necessary.  I'm very new to Strataframe and .NET in general and your code has helped a lot to translate some familiar concepts and give me a lot of ideas as to how I want to use the Strataframe framework.

Stuff like this always very much appreciated.  Smile

By Trent L. Taylor - 1/29/2008

You and Trent are suggesting that I need to relocate the business logic back into the database.

Actually, no.  There has been a lot of good dialog come from this post and this alone should be evident that there are many different ways to approach a problem.  First of all, if you create a custom property and want to sort on that property, there has to be an underlying column in the CurrentDataTable....you can, as you have seen from Greg and others, pull a faux field as part of your query....or in the CurrentDataRefilled event of your BO, you could progammatically add the columns into the CurrentDataTable so that you could filter on this as well:

CurrentDataTable.Columns.Add("MyAggregateField",GetType(Integer))

You could then, in your custom property, store off the value in the Set (or even the Get of the proper if it is readonly):

<Add standard attributes>
Public Readonly Property MyCustomProperty As Integer
   Get
      Me.Item("MyAggregateField") = 1 + 1
      Return CType(me.Item("MyAggregateField"), Integer)
   End Get
End Property

Taking this approach you could then sort and filter on the aggregate field.

By Bill Cunnien - 1/29/2008

Peter Jones (01/28/2008)
...we have quite a number of custom fields...

Thanks, Peter!  I will review your code a bit more to see if I may be missing something. 

Can a user search on these custom fields (e.g. OperatorName="Bill") using the BrowseDialog?  Or, could I use code to sort on one of these fields (e.g. myBO.Sort = "INIMaxWeight DESC")?

Regards,
Bill

By Trent L. Taylor - 1/29/2008

I could see an initial speed hit at the time of filling the BO, but not when the user is performing their own sorts, groupings and such on the corresponding DX datagrid.  Would this work?

Yes, but this will also make the BO dirty and if you do not want the table to be updated back at the server, you will have to AcceptChanges on the CurrentDataTable...otherwise this will be deleted from the server if a Save() is called:

MyBo.CurrentDataTable.AcceptChanges()
By Bill Cunnien - 1/29/2008

Trent L. Taylor (01/29/2008)
[quote]...you can, as you have seen from Greg and others, pull a faux field as part of your query...

Trent,

I have been known to be quite dense at times.  Sometimes saying something slightly differently fires certain synapses and things start clicking.  For me, it was the word "faux".  Thanks for pulling out the French.  English just wasn't getting through to me.

I will see what I can do with the creation of columns in an sp for the use with my custom properties.  Also, I need to spend some more time with Peter's code.  Looks like a good morning ahead.

Thanks, everyone, for your patience with me.
Bill

By Trent L. Taylor - 1/29/2008

Can a user search on these custom fields (e.g. OperatorName="Bill") using the BrowseDialog? 

No.  The BrowseDialog creates Dynamic queries that are placed against the Database on the server, not the BO.  The BO is not where the query takes place....if you think about it, if the BO were where the query occurred, you would never pull any records back from the server.  So in this instance you will never be able to query on a custom property (unless it has an underlying field on the server that can be queried).

By Trent L. Taylor - 1/29/2008

Thanks for pulling out the French.  English just wasn't getting through to me.

No problem BigGrin

By Greg McGuffey - 1/29/2008

Thanks for pulling out the French. English just wasn't getting through to me.




Ah, I shoulda used French (not that I know much Francaise...) Tongue



I'm glad the light bulb went off!



Bonne chance!
By Bill Cunnien - 6/25/2008

...or in the CurrentDataRefilled event of your BO, you could progammatically add the columns into the CurrentDataTable so that you could filter on this as well:

CurrentDataTable.Columns.Add("MyAggregateField",GetType(Integer))

You could then, in your custom property, store off the value in the Set (or even the Get of the proper if it is readonly):

<Add standard attributes>
Public Readonly Property MyCustomProperty As Integer
   Get
      Me.Item("MyAggregateField") = 1 + 1
      Return CType(me.Item("MyAggregateField"), Integer)
   End Get
End Property

Taking this approach you could then sort and filter on the aggregate field.

Took a while for me to readdress this issue.  I am hitting another brick wall.  I tried adding the column in the method.  If I run a search on the BO, the CurrentDataTable gets refilled again and the column is tried to be readded.  Pop!  Error.  So, I'll check for existence of the column before adding it.

Also, setting the aggregate field to the value of the custom property is tripping me up.  Your VB "Me.Item("MyAggregateField")" is not translating for me in my C# world.  Please let me know where I can access that collection(?). 

Thanks!!
Bill

By Trent L. Taylor - 6/25/2008

You would just want to reference the Item property of the BO or the Item property of the CurrentDataTable.  The example above is just talking to the BO default property which is the Item collection.  When dealing with a default property, you can access on the root of the object like this:

this["MyAggregateField"] = 1 + 1;

or you could talk straight to the current row:

this.CurrentRow["MyAggregateField"] = 1 + 1;
By Bill Cunnien - 6/25/2008

Thanks, Trent!

Man, I am getting tired...past midnight here.  Still struggling with this, though.  Looks like I will have to get back to this later.  Gotta sleep.

No Mountain Dew in the house.  Wink

I'll get back with you "tomorrow."
Bill

By Bill Cunnien - 6/26/2008

I added the code below into the CurrentDataTableRefilled method:

CurrentDataTable.Columns.Add("colReqQty", typeof(decimal));

I am checking for the existence before adding.  I then set a filter on the BO to something like this:

MyBO.Filter = "colReqQty > 0";

Nothing.  As a matter of fact, I am getting no value for my Custom Property, at all (named "spxRequiredQty").  Here is my code for the custom property:


public decimal spxRequiredQty
{
   
get
   
{
       
decimal mReqQty = (this.minstocklevel - this.onhandqty) + (this.spxSalesOrderQty - this.spxWorkOrderQty);
       
if (mReqQty < 0) mReqQty = 0;
       
this["colReqQty"] = mReqQty;
       
return mReqQty;
    }
}

I should be able to see a column called [colReqQty] in my BO; however, it does not show up.  For example, I cannot add it to a grid via a BBS.

At the moment, I am quite confused.

Blink

By Bill Cunnien - 6/26/2008

If I put a break on the first line of the code in the custom property, then run the app and open the window that uses the BO, the code NEVER hits this line:

decimal mReqQty = (this.minstocklevel - this.onhandqty) + (this.spxSalesOrderQty - this.spxWorkOrderQty);

What am I doing wrong?  Since the actual custom property is not getting "filled" then the value of my additional column will never get updated.  Therefore, my filter will not work.

This is just viscious! 

BigGrin  <--- see I am still smiling!!

Perhaps I did not get enough sleep last night.

By Greg McGuffey - 6/26/2008

Well, you need to have data in the colReqQty for the filter to work, but your only filling it if/when the custom property is being accessed. Time to rethink this, I'd think Wink



Here are some ideas (in no particular order):

- in your fill method, you could loop through the BO and fill the column by accessing the custom property

- use a method that would fill that column (after adding it) (rather than using the custom property).

- have SQL do the work and return that column



Just do this before you set the filter.



Hope this sparks some ideas (without starting a fire! Blink )
By Bill Cunnien - 6/26/2008

The fire is already crackling loudly under my feet.  I'll go through those ideas in no particular order.  Smile

Thanks a ton, Greg!
Bill

By Bill Cunnien - 6/26/2008

Fascinating.  Bewildering.  Frustrating.

There are three custom properties and one additional column (for the filter).  Prop3 = Prop2 - Prop1 (basically), then ColumnA = Prop3.  Filter ColumnA so that only values greater than 0 are shown.

in your fill method, you could loop through the BO and fill the column by accessing the custom property

Looping through the BO is very time consuming...several minutes to kerchunk each custom prop, then display the grid.  Not a good option.  In addition, the filter did not work even though the column has data!  It shows on the grid.  None of the custom properties show up on the grid...all blank still.

use a method that would fill that column (after adding it) (rather than using the custom property).

That seems to only affect the first row of the BO. 

Both of these last two options made the BO dirty.  Closing the window asked me if I want to save changes.

have SQL do the work and return that column

This is an option (and I have done this for several other BOs)...but, the complexity of the stored procs and UDFs would make the process so slow and painful that it just isn't fun anymore.  Placing the complex code inside of custom class and/or business object really, really makes things a LOT easier (cleaner and neater, too). 

Well, back to the drawing board.  And, in the immortal words of Bill the Cat:

ACK!

By Greg McGuffey - 6/26/2008

Some more thoughts (do I smell something burning?)....Tongue



The second option is basically the first, just using a method to do the walking and loading, rather than the custom property...probably won't be any good in your situation either. I'd also try to find out why it takes so long...maybe there is something you could do to speed it up. Unless the data set is very large and/or the computation very complicated, this should be fast.



For the third option, if you are using SQL Server 2005, consider a CLR sproc. You program in the .NET language of choice (C# for you I believe) and they tend to be fast. You likely could get a several minute process down to milliseconds. Search the forum for CLR stored procedures, there are some good posts about them by the SF team. While I agree that placing code in BOs is the way to go, when I need speed for a data intensive process, I use SQL Server. Now, sometimes, I can get the SQL Server joy AND put the code in the BO. That command object can accept any sort of SQL. I.e. you can do the work on SQL server but put the code (SQL script) in your BO. This won't be as fast as a sproc (the execution plan isn't precompiled and cached on SQL Server), but might do the trick.



If your custom properties aren't showing up, my first guess would be that you forgot to setup the custom property descriptors, but I'm still a newbie when it comes to grids so not sure if they are used in that case... Sad



Good luck and remember to wear your fire retardant pants! w00t
By Trent L. Taylor - 6/26/2008

Bill,

I think that you are trying to make this too complicated.  I think that I would first backup and then start rethinking through your process.

First, we use custom columns as aggregates OR create a custom property that dynamically returns aggregates a lot.  Now since you are trying to apply a filter, then you will most likely want a column in the data table.

OK.  Since we have that established, from what I can understand, you always want to have a calculated column within your data table.  So obviously if you perform a query, then you can do this as part of the query (easier and faster in some cases).  However, if you will have a scenario that will not retrieve data from a database query first, then you would need to ensure that the column gets manually added prior to being refrenced or accessed.  So in this case, I would do one of two things to ensure that the column exists within the BO.

  1. Override the OnAfterAddNew method of the BO and ensure that the desired columns get added within the BO and/or exist when a new record is added.  This way a query is not required, but when you add a new record the BO will ensure that the column exists.
  2. Override the OnCurrentDataTableRefilled method of the BO and call the same logic that is added as part of the OnAfterAddNew method.

By doing the above two things you will always be ensured that you have the column within your data table, thus allowing a filter to be applied.

At this point, you will just create the custom property on the BO to access it through the strong-typed reference, but you can then treat this column just as though you could any other column that comes from the database, however, it just resides within the BO.

By Trent L. Taylor - 6/26/2008

One other point that I saw in regards to your grid...if you do not have a custom property descriptor setup, it will not show up in a grid.  You will need to override the GetCustomBindablePropertyDescriptors method on the BO and provide a ReflectionPropertyDescriptor.
By Bill Cunnien - 6/26/2008

Here is my actual custom property code.  Please criticize at will.  I have no other code reviewer here.  There has got to be something fundamentally wrong with what I am doing so that none of the custom properties values are showing up in a DevEx XtraGrid (via BBS).  I have done this with several other BOs so the process is not new to me. 

From these custom properties, I would like to filter the grid on the spxRequiredQty value.  Somehow, I have to tell the underlying table of the BO that the column exists and what the data actually is (without resorting to stored procs and such). 

I know I am being difficult with this.  I have tried so many things that all of this is becoming a blur.  I have read through this thread several times, but just cannot see the light.  Thought I did a few times, but it was just that proverbial debug train coming at me through the tunnel of code exceptions.

Bill

P.S. Still using SQL Server 2000.  Blush

By Paul Chase - 6/26/2008

Bill,

Try taking the business binding source out of the equation, just throw a few text boxes on a form bound to your business object custom properties  fill it and navigate through your record set and see if it works.

The business binding source can create some serious migraine style headaches as it creates a new instance of the business object for each row so some things that have been set in the original BO are not longer "set" in the newly created bo.. Try it withough the BBS and see if your logic works. 

If it works without the BBS in the middle then you can play doctor with the BBS source code and create a BBS specific to the business object you are binding to.

Paul

By Ivan George Borges - 6/26/2008

Hey Bill.

Just in case it might help you see what Trent told you about adding a new field to your table and filtering on it, here is some code of mine where I do exactly the same, filtering on a field that doesn't exist on the underlying table.

On your BO:

    Private Sub MyBO_CurrentDataTableRefilled() Handles MyBase.CurrentDataTableRefilled
        If Me.CurrentDataTable.Columns.Contains("myField") = False Then
            Me.CurrentDataTable.Columns.Add("myField", System.Type.GetType("System.Boolean"))
        End If
    End Sub

    Private Sub MyBO_CurrentDataTableInitialized() Handles MyBase.CurrentDataTableInitialized
        If Me.CurrentDataTable.Columns.Contains("myField") = False Then
            Me.CurrentDataTable.Columns.Add("myField", System.Type.GetType("System.Boolean"))
        End If
    End Sub

And, of course, the property:

    <Browsable(False), _
     BusinessFieldDisplayInEditor(), _
     Description("My property"), _
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    Public Property [myFieldProperty]() As Boolean
        Get
            Dim loValue As Object
            loValue = Me.CurrentRow.Item("myField")
            If loValue Is DBNull.Value Then
                Return False
            Else
                Return CBool(Me.CurrentRow.Item("myField"))
            End If
        End Get
        Set(ByVal value As Boolean)
            Me.CurrentRow.Item("myField") = value
        End Set
    End Property


    Protected Overrides Function GetCustomBindablePropertyDescriptors() As MicroFour.StrataFrame.Business.FieldPropertyDescriptor()
        '-- Create and return a new array of FieldPropertyDescriptor
        ' objects that contains the ReflectionPropertyDescriptor
        ' for the cas_Idade field.
        Return New MicroFour.StrataFrame.Business.FieldPropertyDescriptor() { _
            New MicroFour.StrataFrame.Business.ReflectionPropertyDescriptor("myFieldProperty", GetType(MyBO))}
    End Function

So, now, on my logic, after setting my field depending on conditions:

        '-- applies the filter
        Me.MyBO1.Filter = "myField = True"

Hope it helps in some way...

By Bill Cunnien - 6/26/2008

With the textboxes, the values are showing up properly as I navigate through the BO.

The grid has a BBS datasource that points to the exact same BO.  All of the fields in the grid are blank.

(preparing to look for a towel to throw in)

By Bill Cunnien - 6/26/2008

Hi Ivan,

Thanks for the input.  The only real difference is that my properties are read-only; therefore, no set portion.  I have done exactly what you did...except I did not put the add column code in both methods of the BO (initialize and refilled).  I will try that.

Bill

By Paul Chase - 6/26/2008

That is good, if it work's without the BBS then you know it is a BBS issue and not a business object issue.

What happens when you use a BBS is that it creates a new instance of the busines object for every row. However it does not carry over any custom properties etc that are to the new instance.for instance if you have a property called foo in you business object and its value has been set to "Something" what the business binding source create a new instance of the Bo for that row the property foo's value with be nothing.

If that is what is happening then you can fix it by creating a BBS that is customized to your business object type. If you rthink this is where your problem is i can send you a copy of one I modified.

Paul

By Greg McGuffey - 6/26/2008

Bill,



You might try doing something real simply like just returning a constant, bypassing all the code in you properties to see if that works. i.e.

public decimal spxSalesOrderQty

{

get

{

// just skip the rest for now

return 1;



decimal mSOQty = 0;

...rest of code untouched

}

}





If this works, then it has something to do with the property code. If it doesn't, then is has to do with the properties themselves or how the grid is using those properties.
By Bill Cunnien - 6/26/2008

Paul:  I have a very similar setup on my InvoiceBO with the [invoicetotal] field.  It is a custom property and displays nicely on a grid via a BBS.  I am mimicking much of what I did there within this current BO.

Greg:  I returned only a constant and the value still are not showing up in the grid.  Disturbing.  For some reason, as Paul is alluding to, the BBS is not picking up these values and passing them to the grid.

Guys...thanks for all of the help.  I am hitting the 24 hour mark on this current problem.  Hopefully, it will get resolved soon. 

Btw, I have eliminated any column adding and filtering options...I need to get the data flowing first to the grid.  I will go from there once that is back in business.

By Trent L. Taylor - 6/26/2008

BILL, if you would read my previous post, there is ONLY one reason it would not show up in a grid through a BBS.  You are missing a property descriptor for the custom property...period!

You may be dealing with post and information overload, but this is not an overly complicated process or problem...when I run into something like this it is generally due to frustration and dealing with the same problem over a period of time.  There is a lot of good information in this thread...but first things first, I had posted explaining how to ensure the column always exists within the CurrentDataTable.  Secondly, I have told you the one and only reason why a custom property would not be visible through the BBS.  Please check and refer to these two points and I am sure that you will see where your problem is.

By Trent L. Taylor - 6/26/2008

Also, if you read Ivan's post, he has posted some code that shows an example of how to setup the basic structure of this custom property.  If you backup and start with his code, you are going to be a long way down the road.
By Bill Cunnien - 6/26/2008

The three columns (which are blank) are dragged from the available columns in the BBS to the layout of the grid.  All three are read-only custom properties that return a constant (3, 2, 1, respectively; no complex logic, at all, per Greg).   I have done nothing else.

You can see from my attached code earlier that I indeed have the proper ReflectionPropertyDescriptors set for each custom property.  Otherwise, these properties wouldn't even be seen by the grid design tool via the BBS.

I'll re-re-re-read everything, Trent.  I am not ignoring anyone's counsel.  I have acted on every part.

(I haven't found any towels, yet)
Bill

By Bill Cunnien - 6/26/2008

Trent L. Taylor (06/26/2008)
Also, if you read Ivan's post, he has posted some code that shows an example of how to setup the basic structure of this custom property.  If you backup and start with his code, you are going to be a long way down the road.

Were you able to review the code that I posted?  Other than the initialize and refilled code, I didn't think I was that far off.  Maybe I am.

At this point, I am just wanting a simple value to show up in the grid from a custom property.  Not even worried about filtering for now.

By Paul Chase - 6/26/2008

If your customer property work's fine with textbox's but not with a BBS then what does that tell you?
By Bill Cunnien - 6/26/2008

Paul Chase (06/26/2008)
If your customer property work's fine with textbox's but not with a BBS then what does that tell you?

I am focusing on the BBS, as mentioned in an earlier post (following Greg's suggestion to return a constant).  My biggest issue is why the code works for one grid and not another.  Both have BOs with custom properties flowing through a BBS to the grid.

I'll continue to grind it out on my own.  Apparently, I have worn y'all out with this one. 

Sorry about that,
Bill

By Ivan George Borges - 6/26/2008

Hey Bill.

Don't even know wether this would make a difference or not, but had a look at your code and couldn't see the "OnSalesOrder" property defined (which you create a Descriptor for)... I guess you just didn't post it, is that so?

By Bill Cunnien - 6/26/2008

Thanks for reviewing the code, Ivan.  Yup, just left it out, since I am replacing it with the newer properties that I shared with you.
By Bill Cunnien - 6/27/2008

I thought I would try to wrap up this thread with a play-by-play of what I did to solve the problem.  Thanks everyone for all of the help!


1)  Added code to the CurrentDataTableInitialized and CurrentDataTableRefilled (per Trent and Ivan)

if (CurrentDataTable.Columns["spxInventoryQty"] == null) { CurrentDataTable.Columns.Add(new DataColumn("spxInventoryQty", typeof(decimal))); }

2)  Altered the custom property to a basic structure (no reference to the new column)

[<snipped>]
public decimal InventoryQty
{
   
get
   
{
       
decimal mInvQty = 0;
       
// calculate the amount on hand (pending)
       
mInvQty = this.onhandqty; // use the invalid value for now (for testing)
       
return mInvQty;
    }
}

3)  Filled the BO that applies a similar filter to what I wanted in the BO (removed that part of the problem entirely)

4)  Set all new data column values to the existing value of the custom property (note: using the various code supplied in this thread, the best I could do was fill the first row with data from the custom properties...the rows' column data mostly stayed empty)

private void SetColumnValues()
{
   
foreach (PartsBO mBO in partsBO1.GetEnumerable())
    {
        partsBO1.CurrentDataTable.Rows[partsBO1.CurrentRowIndex][
"spxInventoryQty"] = partsBO1.InventoryQty;
    }
}

5)  Create a dataset from the CurrentDataTable(s) to be the data source for the DevEx Grid (necessary for the master/detail gridviews, BBS bypassed)

DataSet ds = new DataSet();
ds.Tables.Add(partsBO1.CurrentDataTable);
ds.Tables.Add(salesOrderDetailsBO1.CurrentDataTable);
ds.Relations.Add(
"SalesOrders", ds.Tables[0].Columns["partindex"], ds.Tables[1].Columns["partindex"], false);
PartsGridControl.DataSource = ds.Tables[0];
PartsGridControl.LevelTree.Nodes.Add(
"SalesOrders", SalesOrderView);
PartsGridControl.RefreshDataSource();


There are a couple of outstanding issues...the main one is that once I set a column's value in the CurrentDataTable the row is now dirty and the BO wants to save whenever I exit the window.  I will do some searching on how to prevent saves in this situation.  I am sure it has been covered in the docs or other forum entries.  The secondary one is that the lookup data in the related sales order grid (e.g. customer name, unit description, etc.) is always empty.  Since this is new water for me, I will do my due diligence and research the DevEx grid more throroughly and come up with a solution.

Thanks, again, for everyone's help!  Naturally, if anyone sees something that may help me improve things, feel free to let me know.

Have a great day!
Bill

By Ivan George Borges - 6/27/2008

Glad you got it going, Bill! Wink

There are a couple of outstanding issues...the main one is that once I set a column's value in the CurrentDataTable the row is now dirty and the BO wants to save whenever I exit the window.

Not sure this is the recommended way, I'm sure you will be adviced against it if not. Have you tryed issuing a SAVE right after you modifiy all your custom field values? This is what I'm doing, anyway! Smile

As this column is not part of your table, nothing will happen to it, I suppose.

By Bill Cunnien - 6/27/2008

Since the form's purpose is to display information only, I set the AutoShowSaveChangesMessage on the maintenance form to 'false.'  That took care of it.