As far as "objects building their own queries" -- I think you misunderstood what I was talking about. In CSLA, the business objects do not use a seperate layer: the busines object does its own sql (and that of its children) internally. There is no repository
you go to generate (or create your own) dynamic sql -- rather the business classes have static methods, where one "root" object gets all of its children, grand children, etc. i've modified the framework a bit to better support lazy loading and seperating
out the sql specific stuff from teh business objects, but they are still tightly intervoven. One of the things that attracts me to Paul's work is that you can ask the repository for the objects in a variety of different ways, rather than embedding all that
data access logic in the object hierarchy itself. The business objects seem blissfuly unaware that they are persisted at all, keeping the two concerns a lot more seperate. Also, the ability to not have to touch sql (for most things) as you mentioned.
I'm not aware of an O/R mapper that doesn't support that, every O/R mapper has at least one paradigm where they support a central 'context' or 'session' which produces the objects you want, making the entity objects persistence unaware.
It's persistence as a service, you apply the service to the entity object to get persisted (and vice versa). Keep in mind that some O/R mappers still offer lazy loading with this, which opens the gate to the database in your PL layer even though you don't have
a session around. This can lead to the ability to side-track the BL tier for fetching/saving data, and in large(r) teams that's definitely not what you want. Our Adapter paradigm supports true isolation of persistence core and entity objects for example for
this purpose.
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
I'm jumping in a little late to the party, forgive me. The quality of discussion is amazingly high. It's great to see Frans weighing in (I have tremendous respect for his work and his contributions on persistence subjects). LLBLGen, WORM, and Hibernate all
seem to be fine products. No bashing from me.
I saw my company's product, IdeaBlade, mentioned a few times and thought it might be useful to make myself available for questions and to add some thoughts if I can be helpful. We take a rather different
approach to ORM than the other products mentioned here so I suspect translation of terms may resolve some quandries.
Our distinctiveness is due substantially to our support for smart client apps which can operate disconnected for long intervals [so why I am I here on an ASP forum, right?]. Object-Relational Persistence is an important component in what we do but not our whole
story which extends to UI databinding and deployment. We care alot about usability and ease of learning so we put much energy into the GUI designers and documentation.
I'm not trying to post a commercial. I'm just positioning us with respect to the other products you've covered. To make that evident, let me confess that our strongly-typed object query language is not as rich as Hibernate or WORM's (haven't looked as closely
at LLBLGen yet). Customers haven't complained but there are definitely useful queries that we cannot express in our OQL. Our fall back is SQL PassThru, Stored Procedures, or (worst case) Remote Procedure Calls.
BTW, we have recently added support for Identity columns (for SQL Serverso far) . The prior work-arounds were not ideal. We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been
assigned and used as foreign keys by other objects.
Let me illustrate. Suppose you add new Order 'A' with its new order items 'A1', 'A2'.... You make a small change to Order's existing Customer 'C' and then save the entire bunch transactionally. The db gives you a new Id for A which you stick in the ParentOrderId
columns of A1, A2, etc. Unfortunately, you get a concurrency failure because someone modified Customer C and saved it before you did. The database roles back the transaction but your A1, A2, etc are sitting in cache with A's fixed up ID - an ID that it can
never actually have. That ID is gone forever. What do you do? I wonder how other vendors manage this.
Moving on. Would you be willing to amplify on your comments about intuitiveness, collections, and inheritance? Happy to have this conversation elsewhere if this is not the right place.
Just to get the juices flowing, let's talk about inheritance. As Frans made clear, it's easy to change "types" in a database table but it's - shall we say - "problematic" to have a Manager class that inherits from Employee (nor should SalesRep nor BoardMember
nor PartTime, to mention a few of the other potential hats the Employee may put on and take off). On the other hand, it makes good sense for a number of otherwise un-related classes to inherit common behavior from a base class. For example, I might want some
subset of my entities to share the same logging or auditing behaviors. The ORM facility should enable the developer to insert an Audit support class into the inheritance chain. That Audit class is probably not a persistable entity in its own right.
You are pretty bad in your english, because you DID post just a commercial, you know.
Somehow your statement really reminds me of the "this is not spam" lines in - spam.
Plus:
We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been assigned and used as foreign keys by other objects.
Total non-issue. See, as id-columns are not supposed to be end user known identities (like order number), loosing numbers is just a total non-issue. This is basic SQL 101 for beginners. There is no need for a solution unless you make up a problem by abusing
SQL features for things they are not. It is a typical beginner mistake to abuse them as things like order numbers.
For example, I might want some subset of my entities to share the same logging or auditing behaviors. The ORM facility should enable the developer to insert an Audit support class into the inheritance chain.
Yeah. Or you use a nicely designed API that is not bolloks and that exposes events that an audut subsystem can pick up. Like "object changed, object created, object deleted". For auditing there is ZERO need to insert audit support into the inheritance chain
at all, if the O/R mapper in question (or the business architecture) provides anything in the vincinity of a peroperly designed object model.
So what's your use case for inheritance?
For anyone knowing OO: the use case for inheritance is explicitly described in the basicos of object orientation, and is for a specialization case. Just athat a lot of people (like you, and a lot of others on the board) take the employe/job specialization (which
is the TYPICAL case for "doing it wrong", out of the text books) does not mean it is a useless feature. Factually, every text book about object orientation names some good examples. If you, sadly a provider of what you call an O/R mapper, have a problem with
basic OO terms - maybe you should read something like Scott Ambler's "The Object Primer", first. And understand the need for things like data driven inheritance - which is a hugh saver on certain situations (just not the idiotic "employee is a manager" abuse).
I can give you an example of data driven inheritance.
http://www.powernodes.com/, a CMS driven by our EntityBroker. It's main storage architecture is developped around a "ContentItem" hierarchy, like a file system. A ContentItem contains an Items collection that contains it's children. There are more than
100 subclasses of ContentItem, most with their own separate data (in separate tables). There is a lot of specializations that is possible there. From a generic distinction between "Folder" and "File", so specializations of File (Document, Product, lots of
different things). A classical data driven inheritance example - we use a four character field to distinguish types, and everything happens "automatically". An example out of a textbook. Without it, we could not easily extend the user interface, and application
logic, and introduce additional ContentItem subclasses.
I'm jumping in a little late to the party, forgive me. The quality of discussion is amazingly high. It's great to see Frans weighing in (I have tremendous respect for his work and his contributions on persistence subjects).
Thank you! [:)]
To make that evident, let me confess that our strongly-typed object query language is not as rich as Hibernate or WORM's (haven't looked as closely at LLBLGen yet). Customers haven't complained but there are definitely useful queries that we cannot express
in our OQL. Our fall back is SQL PassThru, Stored Procedures, or (worst case) Remote Procedure Calls.
I think there isn't an O/R mapper query language which can express everything that SQL can. Most can do expressions (WHERE field1 + field2 = (field3/value * field4) etc.) and aggregates, groupby + having, subqueries and joins in all styles, but for example
custom UDF calls, CASE and system function calls are somewhat less common, left alone derived tables (SELECT * FROM (Select foo, bar from table) AS bla) or subselects in the select list. I'm not sure they add that much as well. We support expressions, aggregates
etc. but not UDF's, CASE and system function calls. These can be added manually by implementing an interface (our query language (it's not really a language) is completely typed, no strings used) but it's still not smoothly as typing SELECT ISNULL(foo, "")
AS ValueFoo FROM... .
Personally I think it's also not that necessary per se to have these kind of features. I mean, if you do, you either end up with a query language very similar as SQL or SQL in a different syntax. What a query language should more focus on is providing filters
on objects or views on objects (sets defined on fields in related objects), which make it a bit different than what 100% SQL provides.
BTW, we have recently added support for Identity columns (for SQL Serverso far) . The prior work-arounds were not ideal. We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been
assigned and used as foreign keys by other objects.
Let me illustrate. Suppose you add new Order 'A' with its new order items 'A1', 'A2'.... You make a small change to Order's existing Customer 'C' and then save the entire bunch transactionally. The db gives you a new Id for A which you stick in the ParentOrderId
columns of A1, A2, etc. Unfortunately, you get a concurrency failure because someone modified Customer C and saved it before you did. The database roles back the transaction but your A1, A2, etc are sitting in cache with A's fixed up ID - an ID that it can
never actually have. That ID is gone forever. What do you do? I wonder how other vendors manage this.
LLBLGen Pro has build in field value versioning for this. The system uses a versioning mechanism which saves all values of the entities in a transaction and if a commit is executed, these values are thrown away, but if a rollback is issued, the saved values
are rolled back. This system is multi-level (you can save as much versions as you'd like and roll back to any saved version later on), and can be used by the developer as well, to save field values of an entity or entities in code, for example during each
step of a wizard or prior to opening an edit screen. I'm planning to do some O/R mapper cronicals blogposts soon in which I'll discuss this more in depth. It's actually a very interesting topic and very complex when you dig deeper than just the entity fields.
Because, why stop there? What you really want (and what I've failed to implement thus far, as anyone else, it's very complex, though I have an idea how to solve it) is graph versioning. So you have your C, your A and its A1, A2 etc. You pass it into an edit
form, all kind of things happen there, C gets a new address entity assigned, another Order B gets a new B4 orderitem etc. After a while, the user clicks 'Cancel' (these people exist [;)]). What to do? Your graph with on the root entity C, is changed in various
places. You could of course fetch them all back, but what if some entities were new and not yet saved?. Simply rolling back field values isn't sufficient, as some entities have got new related entities which have to be removed and replaced by the old referenced
entity (in the example of C's old Address entity). It looks simple at first, but once you start, you end up in a real complex environment where everything seems to affect everything else, and rolling back one thing affects another, so rolling back that one
has to be done as well etc...
Moving on. Would you be willing to amplify on your comments about intuitiveness, collections, and inheritance? Happy to have this conversation elsewhere if this is not the right place.
Just to get the juices flowing, let's talk about inheritance. As Frans made clear, it's easy to change "types" in a database table but it's - shall we say - "problematic" to have a Manager class that inherits from Employee (nor should SalesRep nor BoardMember
nor PartTime, to mention a few of the other potential hats the Employee may put on and take off). On the other hand, it makes good sense for a number of otherwise un-related classes to inherit common behavior from a base class. For example, I might want some
subset of my entities to share the same logging or auditing behaviors. The ORM facility should enable the developer to insert an Audit support class into the inheritance chain. That Audit class is probably not a persistable entity in its own right.
I've spend the last 3 months implementing inheritance support and ran into a lot of these questions as well. One of the main errors I made in the beginning was that I fell into the trap of defining types which were really instances. An example of that is for
example "Marketing Department Europe", derived from 'Department'. That's wrong, it should be 'Marketing Department', as of a type there should be able to be more than one instance. This might sound like 'Duh!' to some people, but it's not that trivial in all
cases: a lot of people run into the classic Person <-Employee and Person <-Customer problem where Employee is also a Customer.
Your example is a good one as it illustrates 2 types of inheritance in O/R mapper environments:
1) pure type inheritance to create distinctive types and per-type relations
2) inheritance to add / change behavior through polymorphism or by adding new code.
An example of 1) can be: Employee <- Manager <- Boardmember. Employee has a relation with Department which illustrates that the Employee works there. Manager has a relation with department to illustrate that manager is a manager of that department and Boardmember
has a relation with 'CompanyCar' which is a hierarchy of its own, but which illustrates the companycar of that boardmember.
This inheritance chain clearly distinguishes types so relations can be defined in either of them which aren't available in other types, for example only boardmembers have a companycar (it's not a friendly company to work for [;)]. ). As this is an example,
you can shoot holes in it of the size of the atlantic ocean, but you'll get the idea.
An example of 2) can be your example you mentioned with the auditing code.
1) can be seen as the supertype/subtype definitions in NIAM/ORM (http://www.orm.net) Though behavior is not really addable there. So to add 2) to such hierarchies, you can go for the strategy pattern, in where you inject an
object (an auditing engine for example) which takes care of the auditing if required.
You can also decide to add these types to the supertype/subtype chain directly of course, though you then run into the problem of single-inheritance in .NET and it might be nice to combine it with AOP (spring.net).
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been assigned and used as foreign keys by other objects.
Total non-issue. See, as id-columns are not supposed to be end user known identities (like order number), loosing numbers is just a total non-issue. This is basic SQL 101 for beginners. There is no need for a solution unless you make up a problem by abusing
SQL features for things they are not. It is a typical beginner mistake to abuse them as things like order numbers.
I think he meant that the ID field in the object now had a value, while the transaction was rolled back. This means that if you keep on using that entity object with that ID, it might look like it has a valid value for that field while it doesn't. I don't think
he meant that there were gaps created in sequenced numbers, as you can't solve that anyway as you said.
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
Interesting point. It would point, though, to a crap internal architecture.
Using a tiered approach, the data of bhe objects would goto a non-chatty DAL and the return values would then be submitted to the objects only after the db operations have been performed. At least this is how we do it in the EntityBroker.
Plus, Transaction 101 and Windows standard approaches (COM+) demand that transactions having an exception are not resubmitted but thrown away, which means - the issue is a non issue again.
Interesting point. It would point, though, to a crap internal architecture.
Using a tiered approach, the data of bhe objects would goto a non-chatty DAL and the return values would then be submitted to the objects only after the db operations have been performed. At least this is how we do it in the EntityBroker.
That would require a serious structure to keep track of all the new ID's in a hierarchical save, while you could solve it more elegantly by leaving it to the objects (delegation of responsibility), at least, if you have a lot of new entities in a hierarchy
and all require identity values: after each save, you have to sync the new values with FK fields for their save, and so forth.
Plus, Transaction 101 and Windows standard approaches (COM+) demand that transactions having an exception are not resubmitted but thrown away, which means - the issue is a non issue again.
Not necessarily, there are scenario's where you retry a couple of times before giving up (essentially restarting the transaction but with the same entities).
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
That would require a serious structure to keep track of all the new ID's in a hierarchical save, while you could solve it more elegantly by leaving it to the objects
At the price of not being able to use the DAL efficiently through a remoting scenario (not the technology - the scenario). Once your DAL has to do this anyway, the infrastructure is there anyway. Also, as/if you are throwing change events, you want to suppres/delay
them until the DAL has finished. But basically, to properly support any form of layer-border between the DAL and your runtime as tiers, you need this anyway.
Not necessarily, there are scenario's where you retry a couple of times before giving up (essentially restarting the transaction but with the same entities).
Simplistic ones only, given that the DTC / System.Transaction functionality does not support it.
That would require a serious structure to keep track of all the new ID's in a hierarchical save, while you could solve it more elegantly by leaving it to the objects
At the price of not being able to use the DAL efficiently through a remoting scenario (not the technology - the scenario). Once your DAL has to do this anyway, the infrastructure is there anyway. Also, as/if you are throwing change events, you want to suppres/delay
them until the DAL has finished. But basically, to properly support any form of layer-border between the DAL and your runtime as tiers, you need this anyway.
Only when you use remoting in a connected scenario: you have a graph in your BL tier, you send it off to the DAL, effectively sending packed data to the dal on another server, it's been processed there, results come back and merged with the entity objects in
the BL tier. I know your setup handles remoting transparently, though in a lot of situations, remoting isn't transparent but a given system aspect. There you know you're communicating with a remote service, and send the data, by value(!) to the service for
processing. After that send action, the life of the object has effectively ended. This indeed requires other ways of writing your software: you first gather information, and send it off to the BL for processing, which processes it for example by sending the
graph to the service. Processing done. If the caller requires the info back, the BL has to refetch the data for the caller, though often that's not that important, for example because the processing is done after a multi-page form sequence.
Not necessarily, there are scenario's where you retry a couple of times before giving up (essentially restarting the transaction but with the same entities).
Simplistic ones only, given that the DTC / System.Transaction functionality does not support it.
True, though that's why I said you start effectively a new transaction (DTC/ADO.NET transaction) with the same data. Though often you need additional info from the user as well, like in the situation where a constraint violation (unique constraint for example)
or concurrency problem occured and the save failed, and the user has to perform an action, for example merge data, alter data etc. before a retry can be performed. This then of course demands that any transaction is ended before control is given back to the
user, as any user interaction is not allowed during a transaction (dtc/ado.net). However it ALSO means that the data might be reused and thus has to be in the same state as it was when the first transaction (dtc/ado.net) started.
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
FransBouma
Participant
1509 Points
312 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 02, 2005 08:16 PM|LINK
I'm not aware of an O/R mapper that doesn't support that, every O/R mapper has at least one paradigm where they support a central 'context' or 'session' which produces the objects you want, making the entity objects persistence unaware.
It's persistence as a service, you apply the service to the entity object to get persisted (and vice versa). Keep in mind that some O/R mappers still offer lazy loading with this, which opens the gate to the database in your PL layer even though you don't have a session around. This can lead to the ability to side-track the BL tier for fetching/saving data, and in large(r) teams that's definitely not what you want. Our Adapter paradigm supports true isolation of persistence core and entity objects for example for this purpose.
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
WardB
Member
16 Points
3 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 03:40 AM|LINK
I'm jumping in a little late to the party, forgive me. The quality of discussion is amazingly high. It's great to see Frans weighing in (I have tremendous respect for his work and his contributions on persistence subjects). LLBLGen, WORM, and Hibernate all seem to be fine products. No bashing from me.
I saw my company's product, IdeaBlade, mentioned a few times and thought it might be useful to make myself available for questions and to add some thoughts if I can be helpful. We take a rather different approach to ORM than the other products mentioned here so I suspect translation of terms may resolve some quandries.
Our distinctiveness is due substantially to our support for smart client apps which can operate disconnected for long intervals [so why I am I here on an ASP forum, right?]. Object-Relational Persistence is an important component in what we do but not our whole story which extends to UI databinding and deployment. We care alot about usability and ease of learning so we put much energy into the GUI designers and documentation.
I'm not trying to post a commercial. I'm just positioning us with respect to the other products you've covered. To make that evident, let me confess that our strongly-typed object query language is not as rich as Hibernate or WORM's (haven't looked as closely at LLBLGen yet). Customers haven't complained but there are definitely useful queries that we cannot express in our OQL. Our fall back is SQL PassThru, Stored Procedures, or (worst case) Remote Procedure Calls.
BTW, we have recently added support for Identity columns (for SQL Serverso far) . The prior work-arounds were not ideal. We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been assigned and used as foreign keys by other objects.
Let me illustrate. Suppose you add new Order 'A' with its new order items 'A1', 'A2'.... You make a small change to Order's existing Customer 'C' and then save the entire bunch transactionally. The db gives you a new Id for A which you stick in the ParentOrderId columns of A1, A2, etc. Unfortunately, you get a concurrency failure because someone modified Customer C and saved it before you did. The database roles back the transaction but your A1, A2, etc are sitting in cache with A's fixed up ID - an ID that it can never actually have. That ID is gone forever. What do you do? I wonder how other vendors manage this.
Moving on. Would you be willing to amplify on your comments about intuitiveness, collections, and inheritance? Happy to have this conversation elsewhere if this is not the right place.
Just to get the juices flowing, let's talk about inheritance. As Frans made clear, it's easy to change "types" in a database table but it's - shall we say - "problematic" to have a Manager class that inherits from Employee (nor should SalesRep nor BoardMember nor PartTime, to mention a few of the other potential hats the Employee may put on and take off). On the other hand, it makes good sense for a number of otherwise un-related classes to inherit common behavior from a base class. For example, I might want some subset of my entities to share the same logging or auditing behaviors. The ORM facility should enable the developer to insert an Audit support class into the inheritance chain. That Audit class is probably not a persistable entity in its own right.
So what's your use case for inheritance?
Regards to all,
Ward
thona
Member
20 Points
2923 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 08:44 AM|LINK
You are pretty bad in your english, because you DID post just a commercial, you know.
Somehow your statement really reminds me of the "this is not spam" lines in - spam.
Plus:
We finally found a way to cope with the problem of transaction rollbacks of new objects after their Identity column ids have already been assigned and used as foreign keys by other objects.
Total non-issue. See, as id-columns are not supposed to be end user known identities (like order number), loosing numbers is just a total non-issue. This is basic SQL 101 for beginners. There is no need for a solution unless you make up a problem by abusing SQL features for things they are not. It is a typical beginner mistake to abuse them as things like order numbers.
For example, I might want some subset of my entities to share the same logging or auditing behaviors. The ORM facility should enable the developer to insert an Audit support class into the inheritance chain.
Yeah. Or you use a nicely designed API that is not bolloks and that exposes events that an audut subsystem can pick up. Like "object changed, object created, object deleted". For auditing there is ZERO need to insert audit support into the inheritance chain at all, if the O/R mapper in question (or the business architecture) provides anything in the vincinity of a peroperly designed object model.
So what's your use case for inheritance?
For anyone knowing OO: the use case for inheritance is explicitly described in the basicos of object orientation, and is for a specialization case. Just athat a lot of people (like you, and a lot of others on the board) take the employe/job specialization (which is the TYPICAL case for "doing it wrong", out of the text books) does not mean it is a useless feature. Factually, every text book about object orientation names some good examples. If you, sadly a provider of what you call an O/R mapper, have a problem with basic OO terms - maybe you should read something like Scott Ambler's "The Object Primer", first. And understand the need for things like data driven inheritance - which is a hugh saver on certain situations (just not the idiotic "employee is a manager" abuse).
I can give you an example of data driven inheritance. http://www.powernodes.com/, a CMS driven by our EntityBroker. It's main storage architecture is developped around a "ContentItem" hierarchy, like a file system. A ContentItem contains an Items collection that contains it's children. There are more than 100 subclasses of ContentItem, most with their own separate data (in separate tables). There is a lot of specializations that is possible there. From a generic distinction between "Folder" and "File", so specializations of File (Document, Product, lots of different things). A classical data driven inheritance example - we use a four character field to distinguish types, and everything happens "automatically". An example out of a textbook. Without it, we could not easily extend the user interface, and application logic, and introduce additional ContentItem subclasses.
FransBouma
Participant
1509 Points
312 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 08:51 AM|LINK
Thank you! [:)]
I think there isn't an O/R mapper query language which can express everything that SQL can. Most can do expressions (WHERE field1 + field2 = (field3/value * field4) etc.) and aggregates, groupby + having, subqueries and joins in all styles, but for example custom UDF calls, CASE and system function calls are somewhat less common, left alone derived tables (SELECT * FROM (Select foo, bar from table) AS bla) or subselects in the select list. I'm not sure they add that much as well. We support expressions, aggregates etc. but not UDF's, CASE and system function calls. These can be added manually by implementing an interface (our query language (it's not really a language) is completely typed, no strings used) but it's still not smoothly as typing SELECT ISNULL(foo, "") AS ValueFoo FROM... .
Personally I think it's also not that necessary per se to have these kind of features. I mean, if you do, you either end up with a query language very similar as SQL or SQL in a different syntax. What a query language should more focus on is providing filters on objects or views on objects (sets defined on fields in related objects), which make it a bit different than what 100% SQL provides.
LLBLGen Pro has build in field value versioning for this. The system uses a versioning mechanism which saves all values of the entities in a transaction and if a commit is executed, these values are thrown away, but if a rollback is issued, the saved values are rolled back. This system is multi-level (you can save as much versions as you'd like and roll back to any saved version later on), and can be used by the developer as well, to save field values of an entity or entities in code, for example during each step of a wizard or prior to opening an edit screen. I'm planning to do some O/R mapper cronicals blogposts soon in which I'll discuss this more in depth. It's actually a very interesting topic and very complex when you dig deeper than just the entity fields.
Because, why stop there? What you really want (and what I've failed to implement thus far, as anyone else, it's very complex, though I have an idea how to solve it) is graph versioning. So you have your C, your A and its A1, A2 etc. You pass it into an edit form, all kind of things happen there, C gets a new address entity assigned, another Order B gets a new B4 orderitem etc. After a while, the user clicks 'Cancel' (these people exist [;)]). What to do? Your graph with on the root entity C, is changed in various places. You could of course fetch them all back, but what if some entities were new and not yet saved?. Simply rolling back field values isn't sufficient, as some entities have got new related entities which have to be removed and replaced by the old referenced entity (in the example of C's old Address entity). It looks simple at first, but once you start, you end up in a real complex environment where everything seems to affect everything else, and rolling back one thing affects another, so rolling back that one has to be done as well etc...
I've spend the last 3 months implementing inheritance support and ran into a lot of these questions as well. One of the main errors I made in the beginning was that I fell into the trap of defining types which were really instances. An example of that is for example "Marketing Department Europe", derived from 'Department'. That's wrong, it should be 'Marketing Department', as of a type there should be able to be more than one instance. This might sound like 'Duh!' to some people, but it's not that trivial in all cases: a lot of people run into the classic Person <-Employee and Person <-Customer problem where Employee is also a Customer.
Your example is a good one as it illustrates 2 types of inheritance in O/R mapper environments:
1) pure type inheritance to create distinctive types and per-type relations
2) inheritance to add / change behavior through polymorphism or by adding new code.
An example of 1) can be: Employee <- Manager <- Boardmember. Employee has a relation with Department which illustrates that the Employee works there. Manager has a relation with department to illustrate that manager is a manager of that department and Boardmember has a relation with 'CompanyCar' which is a hierarchy of its own, but which illustrates the companycar of that boardmember.
This inheritance chain clearly distinguishes types so relations can be defined in either of them which aren't available in other types, for example only boardmembers have a companycar (it's not a friendly company to work for [;)]. ). As this is an example, you can shoot holes in it of the size of the atlantic ocean, but you'll get the idea.
An example of 2) can be your example you mentioned with the auditing code.
1) can be seen as the supertype/subtype definitions in NIAM/ORM (http://www.orm.net) Though behavior is not really addable there. So to add 2) to such hierarchies, you can go for the strategy pattern, in where you inject an object (an auditing engine for example) which takes care of the auditing if required.
You can also decide to add these types to the supertype/subtype chain directly of course, though you then run into the problem of single-inheritance in .NET and it might be nice to combine it with AOP (spring.net).
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
FransBouma
Participant
1509 Points
312 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 09:37 AM|LINK
I think he meant that the ID field in the object now had a value, while the transaction was rolled back. This means that if you keep on using that entity object with that ID, it might look like it has a valid value for that field while it doesn't. I don't think he meant that there were gaps created in sequenced numbers, as you can't solve that anyway as you said.
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
thona
Member
20 Points
2923 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 09:49 AM|LINK
Using a tiered approach, the data of bhe objects would goto a non-chatty DAL and the return values would then be submitted to the objects only after the db operations have been performed. At least this is how we do it in the EntityBroker.
Plus, Transaction 101 and Windows standard approaches (COM+) demand that transactions having an exception are not resubmitted but thrown away, which means - the issue is a non issue again.
FransBouma
Participant
1509 Points
312 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 12:16 PM|LINK
That would require a serious structure to keep track of all the new ID's in a hierarchical save, while you could solve it more elegantly by leaving it to the objects (delegation of responsibility), at least, if you have a lot of new entities in a hierarchy and all require identity values: after each save, you have to sync the new values with FK fields for their save, and so forth.
Not necessarily, there are scenario's where you retry a couple of times before giving up (essentially restarting the transaction but with the same entities).
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
thona
Member
20 Points
2923 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 16, 2005 12:26 PM|LINK
At the price of not being able to use the DAL efficiently through a remoting scenario (not the technology - the scenario). Once your DAL has to do this anyway, the infrastructure is there anyway. Also, as/if you are throwing change events, you want to suppres/delay them until the DAL has finished. But basically, to properly support any form of layer-border between the DAL and your runtime as tiers, you need this anyway.
Not necessarily, there are scenario's where you retry a couple of times before giving up (essentially restarting the transaction but with the same entities).
Simplistic ones only, given that the DTC / System.Transaction functionality does not support it.
Caddre
All-Star
26581 Points
5308 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 17, 2005 12:21 AM|LINK
Here in Texas we say y'all be sweet. lol
Gift Peddie
FransBouma
Participant
1509 Points
312 Posts
Re: WORM, NHibernate and reviews of any other ORMappers
Aug 17, 2005 08:45 AM|LINK
Only when you use remoting in a connected scenario: you have a graph in your BL tier, you send it off to the DAL, effectively sending packed data to the dal on another server, it's been processed there, results come back and merged with the entity objects in the BL tier. I know your setup handles remoting transparently, though in a lot of situations, remoting isn't transparent but a given system aspect. There you know you're communicating with a remote service, and send the data, by value(!) to the service for processing. After that send action, the life of the object has effectively ended. This indeed requires other ways of writing your software: you first gather information, and send it off to the BL for processing, which processes it for example by sending the graph to the service. Processing done. If the caller requires the info back, the BL has to refetch the data for the caller, though often that's not that important, for example because the processing is done after a multi-page form sequence.
True, though that's why I said you start effectively a new transaction (DTC/ADO.NET transaction) with the same data. Though often you need additional info from the user as well, like in the situation where a constraint violation (unique constraint for example) or concurrency problem occured and the save failed, and the user has to perform an action, for example merge data, alter data etc. before a retry can be performed. This then of course demands that any transaction is ended before control is given back to the user, as any user interaction is not allowed during a transaction (dtc/ado.net). However it ALSO means that the data might be reused and thus has to be in the same state as it was when the first transaction (dtc/ado.net) started.
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)