DynORM is an open source project which develop a new ORM (object-relational mapping) that can work with relational data (tables and its records) which structure (set of fields, its type, set of constraints etc) can be changed even in run-time. “Dyn” for that reasons stands for “dynamic”). DynORM is written on C# and available with its source code.

The main difference from other well-known ORM is that DynORM incorporates “convention vs configuration” approach. This ORM is almost zero-configuration. So instead of creating some huge amount of xml files that describe how your domain classes map to ER-model you leave this work for DynORM. Focusing on development of your business logic layer (BLL) all interaction with database restricted to calling appropriate DynORM adapter’s methods when you need store, retrieve, update or delete your domain objects. Database is used more like a “black box” in this concept. You do give up some control of how database objects named but, as advantage, all the work with database is accomplished automatically. For example, even when you modify your domain classes (say, you add some new members, collections with related objects, etc) and after that connect with this recompiled code to database with the previous version of generated ER-model DynORM update it automatically in run-time and there is no need for you regenerate any xml mapping files or do any other work.


DynORM has the following features:
  • it is almost zero-configuration ORM (you need to apply only several custom attributes to you BLL classes' members);
  • mapping CLR types to corresponding RDBMS' types is done automatically by DynORM. Not null constraint to fields (or in contrary nullability) in db applied automatically based on which CLR type was used to define corresponding class' member (all you need to do is use nullable CLR types like int?, decimal? etc);
  • possibility of using composite primary keys (composed out of several class' members);
  • lazy loading can be applied when you retrieve objects from db;
  • optimistic lock logic is supported by DynORM automatically which gives you multy-user environment out of the box;
  • several modes of deletion, including cascade deletion;
  • several retrieving logics, including retrieving with predicate defined in terms of BLL classes' members (not db fields), retrieving the complete graph of related objects and some others;
  • there are custom attributes and appropriate implemented logic which helps to define classes with read-only members which identify objects and the object's parameterful constructors which DynORM will use automatically to create object and init those id members.
  • you can define several types of relation between BLL classes (one-to-many, many-to-many etc) just using one custom attribute in your class definition;
  • it can work with different RDBMS engines:
    • MSSQL,
    • MySQL,
    • PostgreSQL,
    • Oracle.
    • Support of other RDBMS can be added by means of implementation IDAL interface (see documentation and source code for details);
  • although it doesn't need any configuration file which describes structure of tables it works with, it's possible to retrieve structure of data and data itself and serialize/deserialize it to/from xml;
  • just because DynORM can serialize/deserialize structure of tables and its records and because DynORM supports several RDBMS it can be used to easily deploy datastructure among servers with different database engines. Serialized structure of tables described in CLR datatypes.
and some other features.

Without any additional forewords let take a look at quick example and how simply it can be used.
Suppose you have a business logic layer (BLL) class which is POCO (plain old C# object). In order to store the information encapsulated in this class' objects all you need is just apply several custom attributes to some members of the class.

        public class TestBLL
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute]
            public Decimal DecimalProperty { get; set; }

            [FieldAttribute]
            public string StringProperty { get; set; }

            [FieldAttribute]
            public bool BoolProperty { get; set; }

            [FieldAttribute]
            public bool? BoolPropertyNullable { get; set; }

            [FieldAttribute]
            public DateTime? DateTimePropertyNullable { get; set; }

		...
        }


As you can see, you just need to mark properties (or fields, but using of them is discouraged because properties give better encapsulation) which you want to use as identification of the class' objects with PrimaryKeyAttribute and fields you want to store in database with FieldAttribute. You can optionally use TableAttribute attribute to give table that DynORM will create the name (by default the class' name is used). In order to store in database relation between objects you just need apply ForeignKeyDeclarationAttribute (more about it in the examples in the following sections) and continue to use the class' member that references to related objects. You can apply several other attributes and its combinations to implement more complicated ER models but at minimum you need only these several attributes.
You can use custom attributes without any parameters if you satisfied with defaults or can provide your preferences (like varchar type's length, numeric type's scale and precision):

        [TableAttribute(TableName = "Parent")]
        public class TestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField1", Attributes=new object[] { 12, 5 })]
            public Decimal DecimalPropCustomNameAndSize { get; set; }

            [FieldAttribute(FieldName = "SomeField2")]
            public Decimal DecimalPropCustomName { get; set; }

            [FieldAttribute(Attributes=new object[] { 64 })]
            public string StringPropertyCustomSize { get; set; }
        }


