Physical class layout of virtual contact

Physical class layout of virtual contact

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


wolfpackfan posted on Wednesday, March 14, 2007

So I have gone back and forth a number of times with the layout and design of my class files based on my table structure. Here is what i have:

in sql:
-------------------
Contact (100 fields)
Contact2 (50+)
Contact3 (50+)

all related via Sysid
My first thought was to just make one big class file (BusinessBase) to represent the three tables in the database.  So I created a view of the tables and let CodeSmith hit the view to create an EditableRoot object.  Then i modified the Fetch, Update methods to call separate stored procedures for each table in the database (One fetch from view, 3 update methods to individual tables). However, w only want to update those fields that have changed and not sent back everything.  So based on another thread i created my own base that inherits from BusinessBase that maintains a list of dirtyFields.  So this is the problem.  I don't know which table the field belongs to.  So that leads me to beleive i should have three separate class files.  But if i do that, how do i incorporate the class files in such a manner that all fields across all tables would be available in a gridview?

Justin replied on Wednesday, March 14, 2007

Why do you have 3 tables , is it because you are trying to represent inheritance in the table structure such that Contact3 inherits from Contact2 which inherits from Contact? If so the doing inheritance in the object model would make sense, each tracking thier own dirty fields and each calling thier bases update in thier update.

If it's not to replicate inheritance but instead for performance reasons or perhaps # of field limitations in your implementation of sql, then if your sql implementation can make the view updatable then you just do that, otherwise you will need some mapping code to to map fields to thier proper table in the BO.

Last option could be composition where you create one object used for the gridview and it has 3 private classes that all update thier respective tables, then the getters and setter for the single "view" object all just pass through to your 3 private classes.

Again kinda depends on why you have 3 tables instead of one.

Justin

 

wolfpackfan replied on Wednesday, March 14, 2007

The reason for three tables is a limitation to the original code used to write the application = Clarion.

Yes i can make the view updatable but am worried about performance vs. updating the tables individually.

Regardless, how do I overwrite the SafeSqlReader for an objects contstructor to insert string.empty or 0 if the column isn't in the reader.

select firm, last, sysid from contactview

Contact (SafeSqlDataReader dr)
{
   this._firm = dr.getString("firm");
   this._first = dr.GetString("first"); <----- this fails cause it wan't in the select.
   ...
}

I'd like to be able to populate a Full Contact object but only specify certain columns to get from the DB.

ajj3085 replied on Wednesday, March 14, 2007

What happens if you try to use the properties on the Contact object and the property isn't loaded?  What if the client sets a property which wasn't loaded, and saves the object?  It sounds like a very complicated scenario.

What are your use cases?  I assume you have different scenarios that you can tell if you need to load certain fields or not?  

If you have use case A, which requires only data fields X and Y to perform the use case, and use case B which needs only fields X and Z, I would suggest you create two classes, one contact class which handles use case A and another contact class to handle use case B.  If you go that route, you won't have to worry about this problem because each class would only load the data it needs and would know how to get the data it needs.

Justin replied on Wednesday, March 14, 2007

I too would wonder what happens if not all fields are loaded? Or maybe more important why this could happen, sounded like in your original question you where always going to load all fields data using a view but only update the changed fields by dynamically building an update statement with dirty fields?

If they really are different entities with shared properties but additional properties for their specific purpose then the approach of different classes with thier own rules and data access code would be the CSLA way of doing things.

To answer your question specifically though you would need to check for the existence of a field in the reader before trying to do this._field1 = dr.GetString("field1");, if its not there then don't set it value, it will remain o or string.empty based on data type.

If you truely want lazy loading then you would not only have to implement dirty fields but a way to track not yet loaded fields and have somethng that runs on the property getter to retieve the value from the db if not yet loaded. Definetly not trivial but possible.

Another option would simply be to track not yet loaded fields but simply throw an exception should a consumer try to set a value.

Justin

wolfpackfan replied on Wednesday, March 14, 2007

It looked as if i could overwrite GetOrdinal("<fieldname>") which returns the index of the field.  Question is will GetOrdinal return a -1 if the field isn't in the reader.  I hadn't got there yet but was going to try to capture the error thrown and just insert a string.empty or zero.

Next decision we just made was if the object is editable, you have to get all fields from the database.  that takes care of knowing what fields were loaded.  So in my read only implementation of the object, you can specify columns and i will just either through an exception or it will be empty.

wolfpackfan replied on Wednesday, March 14, 2007

What i forgot to mention is if in a read only list i'd like to be able to choose "get me these ten columns" from the database so that I don't have the overhead of returning all the data in the 200 columns.  I don't think overriding the safedatareader is the best option.  Isn't there a method in csla.untilties or csla.data.datamapper that can do this for me?  the trouble is i want to set the value without calling the public "set" method.  i want to set the private member.

The use case in this example is a user of the application can define dynamically what columns they want to see in their list.  Why should I retrieve everything from the database when they only want, for example, first, last, firm, phone?

Justin replied on Wednesday, March 14, 2007

Well if the properties to display in the result list is dynamic based on user input then the criteria object that you pass into the fetch would obviously have to have the properties requested along with the comparison criteria correct?

If so then based on that list of requested properties you would use that to loop through the datareader fields and using reflection to find the appropriate internal field to set.

Should be no need to modify the safedatareader for this.

 

wolfpackfan replied on Wednesday, March 14, 2007

Ya that is what i thought so in ContactInfoList I have an array of Columns in FilterCriteria.

Leading to this:

if (criteria.Columns.Length != 0)
{
   
this.Add(MyLibrary.ContactInfo.GetContactInfo(dr, criteria.Columns));
}
else
{
   this.Add(MyLibrary.ContactInfo.GetContactInfo(dr));
}

then in COntact Info I have this called by the internal static GetContactInfo...

private void FetchObject(SafeDataReader dr, string[] cols)
{
   
Dictionary<string, object> dict = new Dictionary<string, object>();
   for (int i = 0; i < cols.Length; i++)
   {
      dict.Add(colsIdea [I], dr.GetValue(colsIdea [I]));
   }
   Csla.Data.
DataMapper.Map(dict, this);
}

I then get an error that there is no set method for column "Last_name" which is true cause the ContactInfo class is read only.  The Map function is using reflection. but i need to set the private members.

wolfpackfan replied on Wednesday, March 14, 2007

using the keyword internal on the set methods works, however what else is it going to break in the CSLA framework.

public string Sysid
{
   get{
      
CanReadProperty("Sysid", true);
      
return m_sysid;
   }
   
internal set { this.m_sysid = value; }
}

Justin replied on Wednesday, March 14, 2007

I would not use the Datamapper and instead write your own reflection code to find private fields instead of modifying the setter.

Justin

ajj3085 replied on Thursday, March 15, 2007

I agree, the DataMapper isn't appropriate here.

If you want to get really fancy, you could use reflection and the compiler services to dynamically build and compile your contact class (using your current class as an abstract base), then use the in memory compiled type as the datasource for a grid. 

Copyright (c) Marimer LLC