tags:

views:

395

answers:

8

This is related conceptually to my question here. However, I've been playing around with NHibernate, and realized what the real core of my question is.

In classic OO design, to properly encapsulate data, it's a common pattern to have values passed in to an object's constructor that are stored in data members (fields). Those values which should not be changed are exposed with only accessors (read-only properties). Those which it is permissible to change have both accessors and mutators (read-write properties). It seems to me that a proper O/RM should respect these conventions, and use the available constructors when creating an object. Relying on read-write properties, reflection, or other, hackish (IMHO) methods seems...wrong.

Is there a .NET O/RM solution out there that does this?

EDIT

To address Praveen's point, I know that there are projects that have a 'default' algorithm for choosing a constructor - StructureMap, for example, always uses the constructor with the most arguments, unless you mark a constructor with a custom attribute. I can see this being an effective way handle the situation. Perhaps utilizing an IoC container in addition to the ORM would provide the sort of solution I'm needing - though it seems to me that this is, while not inherently bad, an unnecessary additional step for using an ORM.

+2  A: 

Unfortunately this is impossible in .NET without you marking up the constructors in some way.

The method signature stored for each constructor in the assembly metadata only contains the type of each parameter to the constructor. There's no way for ANY .NET ORM to really know which constructor to use. All the ORM sees is something like this:

.ctor()
.ctor(string, string)
.ctor(string, string, string)

There's no way for the ORM to know which .ctor parameter corresponds to the FirstName, LastName and MiddleName for your Customer object for instance.

In order to give you this support, a .NET ORM must support reading in custom attributes that you define for each parameter. You'd need to markup your constructors like this:

public Customer([Property("FirstName")] string FirstName, [Property("LastName")] string LastName, [Property("MiddleName")] string MiddleName)

This has 2 disadvantages:

  1. There is no way (that I can think of, someone will probably correct me), that this can go into a mapping file.
  2. You'd still need to write the same mapping as you've always done, because the ORM still needs to be able to get individual values for each property.

So you'd need to do all this extra work marking up the constructors and at the same time, you'd still need to map your classes EXACTLY as you were doing before.

Praveen Angyan
I don't object to mapping classes at all - in fact, I prefer it to marking up source code. You do raise some interesting points about the inherent difficulties though - thanks.
Harper Shelby
"The method signature stored for each constructor in the assembly metadata" also includes the parameter name, not only its type. The ORM could match parameters to properties by name without requiring any mapping (convention over configuration). There's still the issue of which ctor to use, though.
Lucas
@Lucas: Very true - the .NET Reflector does show the names of the parameters. Who knows - maybe I've just found an itch I need to scratch here.
Harper Shelby
+3  A: 

I think most of ORMs actually support this concept, at least DataObject.Net does. This code works as it expected:

[HierarchyRoot(typeof(KeyGenerator), "Id")]
public class Message : Entity
{
  [Field]
  public int Id { get; private set; }

  [Field(Length = 100)]
  public string Text { get; private set; }

  public Message(string Text)
  {
    Text = text;
  }
}

EDIT: DataObjects store data in internal transactional state, and use special materialization constructor, generated by PostSharp. Of course it is not so trivial if ORM use poco objects.

Alex Kofman
A: 

That depends what you consider values that should not be changed. Autoincrements, calculated columns, etc., are good candidates for this.

It is certainly possible, I use an ORM that I wrote, and it throws an exception if you attempt to set the value of a read-only property.

Update:

Remember constructors are used for persisted data as well. It is a common pattern to have your object accept the PK(s) in the constructor, and it will automatically fetch that record.

