Example #1
0
        /// <summary>
        /// Gets a POCO from the data record.
        /// </summary>
        /// <param name="dataRequest">
        /// The data request.
        /// </param>
        /// <param name="entityName">
        /// The entity name.
        /// </param>
        /// <param name="record">
        /// The data record to read.
        /// </param>
        /// <typeparam name="T">
        /// The type of POCO to generate.
        /// </typeparam>
        /// <returns>
        /// The POCO object as a type of <typeparamref name="T"/>.
        /// </returns>
        private static T GetPocoFromReader <T>(PocoDataRequest dataRequest, string entityName, IDataRecord record)
        {
            var baseKey = new Tuple <string, PocoDataRequest>(entityName, dataRequest);
            var baseDirectAttributes = dataRequest.AttributeDefinitions.Where(x => x.IsReferencedDirect).ToList();
            var basePocoDelegate     = PocoFactories.GetOrAdd(
                baseKey,
                key => new Lazy <PocoDelegateInfo>(() => FlatPocoFactory.CreateDelegate(dataRequest, typeof(T), baseDirectAttributes)))
                                       .Value;

            T poco;

#if DEBUG
            try
            {
                ////var recordItems = Enumerable.Range(0, record.FieldCount).Select(i => $"{record.GetName(i)}='{record.GetValue(i)}'");
                ////Trace.WriteLine($"Getting data from record {string.Join(",", recordItems)}");
#endif
            poco = (T)basePocoDelegate.MappingDelegate.DynamicInvoke(record);
#if DEBUG
        }
        catch (TargetInvocationException ex)
        {
            throw new OperationException(basePocoDelegate, $"Error creating a POCO for type {typeof(T)}: {ex.Message}", ex);
        }
#endif
            return(poco);
        }
Example #2
0
        /// <summary>
        /// Creates a delegate that returns a POCO from the provided data reader. TODO: Make private or coalesce into caller?
        /// </summary>
        /// <param name="dataRequest">
        /// The data request to create a POCO for.
        /// </param>
        /// <param name="type">
        /// The type of the POCO to create.
        /// </param>
        /// <returns>
        /// A <see cref="Delegate"/> that creates a POCO from the reader.
        /// </returns>
        public static PocoDelegateInfo CreateDelegate([NotNull] PocoDataRequest dataRequest, [NotNull] Type type)
        {
            if (dataRequest == null)
            {
                throw new ArgumentNullException(nameof(dataRequest));
            }

            return(CreateDelegate(dataRequest, type, dataRequest.AttributeDefinitions));
        }
Example #3
0
        /// <summary>
        /// Creates a delegate that returns a POCO from the provided data reader.
        /// </summary>
        /// <typeparam name="T">
        /// The type of POCO expected by the return statement.
        /// </typeparam>
        /// <param name="dataRequest">
        /// The data request to create a POCO for.
        /// </param>
        /// <returns>
        /// A <see cref="Delegate"/> that creates a POCO from the reader.
        /// </returns>
        public static PocoDelegateInfo CreateDelegate <T>([NotNull] PocoDataRequest dataRequest)
        {
            if (dataRequest == null)
            {
                throw new ArgumentNullException(nameof(dataRequest));
            }

            return(CreateDelegate(dataRequest, typeof(T)));
        }
Example #4
0
        /// <summary>
        /// Fills the return list with POCOs hydrated from the reader.
        /// </summary>
        /// <param name="reader">
        /// The reader to read.
        /// </param>
        /// <param name="entityDefinition">
        /// The entity definition for the POCOs.
        /// </param>
        /// <exception cref="OperationException">
        /// The <see cref="FlatPocoFactory"/> did not return a delegate of the expected type.
        /// </exception>
        private T FillReturnList(IDataReader reader, IEntityDefinition entityDefinition)
        {
            var pocoDataRequest = new PocoDataRequest(reader, entityDefinition, this.DatabaseContext);
            var mappingDelegate = FlatPocoFactory.CreateDelegate <T>(pocoDataRequest).MappingDelegate;

            if (mappingDelegate is Func <IDataReader, T> pocoDelegate)
            {
                var poco = pocoDelegate.Invoke(reader);
                return(poco);
            }
            else
            {
                throw new OperationException(
                          pocoDataRequest,
                          string.Format(CultureInfo.CurrentCulture, ErrorMessages.DelegateCouldNotBeCreatedWithReader, pocoDataRequest));
            }
        }
