I've been looking into several of the OR mapping frameworks out there for several projects I'm about to start on and atom's framework looks very intriguing.
one question that I had though, and this is more theory than support, really.
Several project's I've recently worked on used DB access frameworks where at least +some+ of the mapping was accomplished by specific naming patterns of fields in relationships.
For instance, all Primary keys for all tables were named "ID", and a Foriegn key in a table might be named something like "Users_ID" (where USERS is the name of the table the FK points to).
Doing this in the framework (or one better and actually enumerating the RI via stored procs etc) seems like it'd cut down on the "up front" configuration required in the XML file that defines the mapping. The framework would know that a field named USERS_ID contained a PK from the USERS table, for instance.
Similiarly, since in order to populate an object with all it's properties, we have to pull all the fields from the row anyway, it'd seem simpler to provide the fields as a collection (just a wrapper around the ADO FIELDS collection) rather than explicitly copy each field value to it's property.
I know that would result in late binding, dropping of intellisense etc, but are there other significant negatives to that approach that i'm missing... It seems like a big positive would be a huge reduction in "boilerplate coding" of all the properties that would almost always have to be done through a code generator of some sort.
Anyway, my hat's off to the work you've done. It's a really great an example of some very nice architecture!
Thanks
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
1. Naming conventions
It's a nice idea however from a personal viewpoint (and from many years of enterprise level development) I find that anything that uses a particular naming convention to produce a specific behaviour usually requires more work in the end to get around situations where the naming convention shouldn't or can't apply.
Naming conventions can raise problems for people on non-english locales, or those who want to map to legacy databases that have fields that just happen to be named according to convention but that should not behave in such a manner.
I could theoretically make the use of a naming convention optional, but then I much prefer that all fields be explicitly mapped. That way if an over enthusiastic DBA decides to change names or db tables get added/modified it is clear exactly what will be impacted and no knowledge of the internal workings of the framework is required.
2. Foreign Keys
Just a note on foreign keys - I don't make any reference to them or any use of them at any point in the framework.
It's something that needs to be done for multi step operations that might violate referential key integrity but I haven't done it as yet.
3. Object population
The collection idea is interesting but I'm not quite sure where you are coming from. Are you talking about the code that a programmer has to write to populate an object before saving, or the code in the framework that populates the object from the recordset?
If you refer to the first method then I would think that it's more an issue for how the programmer writes their objects
If it's the second, then you have to be able to take into account proxy objects and the fact that fields selected for a proxy are different to fields selected for a full object, and that association retrieval will only ever retrieve key fields. As a result a fields collection would be different sizes depending on the retrieval method used.
Also field collections don't allow the programmer to intercept a value as it is placed into a property and to take an further action they deem necessary.
I know the boilerplate coding is a pain in the neck and it can be tedious which is why I've developed the framework mapper with the code generation.
Once again, thanks for the feedback and I hope that answers some of the theoretical questions.
- Richard
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
About 1. Good point on legacy DB's and foriegn langs... The legacy DB issue struck me right after I posted. I've mostly been fortunate (i suppose<g>) to be the DB designer on the projects I've worked on, but I could see this as an issue with existing systems
I guess my take has always been that if I rename a field in a table, the meaning of that field is likely changed, and regardless, it just seems like one more mental hoop to jump through when you're chasing down issues through the code. But, the ability to fit a new project onto an old DB layout, while naming the cols what's appropriate could be handy in some cases...
About 2 (foriegn keys). Maybe that's something I could help with. I'm a bit of a stickler for good FK definitions, and it'd be great for the framework to accomodate them automatically.
About 3 (Object population) Sorry about that. I should have been clearer.
I'm actually talking about both.
Currently, the framework requires something like the following
Private m_CustID As String
Private m_Description As String
Private m_ContactName As String
Private m_Country As String
Public Property CustomerID() As String
Get
Return m_CustID
End Get
Set(ByVal Value As String)
If m_CustID <> Value Then
m_CustID = Value
SetDirtyFlag()
End If
End Set
End Property
Pretty typical of all the frameworks I've looked at. This is where most of that boiler plate code comes in.
This +does+ give you early binding to the columns and intellisense, but it requires all that generated code.
What I've seen done in the past (this was in VB 6 so bear with me), was to expose an enumeration property from your object, call it FIELDS(), that took the field name as it's parameter (very much like the fields collection of the recordset itself)
So, accessing fields in an object would go from
CUser.Name
to
CUser!Name
Because this is an exposed enumeration, the object's code can intercept any requests and redirect them as appropriate (ie calling the SetDirty method, logging, etc).
This approach means virtually no boiler plate (and with .NET's inheritance, it's probably even less that what was necessary in VB6). You can still add methods and additional properties (meta properties?) that extend the object past the columns that actually exist in the DB, so you're Business layer is kept intact.
The downside is that there's no intellisense and the field access is effectively latebound. As for the late binding though, the framework essentially has to do the same thing when it populated the object props from the RS now, so there's likely little impact there...
Are there other downsides to that approach that I'm missing?
One thing I really like about AtomsFramework is that, other than the column property handling, there's very very little "boilerplate code" that exists in each object. To me, the less the better!
Thanks again for your responses!
-Darin
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
2. If you want to help please feel free to. The more help, the better :-)
The normal approach it to submit some patches to the code via the tracker and once we've seen a few submissions we normally provide write access to CVS and add you as a developer.
3. I've used the recordset!fieldname syntax in VB6 for years and it drove me nuts. I would forget what a field name was and have to look it up, or I'd type it wrong and only pick it up as a bug at run time and sometimes only when the app was out in the hands of the customer (not good).
Also, don't forget that in .NET you would now be writing CUser.Fields("Name") instead of CUser.Name and the ! syntax isn't available (that I know of).
I too prefer to write as little code as possible however without properties I lose the ability in the framework to use reflection to validate XML field mappings or to use attribute based mappings both of which would be a problem.
This is why I have the code generation in the mapper project. The idea is that with a bit of point and click I can generate the business objects with all the boilerplate code already there and reduce hours/days of tedious typing to minutes.
- Richard
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
You're right. Having the fields validate at compile is nice, if there were only a way to make it happen without all the code gen. I was just looking at the strongly typed dataset stuff, but even that looks like essentially a code generator.
I've been reading up on some of the other OR mappers. Many rely on attributes, but other eschew them in favor of pure XML defs... The reasons are pretty compelling. Then there are some that tout the notion that they "don't require your objects to inherit from a base class", but I'm not sure yet why that's necessarily a bad thing.
Thanks again for the responses. If I come up with something, I'll let you know. It may just be I'm looking for pie in the sky but it doesn't hurt to try!
Darin
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
In regards to both the XML vs Attributes point and the inheritance vs non-inheritance approach, the AtomsFramework actually offers choice in both cases.
You can choose to use XML for mappings or you can choose to use attributes. XML gives more flexibility but far be it from me to say that you must use one or the other.
The same with inheriting from a base class or not. The framework lets you either inherit from the CPersistentObject or just code classes directly and even better you can persist bases on an interface (ie as long as the object implements an interface it can be persisted).
I'd love to get some feedback after you look around at the others so I know how the software compares from the view of someone without the emotional attachment that I have ;-)
- Richard
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Been doing a little more experimenting with ATOMS and have a few architecture questions...
1) I know this is likely an XML parsing issue, but it seems like the XML settings are awfully case sensitive, I entered servername= for a setting and it barfs on me for instance. I know it's minor. I guess I just hate case sensitive bits...
2) Again, this may be just my preference but it seems like it'd be nice to not have to indicate all the redundant info in the XML. For instance, if the property name and the column name is the same, wouldn't it be nice to only have to specify one or the other?
3) I finally figured out the RETRIEVE vs FIND differences, but that got me wondering. In order to do a simple get for all rows that match a criteria, it appears you have to do something like:
Dim pbroker As New CPersistenceBroker
pbroker.Init(xn)
Dim rc As CRetrieveCriteria
Dim c As New CListItem
Dim cursor As CCursor
rc = New CRetrieveCriteria
rc.ClassMap = c.getClassMap
rc.WhereCondition.addSelectEqualTo("ShortDesc", "Pres")
cursor = rc.perform(c)
Is the general consensus that if you need methods to retrieve a set of objects, that you hang a method off the root object that returns a collection of those objects?
For instance, if CListItem represents a single row from the LISTS table, then
Items = CListItem.RetrieveByGroupID(GroupID)
would return a collection (or cursor or whatever) of all the individual CListItem objects who belong to that group?
Or is there some other generally prefered organization?
Thanks again
Very cool stuff! (except for those latest oracle mods, my machine appearently hates oracle<g>)
Darin
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Yes it's an XML parsing issue. It's only an annoyance for us VB programmers. The C# guys have to deal with it on a daily basis since C# is case sensitive.
2) Redundant XML info
The column name isn't actually redundant. It's used to indicate that an attribute has a table mapping instead of just being the target of an association. Maybe there's a simple way to indicate the attribute name and column name are the same, but I don't want to add extra tags to the XML if possible. I like to follow the "keep it simple" rule.
3) Retrieve Simplification
There is a method called getCollectionByAttributesWithEqualTo() for objects inheriting from CPersistentObject that may help to simplify the code. You pass two collections, one with the attribute names and the other with the values to match on.
I still think the whole retrieve criteria part is still a bit too verbose/cumbersome but I haven't yet put enough thinking time into ways to simplify it further, while still retaining the flexibility.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi
I've been looking into several of the OR mapping frameworks out there for several projects I'm about to start on and atom's framework looks very intriguing.
one question that I had though, and this is more theory than support, really.
Several project's I've recently worked on used DB access frameworks where at least +some+ of the mapping was accomplished by specific naming patterns of fields in relationships.
For instance, all Primary keys for all tables were named "ID", and a Foriegn key in a table might be named something like "Users_ID" (where USERS is the name of the table the FK points to).
Doing this in the framework (or one better and actually enumerating the RI via stored procs etc) seems like it'd cut down on the "up front" configuration required in the XML file that defines the mapping. The framework would know that a field named USERS_ID contained a PK from the USERS table, for instance.
Similiarly, since in order to populate an object with all it's properties, we have to pull all the fields from the row anyway, it'd seem simpler to provide the fields as a collection (just a wrapper around the ADO FIELDS collection) rather than explicitly copy each field value to it's property.
I know that would result in late binding, dropping of intellisense etc, but are there other significant negatives to that approach that i'm missing... It seems like a big positive would be a huge reduction in "boilerplate coding" of all the properties that would almost always have to be done through a code generator of some sort.
Anyway, my hat's off to the work you've done. It's a really great an example of some very nice architecture!
Thanks
Firstly - thanks for the positive feedback :-)
Now on to the points you raise
1. Naming conventions
It's a nice idea however from a personal viewpoint (and from many years of enterprise level development) I find that anything that uses a particular naming convention to produce a specific behaviour usually requires more work in the end to get around situations where the naming convention shouldn't or can't apply.
Naming conventions can raise problems for people on non-english locales, or those who want to map to legacy databases that have fields that just happen to be named according to convention but that should not behave in such a manner.
I could theoretically make the use of a naming convention optional, but then I much prefer that all fields be explicitly mapped. That way if an over enthusiastic DBA decides to change names or db tables get added/modified it is clear exactly what will be impacted and no knowledge of the internal workings of the framework is required.
2. Foreign Keys
Just a note on foreign keys - I don't make any reference to them or any use of them at any point in the framework.
It's something that needs to be done for multi step operations that might violate referential key integrity but I haven't done it as yet.
3. Object population
The collection idea is interesting but I'm not quite sure where you are coming from. Are you talking about the code that a programmer has to write to populate an object before saving, or the code in the framework that populates the object from the recordset?
If you refer to the first method then I would think that it's more an issue for how the programmer writes their objects
If it's the second, then you have to be able to take into account proxy objects and the fact that fields selected for a proxy are different to fields selected for a full object, and that association retrieval will only ever retrieve key fields. As a result a fields collection would be different sizes depending on the retrieval method used.
Also field collections don't allow the programmer to intercept a value as it is placed into a property and to take an further action they deem necessary.
I know the boilerplate coding is a pain in the neck and it can be tedious which is why I've developed the framework mapper with the code generation.
Once again, thanks for the feedback and I hope that answers some of the theoretical questions.
- Richard
Hi
Thanks for the response...
About 1. Good point on legacy DB's and foriegn langs... The legacy DB issue struck me right after I posted. I've mostly been fortunate (i suppose<g>) to be the DB designer on the projects I've worked on, but I could see this as an issue with existing systems
I guess my take has always been that if I rename a field in a table, the meaning of that field is likely changed, and regardless, it just seems like one more mental hoop to jump through when you're chasing down issues through the code. But, the ability to fit a new project onto an old DB layout, while naming the cols what's appropriate could be handy in some cases...
About 2 (foriegn keys). Maybe that's something I could help with. I'm a bit of a stickler for good FK definitions, and it'd be great for the framework to accomodate them automatically.
About 3 (Object population) Sorry about that. I should have been clearer.
I'm actually talking about both.
Currently, the framework requires something like the following
Private m_CustID As String
Private m_Description As String
Private m_ContactName As String
Private m_Country As String
Public Property CustomerID() As String
Get
Return m_CustID
End Get
Set(ByVal Value As String)
If m_CustID <> Value Then
m_CustID = Value
SetDirtyFlag()
End If
End Set
End Property
Pretty typical of all the frameworks I've looked at. This is where most of that boiler plate code comes in.
This +does+ give you early binding to the columns and intellisense, but it requires all that generated code.
What I've seen done in the past (this was in VB 6 so bear with me), was to expose an enumeration property from your object, call it FIELDS(), that took the field name as it's parameter (very much like the fields collection of the recordset itself)
So, accessing fields in an object would go from
CUser.Name
to
CUser!Name
Because this is an exposed enumeration, the object's code can intercept any requests and redirect them as appropriate (ie calling the SetDirty method, logging, etc).
This approach means virtually no boiler plate (and with .NET's inheritance, it's probably even less that what was necessary in VB6). You can still add methods and additional properties (meta properties?) that extend the object past the columns that actually exist in the DB, so you're Business layer is kept intact.
The downside is that there's no intellisense and the field access is effectively latebound. As for the late binding though, the framework essentially has to do the same thing when it populated the object props from the RS now, so there's likely little impact there...
Are there other downsides to that approach that I'm missing?
One thing I really like about AtomsFramework is that, other than the column property handling, there's very very little "boilerplate code" that exists in each object. To me, the less the better!
Thanks again for your responses!
-Darin
Hi Darin,
2. If you want to help please feel free to. The more help, the better :-)
The normal approach it to submit some patches to the code via the tracker and once we've seen a few submissions we normally provide write access to CVS and add you as a developer.
3. I've used the recordset!fieldname syntax in VB6 for years and it drove me nuts. I would forget what a field name was and have to look it up, or I'd type it wrong and only pick it up as a bug at run time and sometimes only when the app was out in the hands of the customer (not good).
Also, don't forget that in .NET you would now be writing CUser.Fields("Name") instead of CUser.Name and the ! syntax isn't available (that I know of).
I too prefer to write as little code as possible however without properties I lose the ability in the framework to use reflection to validate XML field mappings or to use attribute based mappings both of which would be a problem.
This is why I have the code generation in the mapper project. The idea is that with a bit of point and click I can generate the business objects with all the boilerplate code already there and reduce hours/days of tedious typing to minutes.
- Richard
Geez. Shows how long i've been using .NET<g>
You're right. Having the fields validate at compile is nice, if there were only a way to make it happen without all the code gen. I was just looking at the strongly typed dataset stuff, but even that looks like essentially a code generator.
I've been reading up on some of the other OR mappers. Many rely on attributes, but other eschew them in favor of pure XML defs... The reasons are pretty compelling. Then there are some that tout the notion that they "don't require your objects to inherit from a base class", but I'm not sure yet why that's necessarily a bad thing.
Thanks again for the responses. If I come up with something, I'll let you know. It may just be I'm looking for pie in the sky but it doesn't hurt to try!
Darin
In regards to both the XML vs Attributes point and the inheritance vs non-inheritance approach, the AtomsFramework actually offers choice in both cases.
You can choose to use XML for mappings or you can choose to use attributes. XML gives more flexibility but far be it from me to say that you must use one or the other.
The same with inheriting from a base class or not. The framework lets you either inherit from the CPersistentObject or just code classes directly and even better you can persist bases on an interface (ie as long as the object implements an interface it can be persisted).
I'd love to get some feedback after you look around at the others so I know how the software compares from the view of someone without the emotional attachment that I have ;-)
- Richard
Been doing a little more experimenting with ATOMS and have a few architecture questions...
1) I know this is likely an XML parsing issue, but it seems like the XML settings are awfully case sensitive, I entered servername= for a setting and it barfs on me for instance. I know it's minor. I guess I just hate case sensitive bits...
2) Again, this may be just my preference but it seems like it'd be nice to not have to indicate all the redundant info in the XML. For instance, if the property name and the column name is the same, wouldn't it be nice to only have to specify one or the other?
3) I finally figured out the RETRIEVE vs FIND differences, but that got me wondering. In order to do a simple get for all rows that match a criteria, it appears you have to do something like:
Dim pbroker As New CPersistenceBroker
pbroker.Init(xn)
Dim rc As CRetrieveCriteria
Dim c As New CListItem
Dim cursor As CCursor
rc = New CRetrieveCriteria
rc.ClassMap = c.getClassMap
rc.WhereCondition.addSelectEqualTo("ShortDesc", "Pres")
cursor = rc.perform(c)
Debug.WriteLine(cursor.TotalRows)
cursor.loadObject(c, 1)
and load each object in to access it.
Is the general consensus that if you need methods to retrieve a set of objects, that you hang a method off the root object that returns a collection of those objects?
For instance, if CListItem represents a single row from the LISTS table, then
Items = CListItem.RetrieveByGroupID(GroupID)
would return a collection (or cursor or whatever) of all the individual CListItem objects who belong to that group?
Or is there some other generally prefered organization?
Thanks again
Very cool stuff! (except for those latest oracle mods, my machine appearently hates oracle<g>)
Darin
1) XML Cases Sensititivity
Yes it's an XML parsing issue. It's only an annoyance for us VB programmers. The C# guys have to deal with it on a daily basis since C# is case sensitive.
2) Redundant XML info
The column name isn't actually redundant. It's used to indicate that an attribute has a table mapping instead of just being the target of an association. Maybe there's a simple way to indicate the attribute name and column name are the same, but I don't want to add extra tags to the XML if possible. I like to follow the "keep it simple" rule.
3) Retrieve Simplification
There is a method called getCollectionByAttributesWithEqualTo() for objects inheriting from CPersistentObject that may help to simplify the code. You pass two collections, one with the attribute names and the other with the values to match on.
I still think the whole retrieve criteria part is still a bit too verbose/cumbersome but I haven't yet put enough thinking time into ways to simplify it further, while still retaining the flexibility.
1) XML, yeah, That's what I suspected. Not much to do there. Man, I liked the good ol' INI days<g>
2) Redundant XML. I agree and I certainly wouldn't want to add MORE tags to the XML. I was just thinking of ways to possibly +lessen+ the amount
3) Simplify Retrieve..Hmmm hadn't seen that. The examples are pretty spare<g>. I'll give it a whirl.
Thanks again