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 :)
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment