Filtered NVL

Filtered NVL

Old forum URL: forums.lhotka.net/forums/t/2347.aspx


TerryHolland posted on Thursday, February 15, 2007

I have a dropdown on one of my webforms that needs to display a list of CostCentres filtered by UserID (the logged on user id).  Could/should I modify my clsCostCentre_NVL_ByUser to pass in a UserID, which in turn will be passed as a parameter to my stored procedure?

Or should I create a readonlyroot list for this purpose?

While on the subject, the app requires a number of dropdown lists to display data that is filtered by previous selections.  NVL or ReadOnlyRootList?

 

RockfordLhotka replied on Thursday, February 15, 2007

NVL is merely a specialized version of ROL, designed to help reduce the amount of code needed to create a name/value list.

The decision on whether to use NVL or ROL should be, I think, driven primarily by which approach allows you to write the least code. I'm guessing, in your scenarios, that NVL will still require less code, even after you create/use your custom criteria classes.

One thing about context-sensitive lists like this. They should flow from your object model. It isn't the UI's job to know what list should be filtered this way or that. That's business logic, and belongs in the business layer.

Global, unfiltered, lists can be created directly, like the roles are in ProjectTracker.

Other lists are filtered based on the state of an object. For instance, consider a Customer object. Once you pick the Region value (from a global list) the possible options for District are filtered. To implement this, the DistrictList object should NOT be available for direct creation. It is not a global, unfiltered list.

Instead, DistrictList should come from the Customer itself, because what you need is the list of districts for this customer.

Additionally, the Customer object needs the filtered list too. Otherwise, how can it validate that the District property has been set to a valid value? It must validate the input - that's business logic - and to do that it already needs the filtered list.

The result is like this:

RegionList regions = RegionList.GetList();

DistrictList districts = _customer.GetDistrictList();

If you want to have the UI automatically respond, you can handle the PropertyChanged event from _customer, and when the Region property changes you should rebind the UI to _customer.GetDistrictList().

TerryHolland replied on Thursday, February 15, 2007

Thanks for response.

So I understand that the filtered list should be a property of my business object. 

In my scenario, I have a list (NVL) called clsCostCentre_NVL which, at present return the entire list of cost-centres (I need this as well as a filtered list)

My User class will require a CostCentres property which will return a filtered list of CostCentres based in UserID ie

public class clsUser

 Public ReadOnly Property ID() As Integer
  Get
   CanReadProperty("ID", True)
   Return m_intID
  End Get
 End Property

    Public ReadOnly Property CostCentres() As clsCostCentre_NVL
        Get
            Return clsCostCentre_NVL_ByUser.GetCostCentre_NVL(m_intID)
        End Get
    End Property

End Class

What Im not sure how to do is modify the criteria class in the NVL class to accept a parameter.  When I try to add a Partial Class Criteria in my NVL class I get a message stating that I should declare it as shadows.  Advice appreciated

 

Terry

 

RockfordLhotka replied on Thursday, February 15, 2007

You need to give the criteria class a different name. NVL already declares a class called Criteria, so you can’t use that name. Partial classes have no place in this model at all (unless you are using a code generator that uses Partial classes – in which case you’ll have to resolve any differences there).

 

Rocky

TerryHolland replied on Thursday, February 15, 2007

Ive got my class to work using the code pasted below.  What I have now are two distinct classes

1) clsCostCentre_NVL which is used throughout the application and is a non filtered list of CostCentres

2) clsCostCentre_NVL_ByUser which will be used by my clsUser class, which will pass in a UserID parameter to filter the list

Im a little cocerned because Im not fully sure I fully understand the call to the DataPortal.Fetch.  In the clsCostCentre_NVL the factory method is:

Public Shared Function GetCostCentre_NVL() As clsCostCentre_NVL

If _list Is Nothing Then

_list = DataPortal.Fetch(Of clsCostCentre_NVL)(New Criteria(GetType(clsCostCentre_NVL)))

End If

Return _list

End Function

In my adapted class Ive replaced this with:

Public Shared Function GetCostCentre_NVL_ByUser(ByVal intUserID As Integer) As clsCostCentre_NVL_ByUser

If _list Is Nothing Then

_list = DataPortal.Fetch(Of clsCostCentre_NVL_ByUser)(New Filter(intUserID))

End If

Return _list

End Function

 Can you foresee any problems doing things the way I have?

Also, you mentioned in previous post that "Partial classes have no place in this model at all ".

Im a little concerened at this as Im using Partial classes throughout.  I have one partial class for template generated code and another for custom code.  Is there likely to be any problems with this?

Thanks again.

 

My Adapted NVL class

=================

''------------------------------------------------------------------------------

'' <autogenerated>

'' This code was generated using CSLA 2.0 CodeSmith Template Collection.

'' Changes to this file may be lost if the code is regenerated.

'' Modify user class CostCentre_NVL_ByUser.vb to extend this generated code.

''

'' Code was generated at 15/02/2007 15:12:39 by tholland

'' Template path: D:\_Development\CodeSmith\3.2\3.2 Templates\SGB CSLA.Net 2.0\NameValueList.cst

'' Template website: http://www.codeplex.com/Wiki/View.aspx?ProjectName=CSLAcontrib

'' </autogenerated>

''------------------------------------------------------------------------------

Imports System

Imports System.Data

Imports System.Data.SqlClient

Imports Csla

Imports Csla.Data

<Serializable()> _

Public Class clsCostCentre_NVL_ByUser

Inherits Csla.NameValueListBase(Of Integer, String)

 

 

#Region " Factory Methods "

Public Class Filter

Public UserID As Integer

Public Sub New(ByVal intUserID As Integer)

UserID = intUserID

End Sub

End Class

Private Sub New()

' require use of factory method

End Sub

Private Shared _list As clsCostCentre_NVL_ByUser

Public Shared Function GetCostCentre_NVL_ByUser(ByVal intUserID As Integer) As clsCostCentre_NVL_ByUser

If _list Is Nothing Then

_list = DataPortal.Fetch(Of clsCostCentre_NVL_ByUser)(New Filter(intUserID))

End If

Return _list

End Function

 

Public Shared Sub InvalidateCache()

_list = Nothing

End Sub

#End Region

#Region " Data Access "

Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)

RaiseListChangedEvents = False

Dim objFilter As Filter = CType(criteria, Filter)

Using cn As SqlConnection = New SqlConnection(Database.Contest03UKConnection)

Using cm As SqlCommand = cn.CreateCommand()

cn.Open()

cm.CommandType = CommandType.StoredProcedure

cm.CommandText = "sprCostCentre_NVL_ByUser_Select_CSLA"

cm.Parameters.AddWithValue("@intUserID", objFilter.UserID)

 

Using dr As SafeDataReader = New SafeDataReader(cm.ExecuteReader())

IsReadOnly = False

While (dr.Read())

Me.Add(New NameValuePair(dr.GetInt32("coce_int_ID"), dr.GetInt16("coce_sin_Number")))

End While

IsReadOnly = True

End Using

End Using

End Using

RaiseListChangedEvents = True

End Sub

#End Region

End Class

 

RockfordLhotka replied on Thursday, February 15, 2007

Your code looks fine to me.

 

If you want, you may consider collapsing your two NVL classes into one class, and just having two factory methods, with two overloads for DP_Fetch(). That might keep your code simpler overall.

 

There’s nothing wrong with using partial classes along with code generation – I didn’t mean to imply otherwise. My point was that Criteria isn’t a partial class, at least in CSLA .NET itself. If your code-gen tool is creating partial classes then that’s fine.

 

Rocky

TerryHolland replied on Thursday, February 15, 2007

Thanks for your help

jkellywilkerson replied on Friday, February 16, 2007

Hey Rocky,

I have a similar situation where I have a combo that uses a NVL and the user can click a checkbox that will basically change the sort from "Sort By Number" to Sort By Name".  The attached code works perfectly when using the Local DataPortal, but does not work when switching to the Remote DataPortal.  Can you see anywhere in the code that may cause that?  I am a bit baffled as to why it would matter which DataPortal is used.

Thanks in advance for your time.

Kelly.

RockfordLhotka replied on Friday, February 16, 2007

What does "does not work" mean?

No data? An exception? If so, what kind of exception? If a DataPortalException, what's the BusinessException? Do other objects work through the remote data portal?

jkellywilkerson replied on Friday, February 16, 2007

Hey Rocky,

Sorry I did not clarify how it did not work in the previous message.  When the checkbox value is changed, the combobox blinks then appears with the previous sort order - always by number.  Is it a problem with the _sortbyname member variable declared as static?  If I leave the static off, I get a compile error of course.  Just not sure what else to try, short of having two different BOs.

Kelly.

RockfordLhotka replied on Friday, February 16, 2007

The problem is that you are effectively using a global variable for your sort flag. When you call the data portal, the data portal calls your DataPortal_Fetch() method IN A NEW INSTANCE OF YOUR OBJECT.

So any instance fields are obviously brand new, and empty so you can load them in DP_Fetch(). Only the criteria parameter has values from the caller.

When you use a local data portal everything runs in one AppDomain, so any static fields are available to all your code - they are global variables (and thus are basically evil btw Wink [;)] ).

When you use a remote data portal the server code actually runs on the server - obviously then in a different AppDomain. Your static field is back on the client, and you didn't pass it to the server.

The correct answer to your problem is to create a custom Criteria class (call it SortedCriteria or something) and include the bool flag value in the criteria. Your DP_F() code should then use the value from the criteria parameter, not from a global field.

jkellywilkerson replied on Friday, February 16, 2007

Thanks Rocky,

It worked beautifully!  I knew I was missing something that was happening with the static bool member variable; I just could not figure it out - my appreciation for you and this community runs deep.  Attached is what the new object code looks like, if others have experienced similar heart/headaches.

Thanks again,

Kelly.

Copyright (c) Marimer LLC