Example #5
0
        /// <summary>
        /// Gets a related entity POCO from the reader.
        /// </summary>
        /// <param name="entityAggregateName">
        /// The entity aggregate name.
        /// </param>
        /// <param name="dataRequest">
        /// The data request.
        /// </param>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="entityReference">
        /// The entity reference.
        /// </param>
        /// <returns>
        /// The related POCO as an <see cref="object"/>.
        /// </returns>
        private object GetRelatedEntity(string entityAggregateName, PocoDataRequest dataRequest, IDataRecord reader, EntityReference entityReference)
        {
            var relatedEntityLocation = this.definitionProvider.GetEntityLocation(entityReference);
            var relatedQualifiedName  = string.IsNullOrWhiteSpace(relatedEntityLocation.Alias)
                                           ? $"{relatedEntityLocation.Container}.{relatedEntityLocation.Name}"
                                           : relatedEntityLocation.Alias;

            var relatedKey = new Tuple <string, PocoDataRequest>($"{entityAggregateName}:{relatedQualifiedName}", dataRequest);

            // TODO: Cache attributes with their locations, or build explicitly.
            ////var relatedAttributes = entityDefinition.ReturnableAttributes.Where(x => x.ReferenceNode?.Value == relatedEntityLocation).ToList();
            var relatedAttributes = dataRequest.AttributeDefinitions.Where(x => x.ReferenceNode?.Value == relatedEntityLocation).ToList();
            var relatedType       = relatedEntityLocation.EntityType;

            var relatedPocoDelegate = PocoFactories.GetOrAdd(
                relatedKey,
                key => new Lazy <PocoDelegateInfo>(() => FlatPocoFactory.CreateDelegate(dataRequest, relatedType, relatedAttributes)))
                                      .Value;

            object relatedEntity;

#if DEBUG
            try
            {
#endif
            relatedEntity = relatedPocoDelegate.MappingDelegate.DynamicInvoke(reader);
#if DEBUG
        }

        catch (TargetInvocationException ex)
        {
            throw new OperationException(relatedPocoDelegate, $"Error creating a POCO for type {relatedType}: {ex.Message}", ex);
        }
#endif
            return(relatedEntity);
        }