Optionally, in order to use lazy logic, optimistic locking logic you can inhered your BLL class from BLLSuperType class.
After you've done this you just call BLLAdaptiveMapper's methods that help you to store, retrieve, update and, if necessary, delete objects like this:

            //create instance of the mapper
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL);

            //init demo BLL object
            Guid id = Guid.NewGuid();
            TestBLL bllObj = (TestBLL)mapper.Create(typeof(TestBLL), id);
            bllObj.DecimalPropCustomName = 1;
            bllObj.DecimalPropCustomNameAndSize = 2;
            bllObj.StringPropertyCustomSize = "some init value";

            //store it in db
            mapper.Write(bllObj, true);



Generic method cab used as well

            //create instance of the mapper
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL);

            //init demo BLL object
            Guid id = Guid.NewGuid();
            TestBLL bllObj = mapper.Create<TestBLL>(id);
            bllObj.DecimalPropCustomName = 1;
            bllObj.DecimalPropCustomNameAndSize = 2;
            bllObj.StringPropertyCustomSize = "some init value";

            //store it in db
            mapper.Write<TestBLL>(bllObj, true);




When you create adapter connection string to your db and db type (MSSQL, MySQL, PostgreSQL, Oracle, etc) can be passed in ctor or can be defined in app config file. When you store the object DynORM discover object's structure (members that should be stored, how object should be identified) and create necessary db objects (tables, relations) in run-time.
Later when you need to retrieve the stored object you can use the code like this (other retrieving logics are supported as well, inc retrieving with simple predicates, lazy loading, retrieve of complete graph of related objects and so on)

            TestBLL retrievedObj = (TestBLL)mapper.Read(typeof(TestBLL), id);


Alternatively generic method can be used.

            TestBLL retrievedObj = mapper.Read<TestBLL>(id);


Note, that non-generics methods of BLLAdaptiveMapper class are defined to receive object. The structure of the class which instance have been passed into mapper's method will be discovered in run-time. And in-run time DynORM will discover if there is an appropriate table in db (and related db structures like constraints) to which connection string you passed to store the object. If there is such a table it will be used. Otherwise DynORM will create the table it need and store object's data in it.

How to start use DynORM in a project.

There are three steps that should be accomplished in order to start use DynORM in some project:
1. Add reference in the project to DynORM's assemblies;
2. Custom attributes should be applied to appropriate members of your BLL classes to make them storable in db (in order to use lazy logic, locking logic inhered your BLL class from BLLSuperType class);
3. Instanciate BLLAdaptiveMapper and use appropriate method of BLLAdaptiveMapper object when store, retrieve, update or delete your BLL classes' objects (you can provide mapper with connection string to your db and db type (MSSQL, MySQL, PostgresSQL, Oracle, etc) passing them into its constructor or config file can be used).

DynORM adaptive mapper.

