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


Author
Message
Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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

Trent Taylor
Trent Taylor
StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)StrataFrame Developer (10K reputation)
Group: StrataFrame Developers
Posts: 6.6K, Visits: 6.9K
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
Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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?

Greg McGuffey
Greg McGuffey
Strategic Support Team Member (3.4K reputation)
Group: Forum Members
Posts: 2K, Visits: 6.6K
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.
Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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


Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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.

Peter Jones
Peter Jones
Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)Advanced StrataFrame User (504 reputation)
Group: Forum Members
Posts: 386, Visits: 2.1K

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

Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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

Greg McGuffey
Greg McGuffey
Strategic Support Team Member (3.4K reputation)
Group: Forum Members
Posts: 2K, Visits: 6.6K
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!
Bill Cunnien
Bill Cunnien
StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)StrataFrame VIP (1.1K reputation)
Group: Forum Members
Posts: 785, Visits: 3.6K
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


GO

Merge Selected

Merge into selected topic...



Merge into merge target...



Merge into a specific topic ID...




Similar Topics

Reading This Topic

Login

Explore
Messages
Mentions
Search