Source

MongoDB.Emitter /

Filename Size Date modified Message
MongoDB.Emitter
MongoDB.Emitter.Test
1.7 KB
1.6 KB
7.1 KB
MongoDB.Emitter:  Emits strongly-typed wrappers for MongoDB.Driver.Document objects
http://objectcloud.com
(C) 2010 Andrew Rondeau
MIT License  (Open Source)

A module for use when working with MongoDB and C#. MongoDB.Emitter takes interfaces, and at runtime, generates
types that wrap Document objects from MongoDB.Driver. This allows for strongly-typed use of Document objects,
yet provides flexibility when handling different data types.  Due to run-time code generation; use of this
library requires high privilages.  It may not work when running in a tightly-controlled sandbox situation.

While I prefer to target C# 2.0; this library makes use of the new HashSet class in .Net 3.5.

***
NOTE:  You must provide your own copy of MongoDB.Driver.dll in the same location as this README in order to build!!!
***

This wrapper uses techniques that I document at: http://www.codeproject.com/KB/cs/AutoInterfaceImplementer.aspx

Usage:
----------------

(For an exhaustive set of samples, see MongoDB.Emitter.Test)

// Use the MongoDB.Emitter namespace
using MongoDB.Emitter;

...

// Declare interfaces.  You can use basic primitives, the ArrayWrapper class to wrap "arrays", and the
// DictionaryWrapper class to wrap "dictionaries"
public interface IParent : IDocumentWrapper
{
   int Value { get; set; }
   bool Value_IsPresent { get; set; }
   ArrayWrapper<long> Array { get; set; }
   DictionaryWrapper<string> NVPs { get; set; }
   IChild Child { get; set; }
}

public interface IChild : IDocumentWrapper
{
   int SubValue { get; set; }
}

// Code:
IParent parent = WrapperFactory.Instance.New<IParent>();
parent.Value_IsPresent // returns false
parent.Value = 5;
parent.Value_IsPresent // returns true now that Value is set
parent.Array = WrapperFactory.Instance.NewArrayWrapper<long>();
parent.Array.Add(234);
parent.NVPs = WrapperFactory.Instance.NewDictionaryWrapper<string>();
parent.NVPs["foo"] = bar;
parent.Child = WrapperFactory.Instance.New<IChild>();
parent.Child.SubValue = 33;

// You can still access and manipulate underlying Document objects
((Document)parent.Document["Child"])["SubValue"] // returns 33
((Document)parent.Document["Child"])["SubValue"] = 44;
parent.Child.SubValue // returns 44

Document d = MongoCollection.FindOne(...);
parent = WrapperFactory.Instance.New<IParent>(d);

Note:  Emitted types check DateTime's Kind property, and will throw an exception for non-UTC types.
Note:  It is possible to have properties that are of type Document.

Philosophy:
----------------

The vast variety of Object/Relational mappers and serialization libraries for C# (and Java) indicates that there is
no perfect approach to converting serialized persistant data into objects or structs easily consumed by C# (and
Java.)  All libraries, code generators, and design patterns have their strengths and weaknesses.

MongoDB.Emitter attempts to be a "good enough" approach for applications that anticipate some flexibility in their
data, but need strong types for developer convenience.  The goal is to allow rapid creation of strong types to assist
in working with Mongo Documents; but to stay out of the way when manipulating the Document object directly is best.

Another goal is to be simple to learn; more complicated mappers and serialzers can sometimes make simple data
representations significantly more difficult to implement due to their learning curve.  Sometimes it really is better
for a mapper to expose the lower-layers when they present a better API.  Objects of emitted types keep the "official"
version of data in Document objects; thus it's possible to mix use of emitted types and document objects.

The Emitter only works with types that have no methods.  This is because mapping into business logic types can create
logical inconsistancies; especially if business logic requires semantics or structures that the emitter is unable to
anticipate.  It is reccomended to restrict using emitted types to layers close to the database.

Advanced Usage and Customization:
----------------

Using the singleton instance of WrapperFactory is strongly reccomended unless special circumstances dictate that a
program have a small set of WrapperFactory objects.  This is because the .Net framework never garbage collects
generated code.  Each instance of WrapperFactory caches and re-uses code it generates for each type.  If the
programmer create a new instance of WrapperFactory each time a query is run; the program will run out of memroy at
some point, even if the WrapperFactories are garbage collected.

The Emitter doesn't support all types.  You can register custom converters by calling the
RegisterConverterDelegates<T> method.  It's also possible to build in new types by adding a call in
WrapperFactory_DefaultConversionDelegates.cs.

Inheritance is supported by registering a delegate that will override the wrapping type prior to wrapping a Document.
These are stored in the Deciders dictionary.

Schema evolution is supported by registering a delegate that will be called prior to wrapping a Document object.
These are stored in the Evolvers dictionary.  If a decider is specified, the evolver must be specified for all
potential types.

Wrapped objects are not thread-safe, but WrapperFactory is intended to be thread-safe.  (Thread-safety is untested.)

Performance:
----------------

Due to the underlying use of a Document object, using an Emitted object will incur a minimal performance cost over
the Document object.  Each instance of WrapperFactory stores generated types for re-use, so the act of generating a
type for re-use incurs minimal performence overhead.

The wrappers will perform best with properties that map to a primitives.  Dictionaries, and other wrapped objects
have some minor logic that's used to ensure that the wrapper keeps pointing at the wrapped object.  Accessing an
ArrayWrapper for the first time will copy and replace the default value.  This is due to unpredictable behavior
in MongoDB.Driver.

ArrayWrappers and Dictionaries that contain a wrapper type, instead of a primitive, do not cache the generated wrapper
object.  They will always re-wrap on each get.

Overall, performance of emitted types is limited by the symbolic nature of the Document itself; accessing values
by a symbolic string instead of by strongly-typed property incurs a marginal CPU cost.  In general, the emitter
shouldn't add noticable overhead to the Document API.

For situations where "every clock cycle is sacred," the emitter, and thus the Document object's API, will be too
slow.  In these situations, it is best to figure out how to expose a BSON reader and populate strongly-typed objects
in an optimized manner.

Default Supported Types:
----------------

long / long?
int / int?
double / double?
bool / bool?
DateTime / DateTime?  (Note, throws exception if setting a DateTime that isn't UTC)
string
byte[]
Guid / Guid?  (See note below)
Document
object
TimeSpan / TimeSpan?  (Note, stored internally as milliseconds)
Oid
DBRef

Regarding Guids:
-----------------
The current implementation of Guids requires explicit support for Guids in the Mongo driver.  This is present in
version 0.81.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.