How to Parse an EntityDataReader

2007.12.18: Please accept my apologies – today I discovered (and fixed) two bugs in the parsing code. One is a silly error. The other is that collections should be treated as primitive types and refs with regard to the number of fields. The code bellow includes the changes.

2007.12.04: Last week I discovered that the pattern I had listed in this post did not catch unwrapped primitive types and ref’s. So I’m fixing it now. This is the pattern for Beta 3. I see that it could be simplified (in the highlighted sections) if the FieldMetadata collection was populated for non-structural types. I will propose the change for RTM, but there is no guarantee it will be accepted.

Although you can successfully parse an EntityDataReader by only reasoning over its physical shape using record.GetType(i), you cannot reconstruct the entire entity-level structure without using EDM metadata. For instance, you can easily figure that something is coming as a nested DbDataRecord, but you cannot know whether that is an entity type, a complex type, or a row type. To get the exact entity-level structure, EDM metadata must be used.

 

Following is an extraction from a class that “visits” an arbitrary EntityDataReader. The highlighted portion is the place where the EDM metadata is obtained.

 

 

        // We want to make sure the passed DbDataReader

        // is actually an EntityDataReader.

        public void VisitReader(EntityDataReader reader)

        {

            VisitReader_(reader);

  }

 

 

        // Nested collections are assembled as internal BridgeDataReader’s.

        // That’s why we can’t reuse the public method.

        private void VisitReader_(DbDataReader reader)

        {

            while (reader.Read())

            {

                // We’ll call this method with internal BridgeDataReader’s

                // that implement IExtendedDataRecord.

                VisitRecord_(reader as IExtendedDataRecord);

            }

        }

 

 

        // The metadata is exposed through IExtendedDataRecord.

        private void VisitRecord_(IExtendedDataRecord record)

        {

// First get the type kind of the record itself.

// A record is not necessarily a strucutral type.

BuiltInTypeKind recordTypeKind = record.DataRecordInfo.RecordType.EdmType.BuiltInTypeKind;

int fieldCount;

// For RefType, PrimitiveType, and CollectionType the FieldMetadata collection is not populated.

// In those cases the record contains exactly one field.

if (recordTypeKind == BuiltInTypeKind.RefType ||

recordTypeKind == BuiltInTypeKind.PrimitiveType ||

recordTypeKind == BuiltInTypeKind.CollectionType)

{

fieldCount = 1;

}

else

{

fieldCount = record.DataRecordInfo.FieldMetadata.Count;

}

for (int i = 0; i < fieldCount; i++)

            {

                // If the field is flagged as DbNull, the shape of the value is undetermined.

                // An attempt to get such a value may trigger an exception.

                if (record.IsDBNull(i))

                {

                // ... (Render NULL)

                }

                else

                {

BuiltInTypeKind fieldTypeKind = recordTypeKind;

// If the record is a structural type, examine the field with the given index.

if (fieldTypeKind == BuiltInTypeKind.EntityType ||

fieldTypeKind == BuiltInTypeKind.ComplexType ||

fieldTypeKind == BuiltInTypeKind.RowType)

{

fieldTypeKind = record.DataRecordInfo.FieldMetadata[i].FieldType.TypeUsage.EdmType.BuiltInTypeKind;

}

switch (fieldTypeKind)

                    {

                        case BuiltInTypeKind.CollectionType:

                            // Collections are surfaced as nested BridgeDataReader’s.

                            VisitReader_(record.GetData(i));

                            break;

                        case BuiltInTypeKind.EntityType:

                            // Entities are wrapped in BridgeDataRecord’s.

                            VisitRecord_(record.GetDataRecord(i) as IExtendedDataRecord);

                            break;

                        case BuiltInTypeKind.ComplexType:

                            // Complex type instances are wrapped in BridgeDataRecord’s.

                            VisitRecord_(record.GetDataRecord(i) as IExtendedDataRecord);

                            break;

                        case BuiltInTypeKind.RowType:

                            // Row instances are wrapped in BridgeDataRecord’s.

                            VisitRecord_(record.GetDataRecord(i) as IExtendedDataRecord);

                            break;

                        case BuiltInTypeKind.RefType:

                            // Ref’s are surfaced as EntityKey instances. The containing record sees them as atomic.

                            VisitKey_(record.GetValue(i) as EntityKey);

                            break;

                        case BuiltInTypeKind.PrimitiveType:

                            // Primitive types are surfaced as plain objects.

        // ... (Render record.GetValue(i).ToString())

                            break;

                        default:

                            // Unhandled type kind. We shouldn't end up here.

        // ... (Render fieldTypecand.ToString())

                            break;

                    }

                }

            }

        }

        private void VisitKey_(EntityKey key)

        {

        // ... (Render key.EntitySetName)

            foreach (EntityKeyMember keyMember in key.EntityKeyValues)

            {

    // ... (Render keyMember.Key)

    // ... (Render keyMember.Value)

        }

        }

The code above uses the following namespaces:

 

using System.Data;

using System.Data.Common;

using System.Data.EntityClient;

using System.Data.Metadata;

using System.Data.Metadata.Edm;