Example #6
0
        /// <summary>
        /// Creates a POCO from the data reader.
        /// </summary>
        /// <param name="dataRequest">
        /// The data request.
        /// </param>
        /// <typeparam name="T">
        /// The type of POCO to return.
        /// </typeparam>
        /// <returns>
        /// A new POCO of the specified type.
        /// </returns>
        public T CreatePoco <T>([NotNull] PocoDataRequest dataRequest)
        {
            if (dataRequest == null)
            {
                throw new ArgumentNullException(nameof(dataRequest));
            }

            var reader = dataRequest.DataReader;

            if (typeof(T) == typeof(object))
            {
                // TODO: See if we can infer the object graph from the included attributes vs. the flatness.
                return(GetPocoFromReader <dynamic>(dataRequest, typeof(T).FullName, dataRequest.DataReader));
            }
            else
            {
                var entityDefinition = this.definitionProvider.Resolve <T>();
                var pocoKey          = GetPocoKey(entityDefinition, reader, entityDefinition.PrimaryKeyAttributes.OrderBy(x => x.Ordinal).ToList());
                var poco             = (T)this.pocoMemoryCache.GetOrAdd(
                    pocoKey,
                    key => new Lazy <object>(() => GetPocoFromReader <T>(dataRequest, entityDefinition.QualifiedName, reader)))
                                       .Value;

                var relationAttributes = entityDefinition.AllAttributes.Where(x => x.AttributeTypes == EntityAttributeTypes.Relation);

                foreach (var relationAttribute in relationAttributes)
                {
                    // TODO: Change this brittle code. We need to match on aliases to get the right IDs.
                    var relatedDefinition     = this.definitionProvider.Resolve(relationAttribute.PropertyInfo.PropertyType);
                    var relationReferenceName = string.IsNullOrWhiteSpace(relationAttribute.Alias)
                                                    ? $"{relatedDefinition.EntityContainer}.{relatedDefinition.EntityName}"
                                                    : relationAttribute.Alias;

                    var keyDefinitions = entityDefinition.ReturnableAttributes.Where(
                        x => (string.IsNullOrWhiteSpace(x.Entity.Alias) ? $"{x.Entity.Container}.{x.Entity.Name}" : x.Entity.Alias)
                        == relationReferenceName && x.IsPrimaryKey)
                                         .OrderBy(x => x.Ordinal);

                    var relatedPocoKey = GetPocoKey(relatedDefinition, reader, keyDefinitions.ToList());

                    var entityReference = new EntityReference
                    {
                        ContainerType = typeof(T),
                        EntityType    = relationAttribute.PropertyInfo.PropertyType,
                        EntityAlias   = relationAttribute.Alias
                    };

                    var relatedEntity = this.pocoMemoryCache.GetOrAdd(
                        relatedPocoKey,
                        key => new Lazy <object>(
                            () => this.GetRelatedEntity(entityDefinition.QualifiedName, dataRequest, reader, entityReference)))
                                        .Value;

                    // Prevents "ghost" POCO properties when resolving second-order or higher relations.
                    if (relatedEntity == null)
                    {
                        continue;
                    }

                    // Check whether this is a first-order relation.
                    if (relationAttribute.EntityNode == relationAttribute.EntityNode.List.First)
                    {
                        relationAttribute.SetValueDelegate.DynamicInvoke(poco, relatedEntity);
                    }
                    else
                    {
                        var relationContainer = NavigateToEntity(relationAttribute.ReferenceNode, poco);
                        relationAttribute.SetValueDelegate.DynamicInvoke(relationContainer, relatedEntity);
                    }
                }

                return(poco);
            }
        }
Example #7
0
        /// <summary>
        /// Creates a delegate that returns a POCO from the provided data reader. TODO: Make internal?
        /// </summary>
        /// <param name="dataRequest">
        /// The data request to create a POCO for.
        /// </param>
        /// <param name="type">
        /// The type of the POCO to create.
        /// </param>
        /// <param name="attributeDefinitions">
        /// The attribute definitions.
        /// </param>
        /// <returns>
        /// A <see cref="Delegate"/> that creates a POCO from the reader.
        /// </returns>
        public static PocoDelegateInfo CreateDelegate(
            [NotNull] PocoDataRequest dataRequest,
            [NotNull] Type type,
            [NotNull] IEnumerable <EntityAttributeDefinition> attributeDefinitions)
        {
            if (dataRequest == null)
            {
                throw new ArgumentNullException(nameof(dataRequest));
            }

            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            if (attributeDefinitions == null)
            {
                throw new ArgumentNullException(nameof(attributeDefinitions));
            }

            var definitions = attributeDefinitions.ToList();

            // Create the method
            var name      = $"poco_factory_{Guid.NewGuid()}";
            var method    = new DynamicMethod(name, type, new[] { typeof(IDataReader) }, true);
            var generator = method.GetILGenerator();
            var reader    = dataRequest.DataReader;

            if (type == typeof(object))
            {
                // var poco=new T()
                var constructorInfo = typeof(ExpandoObject).GetConstructor(Type.EmptyTypes);

                if (constructorInfo == null)
                {
                    throw new InvalidOperationException(
                              string.Format(CultureInfo.CurrentCulture, ErrorMessages.ConstructorInfoDoesNotExist, typeof(ExpandoObject)));
                }

                generator.Emit(OpCodes.Newobj, constructorInfo); // obj
                var addMethod = typeof(IDictionary <string, object>).GetMethod("Add");

                // Enumerate all fields generating a set assignment for the column
                for (var i = dataRequest.FirstColumn; i < dataRequest.FirstColumn + dataRequest.FieldCount; i++)
                {
                    var sourceType = reader.GetFieldType(i);

                    #if NET472
                    var fieldName = reader.GetName(i).Replace(".", string.Empty);                                     // Remove period from dot-qualified names.
                    #else
                    var fieldName = reader.GetName(i).Replace(".", string.Empty, true, CultureInfo.InvariantCulture); // Remove period from dot-qualified names.
                    #endif

                    generator.Emit(OpCodes.Dup);              // obj, obj
                    generator.Emit(OpCodes.Ldstr, fieldName); // obj, obj, fieldname

                    // Get the converter
                    var converter = GetConverter(sourceType, type, dataRequest.DatabaseContext);

                    // Setup stack for call to converter
                    AddConverterToStack(generator, converter);

                    // reader[i]
                    generator.Emit(OpCodes.Ldarg_0);                  // obj, obj, fieldname, converter?,    rdr
                    generator.Emit(OpCodes.Ldc_I4, i);                // obj, obj, fieldname, converter?,  rdr,i
                    generator.Emit(OpCodes.Callvirt, GetValueMethod); // obj, obj, fieldname, converter?,  value

                    // Convert DBNull to null
                    generator.Emit(OpCodes.Dup);                    // obj, obj, fieldname, converter?,  value, value
                    generator.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?,  value, (value or null)
                    var lblNotNull = generator.DefineLabel();
                    generator.Emit(OpCodes.Brfalse_S, lblNotNull);  // obj, obj, fieldname, converter?,  value
                    generator.Emit(OpCodes.Pop);                    // obj, obj, fieldname, converter?

                    if (converter != null)
                    {
                        generator.Emit(OpCodes.Pop); // obj, obj, fieldname,
                    }

                    generator.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null

                    if (converter != null)
                    {
                        var lblReady = generator.DefineLabel();
                        generator.Emit(OpCodes.Br_S, lblReady);
                        generator.MarkLabel(lblNotNull);
                        generator.Emit(OpCodes.Callvirt, InvokeMethod);
                        generator.MarkLabel(lblReady);
                    }
                    else
                    {
                        generator.MarkLabel(lblNotNull);
                    }

                    generator.Emit(OpCodes.Callvirt, addMethod);
                }

                generator.Emit(OpCodes.Ret);
            }
            else if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
            {
                // Do we need to install a converter?
                var sourceType = reader.GetFieldType(0);
                var converter  = GetConverter(sourceType, type, dataRequest.DatabaseContext);

                // "if (!rdr.IsDBNull(i))"
                generator.Emit(OpCodes.Ldarg_0);                  // rdr
                generator.Emit(OpCodes.Ldc_I4_0);                 // rdr,0
                generator.Emit(OpCodes.Callvirt, IsDbNullMethod); // bool
                var continueLabel = generator.DefineLabel();
                generator.Emit(OpCodes.Brfalse_S, continueLabel);
                generator.Emit(OpCodes.Ldnull); // null
                var finishLabel = generator.DefineLabel();
                generator.Emit(OpCodes.Br_S, finishLabel);

                generator.MarkLabel(continueLabel);

                // Setup stack for call to converter
                AddConverterToStack(generator, converter);

                generator.Emit(OpCodes.Ldarg_0);                  // rdr
                generator.Emit(OpCodes.Ldc_I4_0);                 // rdr,0
                generator.Emit(OpCodes.Callvirt, GetValueMethod); // value

                // Call the converter
                if (converter != null)
                {
                    generator.Emit(OpCodes.Callvirt, InvokeMethod);
                }

                generator.MarkLabel(finishLabel);
                generator.Emit(OpCodes.Unbox_Any, type); // value converted
                generator.Emit(OpCodes.Ret);
            }
            else
            {
                // Set this when a column has a value.
                var hasValue = generator.DeclareLocal(typeof(bool));
                generator.Emit(OpCodes.Ldc_I4, 0);
                generator.Emit(OpCodes.Stloc, hasValue);

                // var poco=new T()
                const BindingFlags InstanceConstructors = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
                generator.Emit(OpCodes.Newobj, type.GetConstructor(InstanceConstructors, null, Array.Empty <Type>(), null));

                // Enumerate all fields generating a set assignment for the column
                for (var i = dataRequest.FirstColumn; i < dataRequest.FirstColumn + dataRequest.FieldCount; i++)
                {
                    // Get the PocoColumn for this db column, ignore if not known
                    var key = reader.GetName(i);

                    // We may need to set multiple attributes based on a single reader column when there are multiple paths in a POCO to
                    // the same object reference.
                    var attribute = definitions.FirstOrDefault(x => x.ReferenceName == key);

                    if (attribute == default)
                    {
                        continue;
                    }

                    // Get the source type for this column
                    var sourceType      = reader.GetFieldType(i);
                    var destinationType = attribute.PropertyInfo.PropertyType;

                    // "if (!rdr.IsDBNull(i))"
                    generator.Emit(OpCodes.Ldarg_0);                  // poco,rdr
                    generator.Emit(OpCodes.Ldc_I4, i);                // poco,rdr,i
                    generator.Emit(OpCodes.Callvirt, IsDbNullMethod); // poco,bool
                    var nextLabel = generator.DefineLabel();
                    generator.Emit(OpCodes.Brtrue_S, nextLabel);      // poco

                    // There's a value here so we set our local variable as such.
                    var setLabel = generator.DefineLabel();
                    generator.Emit(OpCodes.Ldloc, hasValue);
                    generator.Emit(OpCodes.Ldc_I4, 1);
                    generator.Emit(OpCodes.Beq, setLabel);
                    generator.Emit(OpCodes.Ldc_I4, 1);
                    generator.Emit(OpCodes.Stloc, hasValue);
                    generator.MarkLabel(setLabel);

                    // Back to our poco.
                    generator.Emit(OpCodes.Dup); // poco,poco

                    // Do we need to install a converter?
                    var converter = GetConverter(sourceType, destinationType, dataRequest.DatabaseContext);

                    // Fast
                    var handled = false;

                    if (converter == null)
                    {
                        var valueGetter = typeof(IDataRecord).GetMethod(string.Concat("Get", sourceType.Name), new[] { typeof(int) });

                        var nullableType = Nullable.GetUnderlyingType(destinationType);

                        var valueGetterMatchesType = valueGetter != null && valueGetter.ReturnType == sourceType &&
                                                     (valueGetter.ReturnType == destinationType || valueGetter.ReturnType == nullableType);

                        if (valueGetterMatchesType)
                        {
                            generator.Emit(OpCodes.Ldarg_0);               // *,rdr
                            generator.Emit(OpCodes.Ldc_I4, i);             // *,rdr,i
                            generator.Emit(OpCodes.Callvirt, valueGetter); // *,value

                            // Convert to Nullable
                            if (nullableType != null)
                            {
                                var constructorInfo = destinationType.GetConstructor(new[] { nullableType });

                                if (constructorInfo == null)
                                {
                                    throw new InvalidOperationException(
                                              string.Format(CultureInfo.CurrentCulture, ErrorMessages.ConstructorInfoDoesNotExist, nullableType));
                                }

                                generator.Emit(OpCodes.Newobj, constructorInfo);
                            }

                            generator.Emit(OpCodes.Callvirt, attribute.PropertyInfo.GetSetMethod(true)); // poco
                            handled = true;
                        }
                    }

                    // Not so fast
                    if (handled == false)
                    {
                        // Setup stack for call to converter
                        AddConverterToStack(generator, converter);

                        // "value = rdr.GetValue(i)"
                        generator.Emit(OpCodes.Ldarg_0);                  // *,rdr
                        generator.Emit(OpCodes.Ldc_I4, i);                // *,rdr,i
                        generator.Emit(OpCodes.Callvirt, GetValueMethod); // *,value

                        // Call the converter
                        if (converter != null)
                        {
                            generator.Emit(OpCodes.Callvirt, InvokeMethod);
                        }

                        // Assign it
                        generator.Emit(OpCodes.Unbox_Any, attribute.PropertyInfo.PropertyType); // poco,poco,value
                        generator.Emit(OpCodes.Callvirt, attribute.SetValueMethod);             // poco
                    }

                    ////columnsMapped--;
                    generator.MarkLabel(nextLabel);
                }

                var onLoadedMethod = RecurseInheritedTypes(type, x => x.GetMethod("OnLoaded", InstanceConstructors, null, Array.Empty <Type>(), null));

                if (onLoadedMethod != null)
                {
                    generator.Emit(OpCodes.Dup);
                    generator.Emit(OpCodes.Callvirt, onLoadedMethod);
                }

                // Check whether anything was set.
                var isSetLabel = generator.DefineLabel();
                generator.Emit(OpCodes.Ldloc, hasValue);
                generator.Emit(OpCodes.Ldc_I4, 1);
                generator.Emit(OpCodes.Beq, isSetLabel);

                // Remove the poco and load a null onto the stack.
                generator.Emit(OpCodes.Pop);
                generator.Emit(OpCodes.Ldnull);
                generator.Emit(OpCodes.Ret);

                // Jump here if something was set.
                generator.MarkLabel(isSetLabel);
                generator.Emit(OpCodes.Ret);
            }

            // Cache it, return it
            var mappingDelegate = method.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
            return(new PocoDelegateInfo(mappingDelegate));
        }
