Monday, October 24, 2011

LINQ to Dynamics AX - Object Classes and Utilities (3)

Ok,
In my last post I covered the Data Access Layer, Query Provider, Query Translator, Expression, and a bunch of other assorted things we need to get our new provider going. In this section we are going to cover the objects and utilities for populating them, as well as the actual context provider itself. I can cover any questions in the comments section later, as I have less time to actually blog, I want to spend most of it giving you the code to get started with your own implementations. So to waste no time, lets jump in.


Create a class in our LINQTest project called ObjectReader, and copy in the following code.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Data.Common;

namespace LINQTest
{
    internal class ObjectReader<T> : IEnumerable<T>, IEnumerable where T : class, new()
    {
        Enumerator enumerator;


        internal ObjectReader(DbDataReader reader)
        {
            this.enumerator = new Enumerator(reader);
        }


        public IEnumerator<T> GetEnumerator()
        {
            Enumerator e = this.enumerator;
            if (e == null)
            {
                throw new InvalidOperationException("Cannot enumerate more than once");
            }
            this.enumerator = null;
            return e;
        }


        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }


        class Enumerator : IEnumerator<T>, IEnumerator, IDisposable
        {
            DbDataReader reader;
            FieldInfo[] fields;
            int[] fieldLookup;
            T current;


            internal Enumerator(DbDataReader reader)
            {
                this.reader = reader;
                this.fields = typeof(T).GetFields();
            }


            public T Current
            {
                get { return this.current; }
            }


            object IEnumerator.Current
            {
                get { return this.current; }
            }


            public bool MoveNext()
            {
                if (this.reader.Read())
                {
                    if (this.fieldLookup == null)
                    {
                        this.InitFieldLookup();
                    }
                    T instance = new T();
                    for (int i = 0, n = this.fields.Length; i < n; i++)
                    {
                        int index = this.fieldLookup[i];
                        if (index >= 0)
                        {
                            FieldInfo fi = this.fields[i];
                            if (this.reader.IsDBNull(index))
                            {
                                fi.SetValue(instance, null);
                            }
                            else
                            {
                                fi.SetValue(instance, this.reader.GetValue(index));
                            }
                        }
                    }
                    this.current = instance;
                    return true;
                }
                return false;
            }


            public void Reset()
            {
            }


            public void Dispose()
            {
                this.reader.Dispose();
            }


            private void InitFieldLookup()
            {
                Dictionary<string, int> map = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
                for (int i = 0, n = this.reader.FieldCount; i < n; i++)
                {
                    map.Add(this.reader.GetName(i), i);
                }
                this.fieldLookup = new int[this.fields.Length];
                for (int i = 0, n = this.fields.Length; i < n; i++)
                {
                    int index;
                    if (map.TryGetValue(this.fields[i].Name, out index))
                    {
                        this.fieldLookup[i] = index;
                    }
                    else
                    {
                        this.fieldLookup[i] = -1;
                    }
                }
            }
        }
    }
}

Now, because we have to support the SELECT implementation I am not done with, create a class called ProjectionReader, and copy in this code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Data.Common;

namespace LINQTest
{
    internal class ProjectionReader<T> : IEnumerable<T>, IEnumerable
    {
        Enumerator enumerator;


        internal ProjectionReader(DbDataReader reader, Func<ProjectionRow, T> projector)
        {
            this.enumerator = new Enumerator(reader, projector);
        }


        public IEnumerator<T> GetEnumerator()
        {
            Enumerator e = this.enumerator;
            if (e == null)
            {
                throw new InvalidOperationException("Cannot enumerate more than once");
            }
            this.enumerator = null;
            return e;
        }


        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }


        class Enumerator : ProjectionRow, IEnumerator<T>, IEnumerator, IDisposable
        {
            DbDataReader reader;
            T current;
            Func<ProjectionRow, T> projector;


            internal Enumerator(DbDataReader reader, Func<ProjectionRow, T> projector)
            {
                this.reader = reader;
                this.projector = projector;
            }


            public override object GetValue(int index)
            {
                if (index >= 0)
                {
                    if (this.reader.IsDBNull(index))
                    {
                        return null;
                    }
                    else
                    {
                        return this.reader.GetValue(index);
                    }
                }
                throw new IndexOutOfRangeException();
            }