RedFilter
If it's creating properties, wouldn't that make it a code generator rather than an O/RM (or is it both)?
Harper Shelby
It's both! There is no rule that an ORM must dynamically generate the mappings.
RedFilter
If your PK is a surrogate key, using a PK in a constructor would, to me, be a bit of a code smell. If the *business* key is the PK, then that makes a bit more sense, but in that case I'd suggest that constructing the object is the wrong approach - passing the PK in to a repository would be a better route.
Harper Shelby
If that is a code smell to you, then how do you feel about hashtables?
RedFilter
I don't see how hashtables relate to this at all.
Harper Shelby
Sorry for the opaqueness. What I am saying is that retrieving an object by its key is a common paradigm in computing. Extending that to constructors does not seem offensive to me. Doing it in an ORM doesn't seem any different than creating a new FileStream by passing the file path, in this case the "key" to the data.
RedFilter
I don't see the parallel there though - the database primary key (assuming it's a surrogate key) has nothing to do with the domain model. If you have code outside the DAL (in business layer or UI code) asking for an object by the database PK, then you've just opened up your non-DAL layers to details of your implementation.
Harper Shelby
A: 

It is - in the general case - not possible for an OR mapper to only use constructors. Assume a object that is initialized by the values passed to the constructor and after that the state is modified by method calls. In this case the object might be persisted with a state that is no valid initial state and hence rejected by the constructor.

So there might by such a OR mapper, but it will definitly have limitations. As illustrate by the given example I would not consider bypassing the object encapsulation by an OR mapper as bad design, but as a requirement in some situations.

Daniel Brückner
I can't see this as a valid argument. If an object's persisted state is valid for a constructor, it should be valid for any point in an object's lifetime. Any object which has its valid states change during its lifetime is likely to be something you don't persist (or persist only partially), or perhaps in need of refactoring.
Harper Shelby
The argument goes the other direction - a state valid during the life time is not neccessarily valid at construction time. Take as a example a life cycle management system for what ever, maybe projects. They are always created with a initial state and then the state changes while the project goes on until you reach a finished, closed, or archived state. You don't even need a consructor that takes the state as an argument because the state is always implicitly the initial state. But of course you must be able to persist and retrieve the project in any state.
Daniel Brückner
I think your assertion is wrong - I see no reason why a project object couldn't be instantiated in an open, finished, archived, or any other *valid* state. If a project can't be archived until finished, then having an archived state with a finish date that hasn't yet occurred is invalid, but having a newly instantiated project be in an archived state is not necessarily so. The 'greedy' constructor selection method would actually support this as well (a fewer- or no-arg ctor wouldn't be selected by default, but be available if it makes sense to have it).
Harper Shelby
It would be wrong from an design perspective to have a constructor accepting a state - every newly created project must start in the initial state. It just doesn't make any sense to create a new project in a non-iniial state. Hence adding a constructor that accepts a state would be possible, but bad design.
Daniel Brückner
How? The object (what is created by the constructor) is a representation of the domain entity - it is not the entity itself. Object lifetime is not equivalent to domain entity lifetime - or we'd have no need to discuss O/RM tools at all!
Harper Shelby
I don't think so. We create the entity exactly once - we create the project entity when we start the project. From this point on the object lives in memory or in the database. We have to recreate a object when we retrieve it from the database, but we don't recreate the entity.
Daniel Brückner
And because (I believe) the design should reflect the problem domain (a constructor without explicit state), not technical details (a constructor with explicit state only required the reconstroct the object representing the entity), I consider it bad design to include such a constructor because the OR mapper is able to recreate the object without it.
Daniel Brückner
A: 

Why do you think this feels wrong ? Do you want your OR/M to perform business logic when reconstituting an object ? IMHO, no.

When you load an object from the DB, then the OR/M should be able to reconstitute the object, no matter what. Setting some value while reconsituting, should not lead to trigger some kind of logic that changes another value (that maybe should be given a value by the ORM as well ... )

Even so, I think a constructor should only contain parameters for those fields that are mandatory at object-creation time to have the object in a 'valid' state. Next to that, you might have public read-only properties that have been given a value by some calculation, and that need to be persisted as well.
If you feel reflection is a 'smell' to reconstitute objects, how are you going to deal with this situation ? You'll have to somehow create a public method which would be able to set that 'read only' value, but this breaks encapsulation, and you don't wan't that either.

Frederik Gheysels
What kind of public read-only property, created by a calculation, would exist that is not a part of an objects persisted state? What data would that calculation work on? If it's data from the object, then you could readily reconstitute it. If it's data external to the object, I'd argue that either the data or the read-only field is in the wrong place.
Harper Shelby
@Harper: "If it's data from the object, then you could readily reconstitute it", in a convenient read-only property! it's analogous to a calculated column in a DB
Lucas
Or, what about somekind of 'status' flag that you have in your object.Suppose you have a flag 'PropertyChanged' that you set whenever the user changes a property, but you do not want to have this flag set when the object is loaded from the DB.
Frederik Gheysels
A: 

Mr. Skeet suggested a 'builder' pattern a while ago. I made it a public class within the POCO class. It has the same properties as POCO, but all read/write. POCO is readonly where necessary, but PRIVATE SET. That way the builder can set the properites when the POCO is instantiated and not have funky args for a constructor. Kind of 'popcicle immutability' there.

n8wrl
This seems to violate the DRY principle - I'm personally opposed to repetitive tasks, especially when I think they're not actually the right approach.
Harper Shelby
I understand. What bugs me about the constructor-parameters approach is it is yet-another-place for me to list the properites. Constructor params list them; constructor setting private fields lists them; and the fields themselves... Have to change a parmeter? brrrr
n8wrl
Yeah...even the C++ method (initializer lists) isn't *much* better. However, the constructor parameter approach does come closer to matching the OO principles I was taught waaaaaay back in the day (OK, 10 years ago - I'm not that old!).
Harper Shelby
A: 

I agree with one of the previous posters that constructors should be for creating an object at the beginning of its lifetime. Using a constructor to hydrate an existing object that is currently in an archived state is counter-intuitive at best.

What needs to be considered, and what all major ORMs seem to be missing at this point, is that re-constituting a domain object from its last known state, as stored in a database or any other data store, is NOT inherently a constructing or modifying operation; so neither constructors or property setters should be used. Rather, the framework mechanism that most closely corresponds to this kind of operation is serialization! There are already recognized patterns for storing an object's state and then later reconstituting it through the ISerializable interface, the standard serialization constructor, etc. Persisting an object to a database is fundamentally no different from persisting it to a stream; in fact, one of the values of the StreamingContextStates enumeration that is used during serialization is Persistence!. IMHO, this should be the standard approach when designing any persistence mechanism. Unfortunately, I don't know of any ORM that supports this out of the box.

It should also be noted that an object that has been designed for serialization is still POCO; persistence ignorance has NOT been violated. The key point here is that a domain object should know best what data is needed to persist and restore it, as well as in what order that data should be restored (which often matters). What the object should NOT have to know is the specific mechanism by which it will be stored: relational database, flat file, binary blob, etc.

Long before ISerializable existed, it was common (and not counterintuitive at all!) to use constructors to deserialize objects. A constructor creates an instance of an object - there is no inherent tie to the constructor call and the lifetime of the *domain entity*. The object, yes...but the object is a *representation* of the domain entity, not the entity itself.
Harper Shelby
+1  A: 

By Jove, I think I've got it!

Thanks to everyone for their input, but I'm going to have to answer my own question. I just spent an hour or so digging through the PoEAA catalog, contemplating OO principles, and mixing that in with deep thought about the C# language and the .NET framework.

The answer I came up with, the one need that I couldn't solve properly using constructors, ends up not being related to the constructor itself in the end. It's lazy loading!

Basically, without implementing the lazy loading inside your domain classes (a major no-no for persistence ignorance and flexibility), there's no way to do it without subclassing the domain classes. This subclassing is the reason that NHibernate requires virtual properties.

I still think that utilizing a constructor rather than reflection or some other method to populate the fields of the parent class would be better (at least for non-collections...lazy loading does have its place), but I definitely see where the no-arg constructor has its place.

Harper Shelby