Those are the rules, even when you don’t like them
Originally posted at 11/4/2010
Recently I had an interesting support call for NHibernate. The problem was a bit complex when explained to me, but we got it simplified to something like:
When we have a component that contains a set, that component is not null, even when all the members are null.
The problem is actually an intersection of two separate rules in NHibernate:
- When all the members of a component are null, the component itself will be null.
- A set is never null, an empty set is still a valid instance of a set.
When you add a set to a component, you add something that is never null. Hence, the behavior of NHibernate in this case is also valid, since we have a non null member value, there will be an instance of that component.
The problem is that the users conceptually thought of the empty set as null as well. I had some hard time explaining that sets are never null, and that no, this isn’t a bug or unexpected combination of the two features. Both behave exactly as expected, and the intersection of both worked as expected. In fact, trying to make it not work in this fashion would introduced a lot of work, complexity and additional queries.
Comments
This feature makes sense from an NHibernate developer's point of view - as you said yourself: "trying to make it not work in this fashion would introduce a lot of work, complexity and additional queries."
But from the user perspective it could be puzzling. Perhaps the fact that you needed to explain this means it's doing something that's unintuitive to someone not as fluent in the source as you are.
Or maybe not.
It seems they probably didn't know the difference between null and empty.
Empty = Known collection of no values
Null = No idea what the collection is, might be empty or might not; we just don't yet have the data.
I agree with Peter. Many people have a difficult time understanding null. null doesn't exists, empty exists, there just isn't anything in it.
It makes sense because usually it is a bad idea to allow collections/sets to be null.
I do think that it would be more intuitive to initialize a new component instance even if all the properties are truely null.
I guess if you don't try to remotely understand the concept of a set it could be confusing.
It's just one of the fundamentals of Discrete Math.
Any collection should never be null. A collection can be empty, but not null. It's not even NHibernate related if you ask me.
It's just bad practice if one of your types contains a collection that can be null.
I also urge my developers to also initialize strings to string.empty so you don't have to use object.Equals to do string comparisments without prevalidating (string.IsNullOrXxx) you're strings. But a string is the only class that I use as if it we're a structure..
Assume you have Parent having collection of Children.
Now, how you represent the following facts?
We didn't yet collect information about children.
We did collect information and there is no children,
We did collect information and there is exactly n children.
We did collect information and there is at least n chldren (but maybe more).
What happens if you have a struct property in a component? Is the default value considered NULL or the component will be initialized?
@Dave, "I also urge my developers to also initialize strings to string.empty "
I guess that if you obey that convention throughout your code, it might be safe. But what if you actually want a string to have an empty value and that is significant?
The purpose of null is to inform you that a field's value has not been set yet. I just have an extension method IsNullOrEmpty which tests for both in one go. In fact, I use IsNullOrEmtptyTrimmed most of the time because I am normally not interested in blank strings.
Reminds me of the vigorous discussion we had with our Oracle DBA who was of the opinion empty strings should be treated as nulls, due to the mistake Oracle made in their SQL implementation.
Happily, he lost :)
Ignoring the bullshit comments about "the difference between null and empty" and "Set Theory", The question still remains:
How do I define a nullable component if that component has a collection in it? It's a perfectly valid OO notion and if NH can't translate that to DB terms it's lacking a feature IMO.
@Roy
however, considering the edge case when one wants a set inside a component (which is a valuetype DDD wise), I don't see why one ever needs it.
@Frans
How about an email component, with its own data and behavior. This can be a value type reused by multiple entities in the system, and it can certainly be nullable.
It works quite well until I add a "UsedFor" collection to the email. NH breaks the nullability silently - with no warning, instead of null Email components we start getting non-null instances with null Address properties. This breaks the model because Address was defined as not-null in the component.
class Email
{
<emailusagecategory UsedFor {get;set;}
}
// Categories are not hard coded
class EmailUsageCategory
{
public string Name {get;set;}
...
}
Roy, thank you for the example,your example made this post clear to me . And surely something is broken when non-nullable fields are allowed to be null - or at least it shouldn't be called the 'expected' behavior.
I always thought components were a bit of a strange way to map something, so I'm not entirely surprised by this behaviour. I guess if you hit this problem, your options are:
Model your code in such a way that the component is never null (e.g. invariants, default populated values), or
Make the component an entity and reference it via a one-to-one (not nullable) or one-to-many relationship (nullable), or
Write a custom IValueType
@Roy: unless I am mistaken, your Email class cannot be mapped as a component if it is reused by multiple entities?
@Richard: We reuse components all the time. I consider it one of the best uses of this feature.
As for the workaround, we'll probably end up masquerading the NH behavior using a property that wraps the NH-initialized component and returns null instead of default values.
All these 'rules' are just complex ORM-technology-related excuses in their nature.
Comment preview