0

I am looking to create a database row-to-object mapper for a production server.

My goal is to fill at a single location what are the database column name, and into which of this object's field to store a single row returned from a database query.

I am planning to later add functionalities to automatically fetch table name and connection string. The purpose of doing this is to centralize all database-related stuff (naming of columns and query string follows automatically, add protections on returned results, error logging, etc.)

I read a lot about generic set accessors thought I am currently unable to synthesize a solution from that.

Here is a (non-working) skeleton of what I am trying to achieve. All comments are welcomed, about optimizations and better practices, too.

Utilisation :

  

    /* get MediaInfo filled regarding CDbColumnFind criterias */
    CEventReportingMediaInfo MediaInfo = DbQuery(1, new CDbColumnFind() { Name = "uid", Value = "2" });

Here is the Db-Mappable base class

 

    public class DbMappableClass
    {
        /********************************************/
        /* required fields for database information */
        /********************************************/
        public string TableName;
        public IList TableColumns;

        /********************************************/
        /* generic object filling helper            */
        /********************************************/
        public void ReadObject(DataRow FromRow)
        {
            foreach (CDbColumn Column in TableColumns)
            {
                TryParseGeneric(FromRow[Column.Name].ToString(), Column.Field);
            }
        }

        /********************************************/
        /* conversion helper                        */
        /********************************************/
        public static bool TryParseGeneric(string src, T value)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));

            try
            {
                value = (T)converter.ConvertFromString(src);
                return true;
            }
            catch
            {
                value = default(T);
                CSysLog.WriteEntry(SysLogLevel.WARNING, "failed to convert src ({0}) to type ({1}), use default value ({2})", src.ToString(), typeof(T), value);
                return false;
            }
        }

        /********************************************/
        /* Db column type                           */
        /********************************************/
        public class CDbColumn
        {
            private readonly string name;
            private readonly SqlDbType sqltype;
            private readonly Type ctype;

            public CDbColumn(string Name, SqlDbType SqlType, object Field)
            {
                this.name = Name;
                this.sqltype = SqlType;
                this.Field = Field;
                this.ctype = Field.GetType();
            }

            public string Name { get { return name; } }
            public SqlDbType SqlType { get { return sqltype; } }
            public object Field { get; set; }
            public Type CType { get { return ctype; } }
        }

        /********************************************/
        /* Db Query type criterias                  */
        /********************************************/
        public class CDbColumnFind
        {
            public string Name;
            public object Value;
        }

        /********************************************/
        /* Generic Db Query                         */
        /********************************************/
        public static TReturnType DbQuery(int ExpectedCount, params CDbColumnFind[] Args) where TReturnType : DbMappableClass, new()
        {
            /* query database, obtain results in RowResults ... */

            TReturnType ReturnResult = new TReturnType();

            DataRow RowResults = TableResult.Rows[0];

            ReturnResult.ReadObject(RowResults);
        }
    }

Now is the custom, centralized declaration of a db-mappable derived object

 

    /********************************************/
    /* Event Reporting Media Table : Database   */
    /********************************************/
    public class CEventReportingMediaInfo : DbMappableClass
    {
        /**********************************/
        /** Member Declaration          ***/
        /**********************************/
        public int          Uid     { get; set; }
        public MediaType    Type    { get; set; }
        public string       IpAddr  { get; set; }
        public Int32        Port    { get; set; }
        public MediaStatus  Status  { get; set; }
        public int          Delay   { get; set; }


        /**********************************/
        /** Database Informations       ***/
        /**********************************/
        public CEventReportingMediaInfo()
        {
            TableName = "event_reporting_media_tbl";

            TableColumns = new ReadOnlyCollection (new[] {
                    new CDbColumn ("uid"           , SqlDbType.Int,            Uid),
                    new CDbColumn ("media_type"    , SqlDbType.TinyInt,        Type),
                    new CDbColumn ("media_ip"      , SqlDbType.VarChar,        IpAddr),
                    new CDbColumn ("media_port"    , SqlDbType.Int,            Port),
                    new CDbColumn ("media_status"  , SqlDbType.TinyInt,        Status),
                    new CDbColumn ("media_delay"   , SqlDbType.Int,            Delay)
            });
        }

        public enum MediaType
        {
            INVALID = 0,
            SIP_GATEWAY = 1,
            MODEM = 2
        }

        public enum MediaStatus
        {
            OFFLINE = 0,
            ONLINE = 1
        }
    }

How can I achieve to have specified fields (Uid, Type, IpAddr, etc) generically filled by ReadObject()? I need an equivalent to a pointer to members... I read about reflection too but I am not sure about efficiency and do not understand 100% how to implement it.

Note: I would prefer to not use full ORM solutions - my understanding is that it is not a stragegic long-term solution (less flexible for future implementations, support, etc - easier but not really enterprise-grade). I would like to keep control of the db mapping layer while reducing glitches occurence in future development on the production servers.

What do you guys think about this implementation, and what would be the best way to achieve my goals?

Thanks for your time, and with my best regards,

Morpheus

1 Answers1

1

Your guess was right: you will have to use Reflection.

(A bit of advice from a man who has done his share of DAL frameworks.

  1. Don't do it. There are lots of available frameworks for every choice and flavor. If you don't like full-fledged ORMs like Entity Framework or nHibernate - use light mappers as Dapper or BLToolkit. Otherwise you'll spend too much time inventing your own bicycle, and most likely you'll still get it wrong.
  2. If you still want to do it, plan on implementing LINQ, because it's simplest way (for developer) to build type-safe queries.
  3. If you still want to to it, don't force your "entity" classes to inherit from you predefined base class (DbMappableClass in your case). It breaks design, it breaks layering, it breaks simplicity.)
Sergei Rogovtcev
  • 5,804
  • 2
  • 22
  • 35