ListOfParams and other horrible things that you shouldn’t bring to RavenDB
In the mailing list, we got asked about an issue with code that looked like this:
1: public abstract class Parameter2: {
3: public String Name { get; set; }4: }
5:
6: public class IntArrayParameter : Parameter7: {
8: public Int32[,] Value { get; set; }9: }
I fixed the bug, but that was a strange thing to do, I thought. Happily, the person asking that question was actually taking part of a RavenDB course and I could sit with him and understand the whole question.
It appears that in their system, they have a lot of things like that:
- IntParameter
- StringParameter
- BoolParameter
- LongParameter
And along with that, they also have a coordinating class:
1: public class ListOfParams2: {
3: public List<Param> Values {get;set;}4: }
The question was, could they keep using the same approach using RavenDB? They were quite anxious about this, since they had a need for the capabilities of this in their software.
This is why I hate Hello World questions. I could answer just the question that was asked, and that was it. But the problem is quite different.
You might have recognized it by now, what they have here is Entity Attribute Value system. A well known anti pattern for the relational database world and one of the few ways to actually get a dynamic schema in that world.
In RavenDB, you don’t need all of those things. You can just get things done. Here is the code that we wrote to replace the above monstrosity:
1: public class Item : DynamicObject2: {
3: private Dictionary<string, object> vals = new Dictionary<string, object>();4:
5: public string StaticlyDefinedProp { get; set; }6:
7: public override bool TryGetMember(GetMemberBinder binder, out object result)8: {
9: return vals.TryGetValue(binder.Name, out result);10: }
11:
12: public override bool TrySetMember(SetMemberBinder binder, object value)13: {
14: if(binder.Name == "Id")15: return false;16: vals[binder.Name] = value;17: return true;18: }
19:
20: public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)21: {
22: var key = (string) indexes[0];23: if(key == "Id")24: return false;25: vals[key] = value;26: return true;27: }
28:
29: public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)30: {
31: return vals.TryGetValue((string) indexes[0], out result);32: }
33:
34: public override IEnumerable<string> GetDynamicMemberNames()35: {
36: return GetType().GetProperties().Select(x => x.Name).Concat(vals.Keys);37: }
38: }
Not only will this class handle the dynamics quite well, it also serializes to idiomatic JSON, which means that querying that is about as easy as you can ask.
The EAV schema was created because RDBMS aren’t suitable for dynamic work, and like many other things from the RDMBS world, this problem just doesn’t exists for us in RavenDB.
Comments
I think I just had an epiphany.
Yeah, the fact that I now know that dynamic objects will work just fine stuffing into RavenDB is something that I think will be really handy :)
It's funny that with few simple (uh, not for everyone) additions a RDBMS can become much more flexible. First is multi-valued fields (array fields), and the second one is a dictionary column (key-value container without a schema). PostgreSQL has them both (hstore and array columns) with querying and indexing capabilities in SQL. I hadn't had a chance to try it yet but it looks like a nice workaround for these limited by flat schema rigidity. And as a cherry on top of that theres the 'GIN' index for full text searching... are these guys trying to compete with NoSQL or what?
@Rafal i've seen blogs about Postgres's addition of the "dictionary column".
If I ever walked into an organization and saw them stuff crap like that into a RDBMS, i'd walk right out the door.
Just because the tool now supports this, it doesn't mean there is an sanity to the approach.
Comment preview