Example #8
0
        /// <inheritdoc />
        public async IAsyncEnumerable <T> QueryAsync <T>(string sql, [EnumeratorCancellation] CancellationToken cancellationToken, params object[] args)
        {
            if (sql == null)
            {
                throw new ArgumentNullException(nameof(sql));
            }

            if (string.IsNullOrWhiteSpace(sql))
            {
                throw new ArgumentException(ErrorMessages.ValueCannotBeNullOrWhiteSpace, nameof(sql));
            }

            if (args == null)
            {
                throw new ArgumentNullException(nameof(args));
            }

            await this.OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false);

            var entityDefinition = this.RepositoryAdapter.DefinitionProvider.Resolve <T>();

            if (this.Connection is DbConnection asyncConnection)
            {
#if NET472
                using (var command = this.CreateAsyncCommand(asyncConnection, sql, args))
#else
                await using (var command = this.CreateAsyncCommand(asyncConnection, sql, args))
#endif
                {
                    ////var results = new List<T>();
#if NET472
                    using (var dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false))
#else
                    await using (var dataReader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
#endif
                    {
                        while (true)
                        {
                            if (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false) == false)
                            {
                                yield break;
                            }

                            var pocoDataRequest = new PocoDataRequest(dataReader, entityDefinition, this)
                            {
                                FirstColumn = 0
                            };

                            var poco = this.pocoFactory.CreatePoco <T>(pocoDataRequest);
                            yield return(poco);
                        }
                    }

                    ////return results;
                }
            }
            else
            {
                using (var command = this.CreateCommand(this.Connection, sql, args))
                {
                    ////var results = new List<T>();
                    using (var dataReader = command.ExecuteReader())
                    {
                        while (true)
                        {
                            if (dataReader.Read() == false)
                            {
                                yield break;
                            }

                            var pocoDataRequest = new PocoDataRequest(dataReader, entityDefinition, this)
                            {
                                FirstColumn = 0
                            };

                            var poco = this.pocoFactory.CreatePoco <T>(pocoDataRequest);
                            yield return(poco);
                        }
                    }

                    ////return results;
                }
            }
        }