            public T Current
            {
                get { return this.current; }
            }


            object IEnumerator.Current
            {
                get { return this.current; }
            }


            public bool MoveNext()
            {
                if (this.reader.Read())
                {
                    this.current = this.projector(this);
                    return true;
                }
                return false;
            }


            public void Reset()
            {
            }


            public void Dispose()
            {
                this.reader.Dispose();
            }
        }
    }
}
 

 Now, we are going to add our LINQ data class, named AddressState. Remember, this is a hand coded representation of the AddressState table from AX. It is an easy candidate for code generation via SQLDictionary, but we can cover that later.

using System;
using System.Collections.Generic;
using Microsoft.Dynamics.BusinessConnectorNet;
using System.Linq;
using System.Text;
using System.Data;

namespace LINQTest
{
    public class AddressState
    {
        // Properties.
        public string NAME;
        public string STATEID;
        public string COUNTRYREGIONID;
        public string DATAAREAID;
        public int TIMEZONE;
        public int RECVERSION;
        public long RECID;


        public DataTable MakeDataTable(AxaptaRecord ar)
        {
            DataTable dt = new DataTable("AddressState");
            DataColumn dc;
            DataRow dr;

            dc = new DataColumn();
            dc.ColumnName = "NAME";
            dc.DataType = typeof(string);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "STATEID";
            dc.DataType = typeof(string);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "COUNTRYREGIONID";
            dc.DataType = typeof(string);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "DATAAREAID";
            dc.DataType = typeof(string);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "TIMEZONE";
            dc.DataType = typeof(int);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "RECVERSION";
            dc.DataType = typeof(int);
            dt.Columns.Add(dc);

            dc = new DataColumn();
            dc.ColumnName = "RECID";
            dc.DataType = typeof(long);
            dt.Columns.Add(dc);


            while (ar.Found)
            {
                dr = dt.NewRow();
                if (ar.get_Field("NAME") != null)
                {
                    dr["NAME"] = (string)ar.get_Field("NAME");
                }
                if (ar.get_Field("STATEID") != null)
                {
                    dr["STATEID"] = (string)ar.get_Field("STATEID");
                }
                if (ar.get_Field("COUNTRYREGIONID") != null)
                {
                    dr["COUNTRYREGIONID"] = (string)ar.get_Field("COUNTRYREGIONID");
                }
                if (ar.get_Field("DATAAREAID") != null)
                {
                    dr["DATAAREAID"] = (string)ar.get_Field("DATAAREAID");
                }
                if (ar.get_Field("DATAAREAID") != null)
                {
                    dr["DATAAREAID"] = (string)ar.get_Field("DATAAREAID");
                }
                if (ar.get_Field("TIMEZONE") != null)
                {
                    dr["TIMEZONE"] = (int)ar.get_Field("TIMEZONE");
                }
                if (ar.get_Field("RECVERSION") != null)
                {
                    dr["RECVERSION"] = (int)ar.get_Field("RECVERSION");
                }
                if (ar.get_Field("RECID") != null)
                {
                    dr["RECID"] = (long)ar.get_Field("RECID");
                }
                dt.Rows.Add(dr);
                ar.Next();

            }

            return dt;
        }
    }
}
 



OK, notice the method MakeDataTable that takes the AxaptaRecord as an argument, and has a return type of DataTable. It's a very important part of this project, as it creates an set that supports enumeration and iteration. The point is here is that we are going to have hundreds of these classes, so the MakeDataTable method has to be implemented on each of them. Again, this will be done via code generation.


The last thing to do in this section is create our context class, so add a class named DynamicsAX to your project, and add the following code to it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;


namespace LINQTest
{
    public class DynamicsAX
    {
      
        public Query<AddressState> AddressState;

        public DynamicsAX(DAXDataAccessLayer connection)
        {
            QueryProvider provider = new DAXQueryProvider(connection);
            this.AddressState = new Query<AddressState>(provider);
         }

    }
}





That completes this section. You should now have a pretty slick LINQ to Dynamics AX provider.


In the next session, I will show you how to use it, if you haven't figured it out already :)


















No comments:

Post a Comment