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?
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
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.
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
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.
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?
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.
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(cols, dr.GetValue(cols
));
}
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.
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; }
}
I would not use the Datamapper and instead write your own reflection code to find private fields instead of modifying the setter.
Justin
Copyright (c) Marimer LLC