In general to interact with db you need to use one of the methods of BLLAdaptiveMapper.
BLLAdaptiveMapper implements IBLLAdaptiveMapper interface. The complete list of members of IBLLAdaptiveMapper interface is following:

    public interface IBLLAdaptiveMapper
    {
	#region generic methods
		
        T Create<T>(params object[] bllObjIDs);
        void Delete<T>(T bllObj);
        void Delete<T>(params object[] bllObjIDs);
        void DeleteCascade<T>(T bllObj);
        void DeleteCascade<T>(params object[] bllObjIDs);
        T Read<T>(params object[] bllObjIDs);
        IEnumerable<T> Read<T>();
        IEnumerable<T> Read<T>(IEnumerable<BLLComparison> predicate);
        T ReadDeep<T>(params object[] bllObjIDs);
        IEnumerable<T> ReadLazy<T>();
        T ReadLazy<T>(params object[] bllObjIDs);
        IEnumerable<T> ReadLazy<T>(IEnumerable<BLLComparison> predicate);
        IEnumerable UploadLazy<T>(T bllObj); //IEnumerable of objects not T because children can be any type
        IDictionary<IPrimaryKey, object> UploadLazyDeep<T>(T bllObj); //Dictionary of objects not T, because children can any type
        void SubstituteChildInObj<T1, T2>(T1 parent, T2 newchild, T2 oldchild);
        void Write<T>(T bllObj, bool updateStructure);
        void WriteDeep<T>(T bllObj, bool updateStructure);
        void UpdateStructure<T>();
		
	#endregion


        #region methods
		
        object Create(Type bllObjType, params object[] bllObjIDs);
        void Delete(object bllObj);
        void Delete(Type bllObjType, params object[] bllObjIDs);
        void DeleteBatch(object[] bllObjs);
        void DeleteCascade(object bllObj);
        void DeleteCascade(Type bllObjType, params object[] bllObjIDs);
        object Read(Type bllObjType, params object[] bllObjIDs);
        IEnumerable<object> Read(Type bllObjType);
        IEnumerable<object> Read(Type bllObjType, IEnumerable<BLLComparison> predicate);
        object ReadDeep(Type bllObjType, params object[] bllObjIDs);
        IEnumerable<object> ReadLazy(Type bllObjType);
        object ReadLazy(Type bllObjType, params object[] bllObjIDs);
        IEnumerable<object> ReadLazy(Type bllObjType, IEnumerable<BLLComparison> predicate);
        IEnumerable UploadLazy(object bllObj);
        IDictionary<IPrimaryKey, object> UploadLazyDeep(object bllObj);
        void SubstituteChildInObj(object parent, object newchild, object oldchild);
        void Write(object bllObj, bool updateStructure);
        void WriteBatch(object[] bllObjs, bool updateStructure);
        void WriteDeep(object bllObj, bool updateStructure);
        void UpdateStructure(Type bllObjType);

        #endregion
    }


All the non-generics methods defined to receive object because DynORM discover members of the object and create appropriate structures in db to store them in run-time. Structure of tables and relations between them in db is very similar to typical ER-model used in relational databases traditionally except that all the tables and relations are made automatically. You can focus on implementation of you BLL delegating all the work with db to DynORM.

Examples of typical use-cases.

Supported data types.

DynORM supports almost all widely used base datatypes. In addition custom typed members can be defined in class (like RelatedObj property in the example below) and if ForeignKeyDeclarationAttribute attribute is applied to the custom type DynORM will create separate table linked with foreign key constraint to the table of defining class to store the data (more about it in one-to-many and many-to-many sections).
It is possible to have any other members in the class that you need (methods, events, nested classes, not attributed with FieldAttribute and PrimaryKeyAttribute fields and properties and so on). DynORM can see only attributed with its custom attributes members.
Here is an example of class that defines several members with supported by DynORM datatypes:

        public class TestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID", 0)]
            public Guid ID { get; set; }

            //this type will be mapped to numeric with scale and precision of 12, 5 in MSSQL. Mapping for other RDBMS see in documentation
            [FieldAttribute(Attributes=new object[] { 12, 5 })]
            public Decimal DecimalPropCustomSize { get; set; }

            //this type will be mapped to numeric with scale and precision of 15, 3 (default values in DynORM) in MSSQL. Mapping for other RDBMS see in documentation
            [FieldAttribute]
            public Decimal DecimalProperty { get; set; }

            [FieldAttribute]
            public Decimal? DecimalPropertyNullable { get; set; }

            //this type will be mapped to varchar with length of 128 (default value in DynORM) in MSSQL. Mapping for other RDBMS see in documentation
            [FieldAttribute]
            public string StringProperty { get; set; }

            //this type will be mapped to varchar with length of 64
            [FieldAttribute(Attributes=new object[] { 64 })]
            public string StringPropertyCustomSize { get; set; }

            [FieldAttribute]
            public bool BoolProperty { get; set; }

            [FieldAttribute]
            public bool? BoolPropertyNullable { get; set; }

            [FieldAttribute]
            public DateTime DateTimeProperty { get; set; }

            [FieldAttribute]
            public DateTime? DateTimePropertyNullable { get; set; }

            [FieldAttribute]
            public Double DoubleProperty { get; set; }

            [FieldAttribute]
            public Double? DoublePropertyNullable { get; set; }

            [FieldAttribute]
            public Single SingleProperty { get; set; }

            [FieldAttribute]
            public Single? SinglePropertyNullable { get; set; }

            [FieldAttribute]
            public Int16 Int16Property { get; set; }

            [FieldAttribute]
            public Int16? Int16PropertyNullable { get; set; }

            [FieldAttribute]
            public Int32 Int32Property { get; set; }

            [FieldAttribute]
            public Int32? Int32PropertyNullable { get; set; }

            [FieldAttribute]
            public Int64 Int64Property { get; set; }

            [FieldAttribute]
            public Int64? Int64PropertyNullable { get; set; }

            [FieldAttribute]
            public Guid GuidProperty { get; set; }

            [FieldAttribute]
            public Guid? GuidPropertyNullable { get; set; }

            private List<ChildTestBLL> relatedBLLObjs = new List<ChildTestBLL>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<ChildTestBLL> RelatedBLLObjs
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.PostgreSQL));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.PostgreSQL));
                    this.relatedBLLObjs = value;
                }
            }
        }



