Using the Entity Framework (LINQ to Entity) to IDataReader Adapter

In a recent article, I developed a theoretical basis for an adapter that allows for the adaptation of Entity Framework (EF) entities into IDataReader-implementing form, and announced the preview availability of this software. 

Herein I describe with more specificity the use of the adapter generator and the resultant adapters, and announce the release of the first production-ready version of the adapter generator software.

To introduce the use of the IDataReaderAdapter generator, consider a hypothetical software package with a data-layer defined as:

public interface IDataLayer
    {
    IDataReader GetSomeData();
    IDataReader GetSomeOtherData(int aParameter);
    }

Business-process code interacts with this data logic exclusively through the IDataLayer interface; this not only creates a clean separation of concerns, but also allows for run-time specification of arbitrary IDataLayer implementation (typically a la dependency injection).  This might be concretely implemented as:

public class DataLayer : IDataLayer
    {
    public IDataReader GetSomeData()
        {
        return SqlHelper.ExecuteReader(connectionString, "GetSomeData");
        }

    public IDataReader GetSomeOtherData(int aParameter)
        {
        return SqlHelper.ExecuteReader(connectionString, "GetSomeOtherData", aParameter);
        }
    }

For those developers wishing to move to Microsoft’s Entity Framework (EF), this presents a problem.  There is no ready method by which one may convert an EntityObject into an IDataRecord (and, similarly, an IEnumerable<EntityObject> into IDataReader).  Without such a conversion, it is not possible to transition to the EF without breaking the IDataLayer interface contract.  For those applications that are widely deployed (or have strong third-party augmentation), this is a very real problem indeed.

The IDataReaderAdapter generator allows for such a conversion in two ways:

  1. Compile-time adaption an EF schema into a set of IDataRecord-implementing wrappers without the use of reflection (and of O(n) property-lookup performance), or
  2. Run-time adaption of an EntityObject (or any) object to an IDataRecord-implementing counterpart through the use of reflection.
Developers are free to choose the method of adaption based upon his or her particular needs.

Note: The adapter generator may be used to adapt any objects into IDataRecord/Reader-implementing objects.  Though the functionality described herein largely deals with the EF, the generator may be applied against any arbitrary object.

Method 1: Compile-Time Adaption of an Entity Framework Schema

To produce IDataRecord/Reader-implementing adapters at compile-time, we execute the adapter generator utility and point it at our EF schema.  The utility is executed in one of two ways, with syntax as:

IDataReaderAdapterGenerator
        /entities:[.NET Entity Framework EDMX file]
        /output:[output class filename]
        [/entities:[entity regex]
        [/language:vb|c#]

IDataReaderAdapterGenerator
        /assembly:
        /output:[output class filename]
        [/objects:[entity regex]
        [/language:vb|c#]

  • Entities/Assembly: Here, the first parameter may either be an EF Entity Data Model XML (EDMX) file or an arbitrary assembly.  The adapter generator will, assuming the former, parse the EDMX, extract entity objects, and create adapters.  If an assembly is specified, adapters are generated for each publicly-exposed object in that assembly.
  • Output: This parameter specifies the output file for the generated adapters (i.e.; “Samples.cs” or “Samples.vb”).
  • Entities/Objects: This setting specifies the particular entities or objects that are adapted.  This is specified as a regular expression (to adapt all entities or objects containing the word “Data” one would use the pattern “.*Data.*”).  By default, all available objects are adapted.
  • Language: This allows specification of the target language for the generated adapters, either VB.NET or C#.

Method #2: Run-time Adaption of Objects

As an alternative to compile-time generation of IDataRecord/Reader-implementing objects, arbitrary objects may also be adapted at run-time.  Note, however, that this flexibility is achieved at the expense of the additional overhead required for the reflection used during access.  Consider this cost carefully — it is likely that compile-time adaption should be utilized wherever possible.

Utilizing IDataRecord/Reader Adapters

Irrespective of the method selected above, the resultant adapters are used in much the same manner.  By way of example, consider the data model that would result when the concrete DataLayer above were converted to utilize the EF.  This model would likely contain two entities, SomeData and SomeOtherData, and would be structured as follows:

If this EDMX existed in a file named SampleDataModel.edmx, we would adapt the model as:

IDataReaderAdapterGenerator /entities:SampleDataModel.edmx /output:AdaptedSample.cs

And we would implement our modern IDataLayer-implementing layer as:

using BrandonHaynes.Data;public class ModernDataLayer : IDataLayer
    {
    // Expose our entity model for new code to access
    public ObjectContext EntityModel
        { get { return new SampleDataModel()); }    public IDataReader GetSomeData()  

        {
        using(var context = new SampleDataModel())
            return new DataReaders.SomeDataDataReader(context.SomeData);
        }

    public IDataReader GetSomeOtherData(int aParameter)
        {
        using(var context = new SampleDataModel())
            return new DataReaders.SomeOtherDataDataReader(context.SomeOtherData
                .Where(someOtherData => someOtherData.id == aParameter));
        }
    }

Note the use of BrandonHaynes.Data.DataReaders.SomeDataDataReader and BrandonHaynes.Data.DataReaders.SomeOtherDataDataReader to adapt returned EntityObjects (context.SomeData and context.SomeOtherData.Where(…)) into IDataReader-implementing interfaces.

To adapt objects at run-time using reflection, we would substitute these per-object adapters with the use of BrandonHaynes.Data.ReflectedDataReaderAdapter (or ReflectedDataRecordAdapter).  This would, for example, read as:

   return new ReflectedDataReaderAdapter(context.SomeData);

Conclusion

Herein we described the command-line use of the IDataReader Adapter generation tool to produce IDataRecord and IDataReader-adapting objects.  These objects may be used to modernize provider-based data layers that heavily rely on these interfaces, without breaking the existing contract. 

The project site, in addition to the source for both generators, includes two samples that should be reviewed by those interested in implementing these techniques in a production environment.  The first includes a sample model similar to that described herein, while the second includes a sample DotNetNuke data provider.  We will revisit this latter sample in subsequent entries as we explore advanced applications of the generated objects.

This entry will be followed by an exploration in more detail of the actual generated IDataReader and IDataRecord-adapting objects, including a review of the actual generated code.  For compile-time generated objects property conversion is performed in O(n) time with the mere addition of a constant-time hash (and lazy-loaded initialization).

As always, comments and feedback are greatly appreciated.

B

Be Sociable, Share!

Comments are closed.

Log in