Note that the class in the example above uses lazy loading logic and that's why DebuggerBrowsable(DebuggerBrowsableState.Never) attribute is applied. Otherwise data uploading will happen everytime you run the Visual Studio in debug mode when VS will try to show the property's current value.
There is no need for properties to be auto-propery (defined like this { get; set; }). Getter and setter can be implemented and the logic in them will be triggered at the moment when DynORM will try to get or set values from them.
There is no differences in how you store and retrieve these objects using BLLAdaptiveMapper.
Lazy loading can be used when you load up related object as usual in DynORM as well.


When the class in the example above was stored in MSSQL we've got the following table:

datatypes.png

One-to-many relation.

To implement relations between the classes you just apply ForeignKeyDeclarationAttribute attribute like in the following example.

        [TableAttribute(TableName = "Parent")]
        public class ParentTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField1", Attributes=new object[] { 12, 5 })]
            public decimal SomeProperty1 { get; set; }

            [FieldAttribute]
            public string SomeProperty2 { get; set; }

            private List<ChildTestBLL> relatedBLLObjs = new List<ChildTestBLL>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<ChildTestBLL> RelatedBLLObjs
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.PostgreSQL));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.PostgreSQL));
                    this.relatedBLLObjs = value;
                }
            }
        }

        [TableAttribute(TableName = "Child")]
        [ForeignKeyDeclarationAttribute(typeof(ParentTestBLL), RelationQuantity.OneOrZeroToManyOrZero, "RelatedBLLObjs", 0)]
        public class ChildTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Child_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField3")]
            public int SomeProperty3 { get; set; }

            [FieldAttribute]
            public string SomeProperty4 { get; set; }

        }



ForeignKeyDeclarationAttribute attribute applied to class which will be a child in the relations. In ForeignKeyDeclarationAttribute attribute you need to declare parent type ((typeof(ParentTestBLL) in the example), relation quantity ( in the above example RelationQuantity.OneOrZeroToManyOrZero) , class' member in the parent class that holds references to children (RelatedBLLObjs in our example) and an index of the foreign key (in most case it equals to 0, but it can be usefull if there several relations (foreign keys) between the same two classes).

RelationQuantity is defined like this:

namespace DynORM.Interfaces.BLLMapper.Enum
{
    public enum RelationQuantity
    {
        OneToManyOrZero,
        OneOrZeroToManyOrZero,
        ManyOrZeroToManyOrZero,
        OneOrZeroToOneOrZero,
        OneToOneOrZero
    }
}



In case of relations that have many entities appropriate class member should implement IList and IEnumerable interfaces. No attributes are required to the member. In case of “one-*” relations the member can be public readable and writable property or just a public field. No attributes are required to the member.

Many-to-many relation.

Many-to many relation declared almost in the way as one-to-many. You need apply ForeignKeyDeclarationAttribute. The only difference is that the attribute should be applied to both classes that participate in the relation and the semantics of the attribute parameters a bit differ (see example below). Behind the scenes DynORM automatically create link entity in database (the special table that implements many-to-many relation) and it will always know which one is service which relation based on the name convention used in DynORM. It is even possible to have several many-to-many relation between the same two classes.
Here is an example of declaring the relation:

        [TableAttribute(TableName = "TestBLLEntity1")]
        [ForeignKeyDeclarationAttribute(typeof(TestBLL2), RelationQuantity.ManyOrZeroToManyOrZero, "RelatedBLLObjs1", 0)]
        public class TestBLL1 : BLLSuperType
        {
            [PrimaryKeyAttribute("TestBLLEntity1_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField1")]
            public int SomeProperty1 { get; set; }

            [FieldAttribute]
            public string SomeProperty2 { get; set; }

            private List<TestBLL2> relatedBLLObjs = new List<TestBLL2>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<TestBLL2> RelatedBLLObjs1
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    this.relatedBLLObjs = value;
                }
            }
        }

        [TableAttribute(TableName = "TestBLLEntity2")]
        [ForeignKeyDeclarationAttribute(typeof(TestBLL1), RelationQuantity.ManyOrZeroToManyOrZero, "RelatedBLLObjs2", 0)]
        public class TestBLL2 : BLLSuperType
        {
            [PrimaryKeyAttribute("TestBLLEntity2_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField3")]
            public int SomeProperty3 { get; set; }

            [FieldAttribute]
            public string SomeProperty4 { get; set; }

            private List<TestBLL1> relatedBLLObjs = new List<TestBLL1>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<TestBLL1> RelatedBLLObjs2
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    this.relatedBLLObjs = value;
                }
            }
        }



ForeignKeyDeclarationAttribute attribute applied to the classes which will be two sides in the relation. In ForeignKeyDeclarationAttribute attribute you need to declare parent type ((typeof(TestBLL1) in case of TestBLL2 and vise versa in the example), relation quantity (in the above example RelationQuantity.ManyOrZeroToManyOrZero), class' member in the class that holds references to related objects (RelatedBLLObjs2 in case of TestBLL2 in our example) and an index of the foreign key (in most case it equals to 0, but it can be usefull if there several relations (link entities between the tables) between the same two classes).
There is no differences in how you store and retrieve these objects using BLLAdaptiveMapper.
Lazy loading can be used when you load up related object as usual in DynORM as well.

Lazy loading.

When you have production level complexity of ER-model you have very complicated net of relations between tables. When you retrieve an object from the database it can turn out into situation when you retrieve a hundred if not more of objects. And if that was done just to read data from one member of retrieve object the overhead you created retrieveng all the related objects is not even necessary!
And in other cases you have cyclical references in you graph of related objects (the simplest example is many-to-many relation). So without any control of which object should be retrieve at which moment you can fall into endless loop.
In this case lazy loading pattern can help.
To use it in DynORM you need to accomplish several steps. Here is an example.
Suppose we define two classes like this:

        [TableAttribute(TableName = "Parent")]
        public class ParentTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField1", Attributes=new object[] { 12, 5 })]
            public decimal SomeProperty1 { get; set; }

            [FieldAttribute]
            public string SomeProperty2 { get; set; }

            private List<ChildTestBLL> relatedBLLObjs = new List<ChildTestBLL>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<ChildTestBLL> RelatedBLLObjs
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL));
                    this.relatedBLLObjs = value;
                }
            }
        }

        [TableAttribute(TableName = "Child")]
        [ForeignKeyDeclarationAttribute(typeof(ParentTestBLL), RelationQuantity.OneOrZeroToManyOrZero, "RelatedBLLObjs", 0)]
        public class ChildTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("Child_PK", "RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField3")]
            public int SomeProperty3 { get; set; }

            [FieldAttribute]
            public string SomeProperty4 { get; set; }

        }



Note that when you load lazy some object only its own fields are read. All the related object will be loaded up as soon as the first use of the property in which we store the children. That is why we have additional logic like in the example above in setter and getter. Note that DebuggerBrowsable(DebuggerBrowsableState.Never) attribute is applied. Otherwise data uploading will happen everytime you run the code the Visual Studio in debug mode when VS will try to show the property's current value.
So now we can create and store the objects and afterward we will read the parent lazy (so child will be uploaded only at the moment of the first use of parent's property)

            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, DBMSTypes.MSSQL);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.ReadLazy(typeof(ParentTestBLL), parentid);
            //BLLAdaptiveMapper activate it with reflection with parameterless ctor
            
            //retrievedParent.Mapper = mapper; this line commented out just because we init Mapper in property getter and setter. Uncomment it otherwise
            Assert.AreEqual<decimal>(parentbllObj.SomeProperty1, retrievedParent.SomeProperty1);
            Assert.AreEqual<string>(parentbllObj.SomeProperty2, retrievedParent.SomeProperty2);
            Assert.AreEqual<int>(parentbllObj.RelatedBLLObjs[0].SomeProperty3, retrievedParent.RelatedBLLObjs[0].SomeProperty3);
            Assert.AreEqual<string>(parentbllObj.RelatedBLLObjs[0].SomeProperty4, retrievedParent.RelatedBLLObjs[0].SomeProperty4);

            mapper.DeleteCascade(parentbllObj);



BLLSuperType-derived objects should be marked as loaded if children need to be stored as well as parent.
Lazy loading pattern in DynORM can be used only if your BLL classes derived from BLLSuperType. Otherwise exception will be thrown. In case of POCOs you can use only full read (not lazy read) logic.

BLLSuperType defined as followed:

    public class BLLSuperType
    {
        LoadStatus Status = LoadStatus.Ghost;

        public Boolean IsGhost
        {
            get { return this.Status == LoadStatus.Ghost; }
        }
        public Boolean IsLoaded
        {
            get { return this.Status == LoadStatus.Loaded; }
        }
        public Boolean IsLoading
        {
            get { return this.Status == LoadStatus.Loading; }
        }
        public void MarkAsGhost()
        {
                this.Status = LoadStatus.Ghost;
        }
        public void MarkAsLoading()
        {
            if (this.IsGhost)
                this.Status = LoadStatus.Loading;
        }
        public void MarkAsLoaded()
        {
            if (this.IsLoading || this.IsGhost)
                this.Status = LoadStatus.Loaded;
        }
        protected void Load(IBLLAdaptiveMapper mapper)
        {
            if (this.IsGhost)
            {
                mapper.UploadLazy(this);
            }
        }
    }



Encapsulation of IDs properties.

For better encapsulation of properties in which you store values that identify you BLL object you can implement them as read-only properties. In this case you should define paramterful constructor of the object. The number, order and type of parameters should be the same as the number, order and type of ids properties. This constructor should be attributed with PrimaryConstructorAttribute. After you've done this DynORM will be using this constructor when it will retrieve from db and instanciate objects. Here is an example of the class with this constructor:

        public class TestBLL
        {
            [PrimaryKeyAttribute("Parent_PK", "RowID_0", 0)]
            public Guid ID0 { get; private set; }

            [PrimaryKeyAttribute("Parent_PK", "RowID_1", 1)]
            public Guid ID1 { get; private set; }

            [PrimaryKeyAttribute("Parent_PK", "RowID_2", 2)]
            public Guid ID2 { get; private set; }

            [PrimaryKeyAttribute("Parent_PK", "RowID_3", 3)]
            public Guid ID3 { get; private set; }

            [PrimaryConstructor]
            public TestBLL(Guid id0, Guid id1, Guid id2, Guid id3)
            {
                this.ID0 = id0;
                this.ID1 = id1;
                this.ID2 = id2;
                this.ID3 = id3;
            }

        }



You can also use BLLAdaptiveMapper's Create method to simplify construction of the objects when you create them. Here is an example:

TestBLL bllObj = (TestBLL)mapper.Create(typeof(TestBLL), id0, id1, id2, id3);



Optimistic lock logic.

In multy-user environment should some kind of a mechanism that would help to avoid situations when two or more users try to modify data at the same time. These is several approaches to do that but one of them is optimistic lock pattern. In order to use this logic in DynORM all you need is just define a public read- and writable member in your class of int type and attribute it with OptimisticLockAttribute.
OptimisticLockAttribute attribute defined like this:

namespace DynORM.BLLMapper
{
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
    public class OptimisticLockAttribute : FieldAttribute
    { }
}



Here is an usage example:

            [OptimisticLock]
            public int DecimalProperty { get; set; }


When you have this kind of class its object (when stored) will be checked automatically. If they have the actual version of data they can be stored in database otherwise an error occurs preventing “lost update” issue.

In conclusion.

There is always severe lack of the time to document all the necessary aspects of the implemented logic ))) Additional topics will be posted later. Please feel free to add your questions, comments, found bugs reports in discussion sections. Thank you in advance for that! Hopefully the library will help in your projects!




Last edited Jan 12 at 8:56 PM by RomanGirin, version 17