using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq.Expressions; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Data; using System.Data.Common; using System.Data.Linq; using System.Data.Linq.Mapping; using System.Data.Linq.Provider; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; namespace System.Data.Linq.SqlClient { using System.Data.Linq.SqlClient.Implementation; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; #if ILGEN || DEBUG namespace Implementation { /// /// Internal interface type defining the operations dynamic materialization functions need to perform when /// materializing objects, without reflecting/invoking privates. /// This interface is required because our anonymously hosted materialization delegates /// run under partial trust and cannot access non-public members of types in the fully trusted /// framework assemblies. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Materializer", Justification = "Spelling is correct.")] [SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors", Justification = "Unknown reason.")] public abstract class ObjectMaterializer where TDataReader : DbDataReader { // These are public fields rather than properties for access speed [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public int[] Ordinals; [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Globals", Justification = "Spelling is correct.")] [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public object[] Globals; [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public object[] Locals; [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public object[] Arguments; [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public TDataReader DataReader; [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "[....]: This is a public type that is not intended for public use.")] public DbDataReader BufferReader; public ObjectMaterializer() { DataReader = default(TDataReader); } public abstract object InsertLookup(int globalMetaType, object instance); public abstract void SendEntityMaterialized(int globalMetaType, object instance); public abstract IEnumerable ExecuteSubQuery(int iSubQuery, object[] args); [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public abstract IEnumerable GetLinkSource(int globalLink, int localFactory, object[] keyValues); [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public abstract IEnumerable GetNestedLinkSource(int globalLink, int localFactory, object instance); public abstract bool Read(); public abstract bool CanDeferLoad { get; } [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "xiaoruda: The method has to be static because it's used in our generated code and there is no instance of the type.")] [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public static IEnumerable Convert(IEnumerable source) { foreach (object value in source) { yield return DBConvert.ChangeType(value); } } [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "xiaoruda: The method has to be static because it's used in our generated code and there is no instance of the type.")] public static IGrouping CreateGroup(TKey key, IEnumerable items) { return new ObjectReaderCompiler.Group(key, items); } [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "xiaoruda: The method has to be static because it's used in our generated code and there is no instance of the type.")] public static IOrderedEnumerable CreateOrderedEnumerable(IEnumerable items) { return new ObjectReaderCompiler.OrderedResults(items); } [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "xiaoruda: The method has to be static because it's used in our generated code and there is no instance of the type.")] public static Exception ErrorAssignmentToNull(Type type) { return Error.CannotAssignNull(type); } } } internal class ObjectReaderCompiler : IObjectReaderCompiler { Type dataReaderType; IDataServices services; MethodInfo miDRisDBNull; MethodInfo miBRisDBNull; FieldInfo readerField; FieldInfo bufferReaderField; FieldInfo ordinalsField; FieldInfo globalsField; FieldInfo argsField; #if DEBUG static AssemblyBuilder captureAssembly; static ModuleBuilder captureModule; static string captureAssemblyFilename; static int iCaptureId; internal static int GetNextId() { return iCaptureId++; } internal static ModuleBuilder CaptureModule { get { return captureModule; } } [ResourceExposure(ResourceScope.Machine)] // filename parameter later used by other methods. internal static void StartCaptureToFile(string filename) { if (captureAssembly == null) { string dir = System.IO.Path.GetDirectoryName(filename); if (dir.Length == 0) dir = null; string name = System.IO.Path.GetFileName(filename); AssemblyName assemblyName = new AssemblyName(System.IO.Path.GetFileNameWithoutExtension(name)); captureAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save, dir); captureModule = captureAssembly.DefineDynamicModule(name); captureAssemblyFilename = filename; } } [ResourceExposure(ResourceScope.None)] // Exposure is via StartCaptureToFile method. [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // Assembly.Save method call. internal static void StopCapture() { if (captureAssembly != null) { captureAssembly.Save(captureAssemblyFilename); captureAssembly = null; } } internal static void SetMaxReaderCacheSize(int size) { if (size <= 1) { throw Error.ArgumentOutOfRange("size"); } maxReaderCacheSize = size; } #endif static LocalDataStoreSlot cacheSlot = Thread.AllocateDataSlot(); static int maxReaderCacheSize = 10; static ObjectReaderCompiler() { } internal ObjectReaderCompiler(Type dataReaderType, IDataServices services) { this.dataReaderType = dataReaderType; this.services = services; this.miDRisDBNull = dataReaderType.GetMethod("IsDBNull", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); this.miBRisDBNull = typeof(DbDataReader).GetMethod("IsDBNull", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.dataReaderType); this.ordinalsField = orbType.GetField("Ordinals", BindingFlags.Instance | BindingFlags.Public); this.globalsField = orbType.GetField("Globals", BindingFlags.Instance | BindingFlags.Public); this.argsField = orbType.GetField("Arguments", BindingFlags.Instance | BindingFlags.Public); this.readerField = orbType.GetField("DataReader", BindingFlags.Instance | BindingFlags.Public); this.bufferReaderField = orbType.GetField("BufferReader", BindingFlags.Instance | BindingFlags.Public); System.Diagnostics.Debug.Assert( this.miDRisDBNull != null && this.miBRisDBNull != null && this.readerField != null && this.bufferReaderField != null && this.ordinalsField != null && this.globalsField != null && this.argsField != null ); } [ResourceExposure(ResourceScope.None)] // Consumed by Thread.AllocateDataSource result being unique. [ResourceConsumption(ResourceScope.AppDomain, ResourceScope.AppDomain)] // Thread.GetData method call. [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public IObjectReaderFactory Compile(SqlExpression expression, Type elementType) { object mapping = this.services.Context.Mapping.Identity; DataLoadOptions options = this.services.Context.LoadOptions; IObjectReaderFactory factory = null; ReaderFactoryCache cache = null; bool canBeCompared = SqlProjectionComparer.CanBeCompared(expression); if (canBeCompared) { cache = (ReaderFactoryCache)Thread.GetData(cacheSlot); if (cache == null) { cache = new ReaderFactoryCache(maxReaderCacheSize); Thread.SetData(cacheSlot, cache); } factory = cache.GetFactory(elementType, this.dataReaderType, mapping, options, expression); } if (factory == null) { Generator gen = new Generator(this, elementType); #if DEBUG if (ObjectReaderCompiler.CaptureModule != null) { this.CompileCapturedMethod(gen, expression, elementType); } #endif DynamicMethod dm = this.CompileDynamicMethod(gen, expression, elementType); Type fnMatType = typeof(Func<,>).MakeGenericType(typeof(ObjectMaterializer<>).MakeGenericType(this.dataReaderType), elementType); var fnMaterialize = (Delegate)dm.CreateDelegate(fnMatType); Type factoryType = typeof(ObjectReaderFactory<,>).MakeGenericType(this.dataReaderType, elementType); factory = (IObjectReaderFactory)Activator.CreateInstance( factoryType, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { fnMaterialize, gen.NamedColumns, gen.Globals, gen.Locals }, null ); if (canBeCompared) { expression = new SourceExpressionRemover().VisitExpression(expression); cache.AddFactory(elementType, this.dataReaderType, mapping, options, expression, factory); } } return factory; } private class SourceExpressionRemover : SqlDuplicator.DuplicatingVisitor { internal SourceExpressionRemover() : base(true) { } internal override SqlNode Visit(SqlNode node) { node = base.Visit(node); if (node != null) { node.ClearSourceExpression(); } return node; } internal override SqlExpression VisitColumnRef(SqlColumnRef cref) { SqlExpression result = base.VisitColumnRef(cref); if (result != null && result == cref) { // reference to outer scope, don't propogate references to expressions or aliases SqlColumn col = cref.Column; SqlColumn newcol = new SqlColumn(col.ClrType, col.SqlType, col.Name, col.MetaMember, null, col.SourceExpression); newcol.Ordinal = col.Ordinal; result = new SqlColumnRef(newcol); newcol.ClearSourceExpression(); } return result; } internal override SqlExpression VisitAliasRef(SqlAliasRef aref) { SqlExpression result = base.VisitAliasRef(aref); if (result != null && result == aref) { // reference to outer scope, don't propogate references to expressions or aliases SqlAlias alias = aref.Alias; SqlAlias newalias = new SqlAlias(new SqlNop(aref.ClrType, aref.SqlType, null)); return new SqlAliasRef(newalias); } return result; } } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public IObjectReaderSession CreateSession(DbDataReader reader, IReaderProvider provider, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries) { Type sessionType = typeof(ObjectReaderSession<>).MakeGenericType(this.dataReaderType); return (IObjectReaderSession)Activator.CreateInstance(sessionType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new object[] { reader, provider, parentArgs, userArgs, subQueries }, null); } #if DEBUG private void CompileCapturedMethod(Generator gen, SqlExpression expression, Type elementType) { TypeBuilder tb = ObjectReaderCompiler.CaptureModule.DefineType("reader_type_" + ObjectReaderCompiler.GetNextId()); MethodBuilder mb = tb.DefineMethod( "Read_" + elementType.Name, MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, elementType, new Type[] { typeof(ObjectMaterializer<>).MakeGenericType(this.dataReaderType) } ); gen.GenerateBody(mb.GetILGenerator(), (SqlExpression)SqlDuplicator.Copy(expression)); tb.CreateType(); } #endif private DynamicMethod CompileDynamicMethod(Generator gen, SqlExpression expression, Type elementType) { Type objectReaderType = typeof(ObjectMaterializer<>).MakeGenericType(this.dataReaderType); DynamicMethod dm = new DynamicMethod( "Read_" + elementType.Name, elementType, new Type[] { objectReaderType }, true ); gen.GenerateBody(dm.GetILGenerator(), expression); return dm; } class ReaderFactoryCache { int maxCacheSize; LinkedList list; class CacheInfo { internal Type elementType; internal Type dataReaderType; internal object mapping; internal DataLoadOptions options; internal SqlExpression projection; internal IObjectReaderFactory factory; public CacheInfo(Type elementType, Type dataReaderType, object mapping, DataLoadOptions options, SqlExpression projection, IObjectReaderFactory factory) { this.elementType = elementType; this.dataReaderType = dataReaderType; this.options = options; this.mapping = mapping; this.projection = projection; this.factory = factory; } } internal ReaderFactoryCache(int maxCacheSize) { this.maxCacheSize = maxCacheSize; this.list = new LinkedList(); } internal IObjectReaderFactory GetFactory(Type elementType, Type dataReaderType, object mapping, DataLoadOptions options, SqlExpression projection) { for (LinkedListNode info = this.list.First; info != null; info = info.Next) { if (elementType == info.Value.elementType && dataReaderType == info.Value.dataReaderType && mapping == info.Value.mapping && DataLoadOptions.ShapesAreEquivalent(options, info.Value.options) && SqlProjectionComparer.AreSimilar(projection, info.Value.projection) ) { // move matching item to head of list to reset its lifetime this.list.Remove(info); this.list.AddFirst(info); return info.Value.factory; } } return null; } internal void AddFactory(Type elementType, Type dataReaderType, object mapping, DataLoadOptions options, SqlExpression projection, IObjectReaderFactory factory) { this.list.AddFirst(new LinkedListNode(new CacheInfo(elementType, dataReaderType, mapping, options, projection, factory))); if (this.list.Count > this.maxCacheSize) { this.list.RemoveLast(); } } } internal class SqlProjectionComparer { [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] internal static bool CanBeCompared(SqlExpression node) { if (node == null) { return true; } switch (node.NodeType) { case SqlNodeType.New: { SqlNew new1 = (SqlNew)node; for (int i = 0, n = new1.Args.Count; i < n; i++) { if (!CanBeCompared(new1.Args[i])) { return false; } } for (int i = 0, n = new1.Members.Count; i < n; i++) { if (!CanBeCompared(new1.Members[i].Expression)) { return false; } } return true; } case SqlNodeType.ColumnRef: case SqlNodeType.Value: case SqlNodeType.UserColumn: return true; case SqlNodeType.Link: { SqlLink l1 = (SqlLink)node; for (int i = 0, c = l1.KeyExpressions.Count; i < c; ++i) { if (!CanBeCompared(l1.KeyExpressions[i])) { return false; } } return true; } case SqlNodeType.OptionalValue: return CanBeCompared(((SqlOptionalValue)node).Value); case SqlNodeType.ValueOf: case SqlNodeType.OuterJoinedValue: return CanBeCompared(((SqlUnary)node).Operand); case SqlNodeType.Lift: return CanBeCompared(((SqlLift)node).Expression); case SqlNodeType.Grouping: { SqlGrouping g1 = (SqlGrouping)node; return CanBeCompared(g1.Key) && CanBeCompared(g1.Group); } case SqlNodeType.ClientArray: { if (node.SourceExpression.NodeType != ExpressionType.NewArrayInit && node.SourceExpression.NodeType != ExpressionType.NewArrayBounds) { return false; } SqlClientArray a1 = (SqlClientArray)node; for (int i = 0, n = a1.Expressions.Count; i < n; i++) { if (!CanBeCompared(a1.Expressions[i])) { return false; } } return true; } case SqlNodeType.ClientCase: { SqlClientCase c1 = (SqlClientCase)node; for (int i = 0, n = c1.Whens.Count; i < n; i++) { if (!CanBeCompared(c1.Whens[i].Match) || !CanBeCompared(c1.Whens[i].Value)) { return false; } } return true; } case SqlNodeType.SearchedCase: { SqlSearchedCase c1 = (SqlSearchedCase)node; for (int i = 0, n = c1.Whens.Count; i < n; i++) { if (!CanBeCompared(c1.Whens[i].Match) || !CanBeCompared(c1.Whens[i].Value)) { return false; } } return CanBeCompared(c1.Else); } case SqlNodeType.TypeCase: { SqlTypeCase c1 = (SqlTypeCase)node; if (!CanBeCompared(c1.Discriminator)) { return false; } for (int i = 0, c = c1.Whens.Count; i < c; ++i) { if (!CanBeCompared(c1.Whens[i].Match)) { return false; } if (!CanBeCompared(c1.Whens[i].TypeBinding)) { return false; } } return true; } case SqlNodeType.DiscriminatedType: return CanBeCompared(((SqlDiscriminatedType)node).Discriminator); case SqlNodeType.JoinedCollection: { SqlJoinedCollection j1 = (SqlJoinedCollection)node; return CanBeCompared(j1.Count) && CanBeCompared(j1.Expression); } case SqlNodeType.Member: return CanBeCompared(((SqlMember)node).Expression); case SqlNodeType.MethodCall: { SqlMethodCall mc = (SqlMethodCall)node; if (mc.Object != null && !CanBeCompared(mc.Object)) { return false; } for (int i = 0, n = mc.Arguments.Count; i < n; i++) { if (!CanBeCompared(mc.Arguments[0])) { return false; } } return true; } case SqlNodeType.ClientQuery: return true; case SqlNodeType.ClientParameter: default: return false; } } [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] internal static bool AreSimilar(SqlExpression node1, SqlExpression node2) { if (node1 == node2) { return true; } if (node1 == null || node2 == null) { return false; } if (node1.NodeType != node2.NodeType || node1.ClrType != node2.ClrType || node1.SqlType != node2.SqlType) { return false; } switch (node1.NodeType) { case SqlNodeType.New: { SqlNew new1 = (SqlNew)node1; SqlNew new2 = (SqlNew)node2; if (new1.Args.Count != new2.Args.Count || new1.Members.Count != new2.Members.Count) { return false; } for (int i = 0, n = new1.Args.Count; i < n; i++) { if (!AreSimilar(new1.Args[i], new2.Args[i])) { return false; } } for (int i = 0, n = new1.Members.Count; i < n; i++) { if (!MetaPosition.AreSameMember(new1.Members[i].Member, new2.Members[i].Member) || !AreSimilar(new1.Members[i].Expression, new2.Members[i].Expression)) { return false; } } return true; } case SqlNodeType.ColumnRef: { SqlColumnRef cref1 = (SqlColumnRef)node1; SqlColumnRef cref2 = (SqlColumnRef)node2; return cref1.Column.Ordinal == cref2.Column.Ordinal; } case SqlNodeType.Link: { SqlLink l1 = (SqlLink)node1; SqlLink l2 = (SqlLink)node2; if (!MetaPosition.AreSameMember(l1.Member.Member, l2.Member.Member)) { return false; } if (l1.KeyExpressions.Count != l2.KeyExpressions.Count) { return false; } for (int i = 0, c = l1.KeyExpressions.Count; i < c; ++i) { if (!AreSimilar(l1.KeyExpressions[i], l2.KeyExpressions[i])) { return false; } } return true; } case SqlNodeType.Value: return Object.Equals(((SqlValue)node1).Value, ((SqlValue)node2).Value); case SqlNodeType.OptionalValue: { SqlOptionalValue ov1 = (SqlOptionalValue)node1; SqlOptionalValue ov2 = (SqlOptionalValue)node2; return AreSimilar(ov1.Value, ov2.Value); } case SqlNodeType.ValueOf: case SqlNodeType.OuterJoinedValue: return AreSimilar(((SqlUnary)node1).Operand, ((SqlUnary)node2).Operand); case SqlNodeType.Lift: return AreSimilar(((SqlLift)node1).Expression, ((SqlLift)node2).Expression); case SqlNodeType.Grouping: { SqlGrouping g1 = (SqlGrouping)node1; SqlGrouping g2 = (SqlGrouping)node2; return AreSimilar(g1.Key, g2.Key) && AreSimilar(g1.Group, g2.Group); } case SqlNodeType.ClientArray: { SqlClientArray a1 = (SqlClientArray)node1; SqlClientArray a2 = (SqlClientArray)node2; if (a1.Expressions.Count != a2.Expressions.Count) { return false; } for (int i = 0, n = a1.Expressions.Count; i < n; i++) { if (!AreSimilar(a1.Expressions[i], a2.Expressions[i])) { return false; } } return true; } case SqlNodeType.UserColumn: return ((SqlUserColumn)node1).Name == ((SqlUserColumn)node2).Name; case SqlNodeType.ClientCase: { SqlClientCase c1 = (SqlClientCase)node1; SqlClientCase c2 = (SqlClientCase)node2; if (c1.Whens.Count != c2.Whens.Count) { return false; } for (int i = 0, n = c1.Whens.Count; i < n; i++) { if (!AreSimilar(c1.Whens[i].Match, c2.Whens[i].Match) || !AreSimilar(c1.Whens[i].Value, c2.Whens[i].Value)) { return false; } } return true; } case SqlNodeType.SearchedCase: { SqlSearchedCase c1 = (SqlSearchedCase)node1; SqlSearchedCase c2 = (SqlSearchedCase)node2; if (c1.Whens.Count != c2.Whens.Count) { return false; } for (int i = 0, n = c1.Whens.Count; i < n; i++) { if (!AreSimilar(c1.Whens[i].Match, c2.Whens[i].Match) || !AreSimilar(c1.Whens[i].Value, c2.Whens[i].Value)) return false; } return AreSimilar(c1.Else, c2.Else); } case SqlNodeType.TypeCase: { SqlTypeCase c1 = (SqlTypeCase)node1; SqlTypeCase c2 = (SqlTypeCase)node2; if (!AreSimilar(c1.Discriminator, c2.Discriminator)) { return false; } if (c1.Whens.Count != c2.Whens.Count) { return false; } for (int i = 0, c = c1.Whens.Count; i < c; ++i) { if (!AreSimilar(c1.Whens[i].Match, c2.Whens[i].Match)) { return false; } if (!AreSimilar(c1.Whens[i].TypeBinding, c2.Whens[i].TypeBinding)) { return false; } } return true; } case SqlNodeType.DiscriminatedType: { SqlDiscriminatedType dt1 = (SqlDiscriminatedType)node1; SqlDiscriminatedType dt2 = (SqlDiscriminatedType)node2; return AreSimilar(dt1.Discriminator, dt2.Discriminator); } case SqlNodeType.JoinedCollection: { SqlJoinedCollection j1 = (SqlJoinedCollection)node1; SqlJoinedCollection j2 = (SqlJoinedCollection)node2; return AreSimilar(j1.Count, j2.Count) && AreSimilar(j1.Expression, j2.Expression); } case SqlNodeType.Member: { SqlMember m1 = (SqlMember)node1; SqlMember m2 = (SqlMember)node2; return m1.Member == m2.Member && AreSimilar(m1.Expression, m2.Expression); } case SqlNodeType.ClientQuery: { SqlClientQuery cq1 = (SqlClientQuery)node1; SqlClientQuery cq2 = (SqlClientQuery)node2; if (cq1.Arguments.Count != cq2.Arguments.Count) { return false; } for (int i = 0, n = cq1.Arguments.Count; i < n; i++) { if (!AreSimilar(cq1.Arguments[i], cq2.Arguments[i])) { return false; } } return true; } case SqlNodeType.MethodCall: { SqlMethodCall mc1 = (SqlMethodCall)node1; SqlMethodCall mc2 = (SqlMethodCall)node2; if (mc1.Method != mc2.Method || !AreSimilar(mc1.Object, mc2.Object)) { return false; } if (mc1.Arguments.Count != mc2.Arguments.Count) { return false; } for (int i = 0, n = mc1.Arguments.Count; i < n; i++) { if (!AreSimilar(mc1.Arguments[i], mc2.Arguments[i])) { return false; } } return true; } case SqlNodeType.ClientParameter: default: return false; } } } class SideEffectChecker : SqlVisitor { bool hasSideEffect; internal bool HasSideEffect(SqlNode node) { this.hasSideEffect = false; this.Visit(node); return this.hasSideEffect; } internal override SqlExpression VisitJoinedCollection(SqlJoinedCollection jc) { this.hasSideEffect = true; return jc; } internal override SqlExpression VisitClientQuery(SqlClientQuery cq) { return cq; } } [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unknown reason.")] class Generator { ObjectReaderCompiler compiler; ILGenerator gen; List globals; List namedColumns; LocalBuilder locDataReader; Type elementType; int nLocals; Dictionary associationSubQueries; SideEffectChecker sideEffectChecker = new SideEffectChecker(); internal Generator(ObjectReaderCompiler compiler, Type elementType) { this.compiler = compiler; this.elementType = elementType; this.associationSubQueries = new Dictionary(); } internal void GenerateBody(ILGenerator generator, SqlExpression expression) { this.gen = generator; this.globals = new List(); this.namedColumns = new List(); // prepare locDataReader this.locDataReader = generator.DeclareLocal(this.compiler.dataReaderType); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, this.compiler.readerField); generator.Emit(OpCodes.Stloc, this.locDataReader); this.GenerateExpressionForType(expression, this.elementType); generator.Emit(OpCodes.Ret); } internal object[] Globals { get { return this.globals.ToArray(); } } internal NamedColumn[] NamedColumns { get { return this.namedColumns.ToArray(); } } internal int Locals { get { return this.nLocals; } } #if DEBUG private int stackDepth; #endif private Type Generate(SqlNode node) { return this.Generate(node, null); } [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "[....]: Cast is dependent on node type and casts do not happen unecessarily in a single code path.")] [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] private Type Generate(SqlNode node, LocalBuilder locInstance) { #if DEBUG try { stackDepth++; System.Diagnostics.Debug.Assert(stackDepth < 500); #endif switch (node.NodeType) { case SqlNodeType.New: return this.GenerateNew((SqlNew)node); case SqlNodeType.ColumnRef: return this.GenerateColumnReference((SqlColumnRef)node); case SqlNodeType.ClientQuery: return this.GenerateClientQuery((SqlClientQuery)node, locInstance); case SqlNodeType.JoinedCollection: return this.GenerateJoinedCollection((SqlJoinedCollection)node); case SqlNodeType.Link: return this.GenerateLink((SqlLink)node, locInstance); case SqlNodeType.Value: return this.GenerateValue((SqlValue)node); case SqlNodeType.ClientParameter: return this.GenerateClientParameter((SqlClientParameter)node); case SqlNodeType.ValueOf: return this.GenerateValueOf((SqlUnary)node); case SqlNodeType.OptionalValue: return this.GenerateOptionalValue((SqlOptionalValue)node); case SqlNodeType.OuterJoinedValue: return this.Generate(((SqlUnary)node).Operand); case SqlNodeType.Lift: return this.GenerateLift((SqlLift)node); case SqlNodeType.Grouping: return this.GenerateGrouping((SqlGrouping)node); case SqlNodeType.ClientArray: return this.GenerateClientArray((SqlClientArray)node); case SqlNodeType.UserColumn: return this.GenerateUserColumn((SqlUserColumn)node); case SqlNodeType.ClientCase: return this.GenerateClientCase((SqlClientCase)node, false, locInstance); case SqlNodeType.SearchedCase: return this.GenerateSearchedCase((SqlSearchedCase)node); case SqlNodeType.TypeCase: return this.GenerateTypeCase((SqlTypeCase)node); case SqlNodeType.DiscriminatedType: return this.GenerateDiscriminatedType((SqlDiscriminatedType)node); case SqlNodeType.Member: return this.GenerateMember((SqlMember)node); case SqlNodeType.MethodCall: return this.GenerateMethodCall((SqlMethodCall)node); default: throw Error.CouldNotTranslateExpressionForReading(node.SourceExpression); } #if DEBUG } finally { stackDepth--; } #endif } private void GenerateAccessBufferReader() { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, this.compiler.bufferReaderField); } private void GenerateAccessDataReader() { gen.Emit(OpCodes.Ldloc, this.locDataReader); } private void GenerateAccessOrdinals() { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, this.compiler.ordinalsField); } private void GenerateAccessGlobals() { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, this.compiler.globalsField); } private void GenerateAccessArguments() { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, this.compiler.argsField); } private Type GenerateValue(SqlValue value) { return this.GenerateConstant(value.ClrType, value.Value); } private Type GenerateClientParameter(SqlClientParameter cp) { Delegate d = cp.Accessor.Compile(); int iGlobal = this.AddGlobal(d.GetType(), d); this.GenerateGlobalAccess(iGlobal, d.GetType()); this.GenerateAccessArguments(); MethodInfo miInvoke = d.GetType().GetMethod( "Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(object[]) }, null ); System.Diagnostics.Debug.Assert(miInvoke != null); gen.Emit(GetMethodCallOpCode(miInvoke), miInvoke); return d.Method.ReturnType; } private Type GenerateValueOf(SqlUnary u) { System.Diagnostics.Debug.Assert(TypeSystem.IsNullableType(u.Operand.ClrType)); this.GenerateExpressionForType(u.Operand, u.Operand.ClrType); LocalBuilder loc = gen.DeclareLocal(u.Operand.ClrType); gen.Emit(OpCodes.Stloc, loc); gen.Emit(OpCodes.Ldloca, loc); this.GenerateGetValue(u.Operand.ClrType); return u.ClrType; } private Type GenerateOptionalValue(SqlOptionalValue opt) { System.Diagnostics.Debug.Assert(opt.HasValue.ClrType == typeof(int?)); Label labIsNull = gen.DefineLabel(); Label labExit = gen.DefineLabel(); Type actualType = this.Generate(opt.HasValue); System.Diagnostics.Debug.Assert(TypeSystem.IsNullableType(actualType)); LocalBuilder loc = gen.DeclareLocal(actualType); gen.Emit(OpCodes.Stloc, loc); gen.Emit(OpCodes.Ldloca, loc); this.GenerateHasValue(actualType); gen.Emit(OpCodes.Brfalse, labIsNull); this.GenerateExpressionForType(opt.Value, opt.ClrType); gen.Emit(OpCodes.Br_S, labExit); gen.MarkLabel(labIsNull); this.GenerateConstant(opt.ClrType, null); gen.MarkLabel(labExit); return opt.ClrType; } private Type GenerateLift(SqlLift lift) { return this.GenerateExpressionForType(lift.Expression, lift.ClrType); } private Type GenerateClientArray(SqlClientArray ca) { if (!ca.ClrType.IsArray) { throw Error.CannotMaterializeList(ca.ClrType); } Type elemType = TypeSystem.GetElementType(ca.ClrType); this.GenerateConstInt(ca.Expressions.Count); gen.Emit(OpCodes.Newarr, elemType); for (int i = 0, n = ca.Expressions.Count; i < n; i++) { gen.Emit(OpCodes.Dup); this.GenerateConstInt(i); this.GenerateExpressionForType(ca.Expressions[i], elemType); this.GenerateArrayAssign(elemType); } return ca.ClrType; } private Type GenerateMember(SqlMember m) { FieldInfo fi = m.Member as FieldInfo; if (fi != null) { this.GenerateExpressionForType(m.Expression, m.Expression.ClrType); gen.Emit(OpCodes.Ldfld, fi); return fi.FieldType; } else { PropertyInfo pi = (PropertyInfo)m.Member; return this.GenerateMethodCall(new SqlMethodCall(m.ClrType, m.SqlType, pi.GetGetMethod(), m.Expression, null, m.SourceExpression)); } } private Type GenerateMethodCall(SqlMethodCall mc) { ParameterInfo[] pis = mc.Method.GetParameters(); if (mc.Object != null) { Type actualType = this.GenerateExpressionForType(mc.Object, mc.Object.ClrType); if (actualType.IsValueType) { LocalBuilder loc = gen.DeclareLocal(actualType); gen.Emit(OpCodes.Stloc, loc); gen.Emit(OpCodes.Ldloca, loc); } } for (int i = 0, n = mc.Arguments.Count; i < n; i++) { ParameterInfo pi = pis[i]; Type pType = pi.ParameterType; if (pType.IsByRef) { pType = pType.GetElementType(); this.GenerateExpressionForType(mc.Arguments[i], pType); LocalBuilder loc = gen.DeclareLocal(pType); gen.Emit(OpCodes.Stloc, loc); gen.Emit(OpCodes.Ldloca, loc); } else { this.GenerateExpressionForType(mc.Arguments[i], pType); } } OpCode callOpCode = GetMethodCallOpCode(mc.Method); if (mc.Object != null && TypeSystem.IsNullableType(mc.Object.ClrType) && callOpCode == OpCodes.Callvirt){ gen.Emit(OpCodes.Constrained, mc.Object.ClrType); } gen.Emit(callOpCode, mc.Method); return mc.Method.ReturnType; } /// /// Cannot use Call for virtual methods - it results in unverifiable code. Ensure we're using the correct op code. /// private static OpCode GetMethodCallOpCode(MethodInfo mi) { return (mi.IsStatic || mi.DeclaringType.IsValueType) ? OpCodes.Call : OpCodes.Callvirt; } private Type GenerateNew(SqlNew sn) { LocalBuilder locInstance = gen.DeclareLocal(sn.ClrType); LocalBuilder locStoreInMember = null; Label labNewExit = gen.DefineLabel(); Label labAlreadyCached = gen.DefineLabel(); // read all arg values if (sn.Args.Count > 0) { ParameterInfo[] pis = sn.Constructor.GetParameters(); for (int i = 0, n = sn.Args.Count; i < n; i++) { this.GenerateExpressionForType(sn.Args[i], pis[i].ParameterType); } } // construct the new instance if (sn.Constructor != null) { gen.Emit(OpCodes.Newobj, sn.Constructor); gen.Emit(OpCodes.Stloc, locInstance); } else if (sn.ClrType.IsValueType) { gen.Emit(OpCodes.Ldloca, locInstance); gen.Emit(OpCodes.Initobj, sn.ClrType); } else { ConstructorInfo ci = sn.ClrType.GetConstructor(System.Type.EmptyTypes); gen.Emit(OpCodes.Newobj, ci); gen.Emit(OpCodes.Stloc, locInstance); } // read/write key bindings if there are any foreach (SqlMemberAssign ma in sn.Members.OrderBy(m => sn.MetaType.GetDataMember(m.Member).Ordinal)) { MetaDataMember mm = sn.MetaType.GetDataMember(ma.Member); if (mm.IsPrimaryKey) { this.GenerateMemberAssignment(mm, locInstance, ma.Expression, null); } } int iMeta = 0; if (sn.MetaType.IsEntity) { LocalBuilder locCached = gen.DeclareLocal(sn.ClrType); locStoreInMember = gen.DeclareLocal(typeof(bool)); Label labExit = gen.DefineLabel(); iMeta = this.AddGlobal(typeof(MetaType), sn.MetaType); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); // this.InsertLookup(metaType, locInstance) gen.Emit(OpCodes.Ldarg_0); this.GenerateConstInt(iMeta); gen.Emit(OpCodes.Ldloc, locInstance); MethodInfo miInsertLookup = orbType.GetMethod( "InsertLookup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(int), typeof(object) }, null ); System.Diagnostics.Debug.Assert(miInsertLookup != null); gen.Emit(GetMethodCallOpCode(miInsertLookup), miInsertLookup); gen.Emit(OpCodes.Castclass, sn.ClrType); gen.Emit(OpCodes.Stloc, locCached); // if cached != instance then already cached gen.Emit(OpCodes.Ldloc, locCached); gen.Emit(OpCodes.Ldloc, locInstance); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brfalse, labAlreadyCached); this.GenerateConstInt(1); gen.Emit(OpCodes.Stloc, locStoreInMember); gen.Emit(OpCodes.Br_S, labExit); gen.MarkLabel(labAlreadyCached); gen.Emit(OpCodes.Ldloc, locCached); gen.Emit(OpCodes.Stloc, locInstance); // signal to not store loaded values in instance... this.GenerateConstInt(0); gen.Emit(OpCodes.Stloc, locStoreInMember); gen.MarkLabel(labExit); } // read/write non-key bindings foreach (SqlMemberAssign ma in sn.Members.OrderBy(m => sn.MetaType.GetDataMember(m.Member).Ordinal)) { MetaDataMember mm = sn.MetaType.GetDataMember(ma.Member); if (!mm.IsPrimaryKey) { this.GenerateMemberAssignment(mm, locInstance, ma.Expression, locStoreInMember); } } if (sn.MetaType.IsEntity) { // don't call SendEntityMaterialized if we already had the instance cached gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labNewExit); // send entity materialized event gen.Emit(OpCodes.Ldarg_0); this.GenerateConstInt(iMeta); gen.Emit(OpCodes.Ldloc, locInstance); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); MethodInfo miRaiseEvent = orbType.GetMethod( "SendEntityMaterialized", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(int), typeof(object) }, null ); System.Diagnostics.Debug.Assert(miRaiseEvent != null); gen.Emit(GetMethodCallOpCode(miRaiseEvent), miRaiseEvent); } gen.MarkLabel(labNewExit); gen.Emit(OpCodes.Ldloc, locInstance); return sn.ClrType; } private void GenerateMemberAssignment(MetaDataMember mm, LocalBuilder locInstance, SqlExpression expr, LocalBuilder locStoreInMember) { MemberInfo m = mm.StorageMember != null ? mm.StorageMember : mm.Member; Type memberType = TypeSystem.GetMemberType(m); // check for deferrable member & deferred source expression if (IsDeferrableExpression(expr) && (this.compiler.services.Context.LoadOptions == null || !this.compiler.services.Context.LoadOptions.IsPreloaded(mm.Member)) ) { // we can only defer deferrable members if (mm.IsDeferred) { // determine at runtime if we are allowed to defer load gen.Emit(OpCodes.Ldarg_0); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); PropertyInfo piCanDeferLoad = orbType.GetProperty("CanDeferLoad"); System.Diagnostics.Debug.Assert(piCanDeferLoad != null); MethodInfo miCanDeferLoad = piCanDeferLoad.GetGetMethod(); gen.Emit(GetMethodCallOpCode(miCanDeferLoad), miCanDeferLoad); // if we can't defer load then jump over the code that does the defer loading Label labEndDeferLoad = gen.DefineLabel(); gen.Emit(OpCodes.Brfalse, labEndDeferLoad); // execute the defer load operation if (memberType.IsGenericType) { Type genType = memberType.GetGenericTypeDefinition(); if (genType == typeof(EntitySet<>)) { this.GenerateAssignDeferredEntitySet(mm, locInstance, expr, locStoreInMember); } else if (genType == typeof(EntityRef<>) || genType == typeof(Link<>)) { this.GenerateAssignDeferredReference(mm, locInstance, expr, locStoreInMember); } else { throw Error.DeferredMemberWrongType(); } } else { throw Error.DeferredMemberWrongType(); } gen.MarkLabel(labEndDeferLoad); } else { // behavior for non-deferred members w/ deferrable expressions is to load nothing } } else if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(EntitySet<>)) { this.GenerateAssignEntitySet(mm, locInstance, expr, locStoreInMember); } else { this.GenerateAssignValue(mm, locInstance, expr, locStoreInMember); } } private void GenerateAssignValue(MetaDataMember mm, LocalBuilder locInstance, SqlExpression expr, LocalBuilder locStoreInMember) { MemberInfo m = mm.StorageMember != null ? mm.StorageMember : mm.Member; if (!IsAssignable(m)) { throw Error.CannotAssignToMember(m.Name); } Type memberType = TypeSystem.GetMemberType(m); Label labExit = gen.DefineLabel(); bool hasSideEffect = this.HasSideEffect(expr); if (locStoreInMember != null && !hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } this.GenerateExpressionForType(expr, memberType, mm.DeclaringType.IsEntity ? locInstance : null); LocalBuilder locValue = gen.DeclareLocal(memberType); gen.Emit(OpCodes.Stloc, locValue); if (locStoreInMember != null && hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } this.GenerateLoadForMemberAccess(locInstance); gen.Emit(OpCodes.Ldloc, locValue); this.GenerateStoreMember(m); gen.MarkLabel(labExit); } private static bool IsAssignable(MemberInfo member) { FieldInfo fi = member as FieldInfo; if (fi != null) { return true; } PropertyInfo pi = member as PropertyInfo; if (pi != null) { return pi.CanWrite; } return false; } private void GenerateAssignDeferredEntitySet(MetaDataMember mm, LocalBuilder locInstance, SqlExpression expr, LocalBuilder locStoreInMember) { MemberInfo m = mm.StorageMember != null ? mm.StorageMember : mm.Member; Type memberType = TypeSystem.GetMemberType(m); System.Diagnostics.Debug.Assert(memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(EntitySet<>)); Label labExit = gen.DefineLabel(); Type argType = typeof(IEnumerable<>).MakeGenericType(memberType.GetGenericArguments()); bool hasSideEffect = this.HasSideEffect(expr); if (locStoreInMember != null && !hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } Type eType = this.GenerateDeferredSource(expr, locInstance); System.Diagnostics.Debug.Assert(argType.IsAssignableFrom(eType)); LocalBuilder locSource = gen.DeclareLocal(eType); gen.Emit(OpCodes.Stloc, locSource); if (locStoreInMember != null && hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } // if member is directly writeable, check for null entityset if (m is FieldInfo || (m is PropertyInfo && ((PropertyInfo)m).CanWrite)) { Label labFetch = gen.DefineLabel(); this.GenerateLoadForMemberAccess(locInstance); this.GenerateLoadMember(m); gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brfalse, labFetch); // create new entity set this.GenerateLoadForMemberAccess(locInstance); ConstructorInfo ci = memberType.GetConstructor(System.Type.EmptyTypes); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); this.GenerateStoreMember(m); gen.MarkLabel(labFetch); } // set the source this.GenerateLoadForMemberAccess(locInstance); this.GenerateLoadMember(m); gen.Emit(OpCodes.Ldloc, locSource); MethodInfo miSetSource = memberType.GetMethod("SetSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { argType }, null); System.Diagnostics.Debug.Assert(miSetSource != null); gen.Emit(GetMethodCallOpCode(miSetSource), miSetSource); gen.MarkLabel(labExit); } private bool HasSideEffect(SqlNode node) { return this.sideEffectChecker.HasSideEffect(node); } private void GenerateAssignEntitySet(MetaDataMember mm, LocalBuilder locInstance, SqlExpression expr, LocalBuilder locStoreInMember) { MemberInfo m = mm.StorageMember != null ? mm.StorageMember : mm.Member; Type memberType = TypeSystem.GetMemberType(m); System.Diagnostics.Debug.Assert(memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(EntitySet<>)); Label labExit = gen.DefineLabel(); Type argType = typeof(IEnumerable<>).MakeGenericType(memberType.GetGenericArguments()); bool hasSideEffect = this.HasSideEffect(expr); if (locStoreInMember != null && !hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } Type eType = this.Generate(expr, mm.DeclaringType.IsEntity ? locInstance : null); System.Diagnostics.Debug.Assert(argType.IsAssignableFrom(eType)); LocalBuilder locSource = gen.DeclareLocal(eType); gen.Emit(OpCodes.Stloc, locSource); if (locStoreInMember != null && hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } // if member is directly writeable, check for null entityset if (m is FieldInfo || (m is PropertyInfo && ((PropertyInfo)m).CanWrite)) { Label labFetch = gen.DefineLabel(); this.GenerateLoadForMemberAccess(locInstance); this.GenerateLoadMember(m); gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brfalse, labFetch); // create new entity set this.GenerateLoadForMemberAccess(locInstance); ConstructorInfo ci = memberType.GetConstructor(System.Type.EmptyTypes); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); this.GenerateStoreMember(m); gen.MarkLabel(labFetch); } // set the source this.GenerateLoadForMemberAccess(locInstance); this.GenerateLoadMember(m); gen.Emit(OpCodes.Ldloc, locSource); MethodInfo miAssign = memberType.GetMethod("Assign", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { argType }, null); System.Diagnostics.Debug.Assert(miAssign != null); gen.Emit(GetMethodCallOpCode(miAssign), miAssign); gen.MarkLabel(labExit); } private void GenerateAssignDeferredReference(MetaDataMember mm, LocalBuilder locInstance, SqlExpression expr, LocalBuilder locStoreInMember) { MemberInfo m = mm.StorageMember != null ? mm.StorageMember : mm.Member; Type memberType = TypeSystem.GetMemberType(m); System.Diagnostics.Debug.Assert( memberType.IsGenericType && (memberType.GetGenericTypeDefinition() == typeof(EntityRef<>) || memberType.GetGenericTypeDefinition() == typeof(Link<>)) ); Label labExit = gen.DefineLabel(); Type argType = typeof(IEnumerable<>).MakeGenericType(memberType.GetGenericArguments()); bool hasSideEffect = this.HasSideEffect(expr); if (locStoreInMember != null && !hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } Type eType = this.GenerateDeferredSource(expr, locInstance); if (!argType.IsAssignableFrom(eType)) { throw Error.CouldNotConvert(argType, eType); } LocalBuilder locSource = gen.DeclareLocal(eType); gen.Emit(OpCodes.Stloc, locSource); if (locStoreInMember != null && hasSideEffect) { gen.Emit(OpCodes.Ldloc, locStoreInMember); this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labExit); } this.GenerateLoadForMemberAccess(locInstance); gen.Emit(OpCodes.Ldloc, locSource); ConstructorInfo ci = memberType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { argType }, null); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); this.GenerateStoreMember(m); gen.MarkLabel(labExit); } private void GenerateLoadForMemberAccess(LocalBuilder loc) { if (loc.LocalType.IsValueType) { gen.Emit(OpCodes.Ldloca, loc); } else { gen.Emit(OpCodes.Ldloc, loc); } } private bool IsDeferrableExpression(SqlExpression expr) { if (expr.NodeType == SqlNodeType.Link) { return true; } else if (expr.NodeType == SqlNodeType.ClientCase) { SqlClientCase c = (SqlClientCase)expr; foreach (SqlClientWhen when in c.Whens) { if (!IsDeferrableExpression(when.Value)) { return false; } } return true; } return false; } private Type GenerateGrouping(SqlGrouping grp) { Type[] typeArgs = grp.ClrType.GetGenericArguments(); this.GenerateExpressionForType(grp.Key, typeArgs[0]); this.Generate(grp.Group); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); MethodInfo miCreateGroup = TypeSystem.FindStaticMethod(orbType, "CreateGroup", new Type[] { typeArgs[0], typeof(IEnumerable<>).MakeGenericType(typeArgs[1]) }, typeArgs); System.Diagnostics.Debug.Assert(miCreateGroup != null); gen.Emit(OpCodes.Call, miCreateGroup); return miCreateGroup.ReturnType; } private Type GenerateLink(SqlLink link, LocalBuilder locInstance) { gen.Emit(OpCodes.Ldarg_0); // iGlobalLink arg int iGlobalLink = this.AddGlobal(typeof(MetaDataMember), link.Member); this.GenerateConstInt(iGlobalLink); // iLocalFactory arg int iLocalFactory = this.AllocateLocal(); this.GenerateConstInt(iLocalFactory); Type elemType = link.Member.IsAssociation && link.Member.Association.IsMany ? TypeSystem.GetElementType(link.Member.Type) : link.Member.Type; MethodInfo mi = null; if (locInstance != null) { // load instance for 'instance' arg gen.Emit(OpCodes.Ldloc, locInstance); // call GetNestedLinkSource on ObjectReaderBase mi = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType).GetMethod("GetNestedLinkSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); System.Diagnostics.Debug.Assert(mi != null); MethodInfo miGLS = mi.MakeGenericMethod(elemType); gen.Emit(GetMethodCallOpCode(miGLS), miGLS); } else { // create array of key values for 'keyValues' arg this.GenerateConstInt(link.KeyExpressions.Count); gen.Emit(OpCodes.Newarr, typeof(object)); // intialize key values for (int i = 0, n = link.KeyExpressions.Count; i < n; i++) { gen.Emit(OpCodes.Dup); this.GenerateConstInt(i); this.GenerateExpressionForType(link.KeyExpressions[i], typeof(object)); this.GenerateArrayAssign(typeof(object)); } // call GetLinkSource on ObjectReaderBase mi = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType).GetMethod("GetLinkSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); System.Diagnostics.Debug.Assert(mi != null); MethodInfo miGLS = mi.MakeGenericMethod(elemType); gen.Emit(GetMethodCallOpCode(miGLS), miGLS); } return typeof(IEnumerable<>).MakeGenericType(elemType); } private Type GenerateDeferredSource(SqlExpression expr, LocalBuilder locInstance) { if (expr.NodeType == SqlNodeType.ClientCase) { return this.GenerateClientCase((SqlClientCase)expr, true, locInstance); } else if (expr.NodeType == SqlNodeType.Link) { return this.GenerateLink((SqlLink)expr, locInstance); } else { throw Error.ExpressionNotDeferredQuerySource(); } } private Type GenerateClientQuery(SqlClientQuery cq, LocalBuilder locInstance) { Type clientElementType = cq.Query.NodeType == SqlNodeType.Multiset ? TypeSystem.GetElementType(cq.ClrType) : cq.ClrType; gen.Emit(OpCodes.Ldarg_0); // ObjectReaderBase this.GenerateConstInt(cq.Ordinal); // iSubQuery // create array of subquery parent args this.GenerateConstInt(cq.Arguments.Count); gen.Emit(OpCodes.Newarr, typeof(object)); // intialize arg values for (int i = 0, n = cq.Arguments.Count; i < n; i++) { gen.Emit(OpCodes.Dup); this.GenerateConstInt(i); Type clrType = cq.Arguments[i].ClrType; if (cq.Arguments[i].NodeType == SqlNodeType.ColumnRef) { SqlColumnRef cref = (SqlColumnRef)cq.Arguments[i]; if (clrType.IsValueType && !TypeSystem.IsNullableType(clrType)) { clrType = typeof(Nullable<>).MakeGenericType(clrType); } this.GenerateColumnAccess(clrType, cref.SqlType, cref.Column.Ordinal, null); } else { this.GenerateExpressionForType(cq.Arguments[i], cq.Arguments[i].ClrType); } if (clrType.IsValueType) { gen.Emit(OpCodes.Box, clrType); } this.GenerateArrayAssign(typeof(object)); } MethodInfo miExecute = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType) .GetMethod("ExecuteSubQuery", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); System.Diagnostics.Debug.Assert(miExecute != null); gen.Emit(GetMethodCallOpCode(miExecute), miExecute); Type actualType = typeof(IEnumerable<>).MakeGenericType(clientElementType); gen.Emit(OpCodes.Castclass, actualType); Type resultType = typeof(List<>).MakeGenericType(clientElementType); this.GenerateConvertToType(actualType, resultType); return resultType; } private Type GenerateJoinedCollection(SqlJoinedCollection jc) { LocalBuilder locCount = gen.DeclareLocal(typeof(int)); LocalBuilder locHasRows = gen.DeclareLocal(typeof(bool)); Type joinElementType = jc.Expression.ClrType; Type listType = typeof(List<>).MakeGenericType(joinElementType); LocalBuilder locList = gen.DeclareLocal(listType); // count = xxx this.GenerateExpressionForType(jc.Count, typeof(int)); gen.Emit(OpCodes.Stloc, locCount); // list = new List(count) gen.Emit(OpCodes.Ldloc, locCount); ConstructorInfo ci = listType.GetConstructor(new Type[] { typeof(int) }); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); gen.Emit(OpCodes.Stloc, locList); // hasRows = true gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Stloc, locHasRows); // start loop Label labLoopTest = gen.DefineLabel(); Label labLoopTop = gen.DefineLabel(); LocalBuilder locI = gen.DeclareLocal(typeof(int)); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Stloc, locI); gen.Emit(OpCodes.Br, labLoopTest); gen.MarkLabel(labLoopTop); // loop interior // if (i > 0 && hasRows) { hasRows = this.Read(); } gen.Emit(OpCodes.Ldloc, locI); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Cgt); gen.Emit(OpCodes.Ldloc, locHasRows); gen.Emit(OpCodes.And); Label labNext = gen.DefineLabel(); gen.Emit(OpCodes.Brfalse, labNext); // this.Read() gen.Emit(OpCodes.Ldarg_0); Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); MethodInfo miRead = orbType.GetMethod("Read", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); System.Diagnostics.Debug.Assert(miRead != null); gen.Emit(GetMethodCallOpCode(miRead), miRead); gen.Emit(OpCodes.Stloc, locHasRows); gen.MarkLabel(labNext); // if (hasRows) { list.Add(expr); } Label labNext2 = gen.DefineLabel(); gen.Emit(OpCodes.Ldloc, locHasRows); gen.Emit(OpCodes.Brfalse, labNext2); gen.Emit(OpCodes.Ldloc, locList); this.GenerateExpressionForType(jc.Expression, joinElementType); MethodInfo miAdd = listType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { joinElementType }, null); System.Diagnostics.Debug.Assert(miAdd != null); gen.Emit(GetMethodCallOpCode(miAdd), miAdd); gen.MarkLabel(labNext2); // loop bottom // i = i + 1 gen.Emit(OpCodes.Ldloc, locI); gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Add); gen.Emit(OpCodes.Stloc, locI); // loop test // i < count && hasRows gen.MarkLabel(labLoopTest); gen.Emit(OpCodes.Ldloc, locI); gen.Emit(OpCodes.Ldloc, locCount); gen.Emit(OpCodes.Clt); gen.Emit(OpCodes.Ldloc, locHasRows); gen.Emit(OpCodes.And); gen.Emit(OpCodes.Brtrue, labLoopTop); // return list; gen.Emit(OpCodes.Ldloc, locList); return listType; } private Type GenerateExpressionForType(SqlExpression expr, Type type) { return this.GenerateExpressionForType(expr, type, null); } private Type GenerateExpressionForType(SqlExpression expr, Type type, LocalBuilder locInstance) { Type actualType = this.Generate(expr, locInstance); this.GenerateConvertToType(actualType, type); return type; } private void GenerateConvertToType(Type actualType, Type expectedType, Type readerMethodType) { GenerateConvertToType(readerMethodType, actualType); GenerateConvertToType(actualType, expectedType); } [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] private void GenerateConvertToType(Type actualType, Type expectedType) { if (expectedType != actualType && !(!actualType.IsValueType && actualType.IsSubclassOf(expectedType)) ) { Type genActualType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; Type genExpectedType = expectedType.IsGenericType ? expectedType.GetGenericTypeDefinition() : null; Type[] genExpectedTypeArgs = genExpectedType != null ? expectedType.GetGenericArguments() : null; Type elemType = TypeSystem.GetElementType(actualType); Type seqType = TypeSystem.GetSequenceType(elemType); bool actualIsSequence = seqType.IsAssignableFrom(actualType); if (expectedType == typeof(object) && actualType.IsValueType) { gen.Emit(OpCodes.Box, actualType); } else if (actualType == typeof(object) && expectedType.IsValueType) { gen.Emit(OpCodes.Unbox_Any, expectedType); } // is one type an explicit subtype of the other? else if ((actualType.IsSubclassOf(expectedType) || expectedType.IsSubclassOf(actualType)) && !actualType.IsValueType && !expectedType.IsValueType) { // (T)expr gen.Emit(OpCodes.Castclass, expectedType); } // do we expected a sequence of a different element type? else if (genExpectedType == typeof(IEnumerable<>) && actualIsSequence) { if (elementType.IsInterface || genExpectedTypeArgs[0].IsInterface || elementType.IsSubclassOf(genExpectedTypeArgs[0]) || genExpectedTypeArgs[0].IsSubclassOf(elementType) || TypeSystem.GetNonNullableType(elementType) == TypeSystem.GetNonNullableType(genExpectedTypeArgs[0]) ) { // reference or nullable conversion use seq.Cast() MethodInfo miCast = TypeSystem.FindSequenceMethod("Cast", new Type[] { seqType }, genExpectedTypeArgs[0]); System.Diagnostics.Debug.Assert(miCast != null); gen.Emit(OpCodes.Call, miCast); } else { // otherwise use orb.Convert(sequence) Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); MethodInfo miConvert = TypeSystem.FindStaticMethod(orbType, "Convert", new Type[] { seqType }, genExpectedTypeArgs[0]); System.Diagnostics.Debug.Assert(miConvert != null); gen.Emit(OpCodes.Call, miConvert); } } // Do we have a sequence where we wanted a singleton? else if (expectedType == elemType && actualIsSequence) { // seq.SingleOrDefault() MethodInfo miFirst = TypeSystem.FindSequenceMethod("SingleOrDefault", new Type[] { seqType }, expectedType); System.Diagnostics.Debug.Assert(miFirst != null); gen.Emit(OpCodes.Call, miFirst); } // do we have a non-nullable value where we want a nullable value? else if (TypeSystem.IsNullableType(expectedType) && TypeSystem.GetNonNullableType(expectedType) == actualType) { // new Nullable(expr) ConstructorInfo ci = expectedType.GetConstructor(new Type[] { actualType }); gen.Emit(OpCodes.Newobj, ci); } // do we have a nullable value where we want a non-nullable value? else if (TypeSystem.IsNullableType(actualType) && TypeSystem.GetNonNullableType(actualType) == expectedType) { // expr.GetValueOrDefault() LocalBuilder loc = gen.DeclareLocal(actualType); gen.Emit(OpCodes.Stloc, loc); gen.Emit(OpCodes.Ldloca, loc); this.GenerateGetValueOrDefault(actualType); } // do we have a value when we want an EntityRef or Link of that value else if (genExpectedType == typeof(EntityRef<>) || genExpectedType == typeof(Link<>)) { if (actualType.IsAssignableFrom(genExpectedTypeArgs[0])) { // new T(expr) if (actualType != genExpectedTypeArgs[0]) { // Ensure that the actual runtime type of the value is // compatible. For example, in inheritance scenarios // the Type of the value can vary from row to row. this.GenerateConvertToType(actualType, genExpectedTypeArgs[0]); } ConstructorInfo ci = expectedType.GetConstructor(new Type[] { genExpectedTypeArgs[0] }); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); } else if (seqType.IsAssignableFrom(actualType)) { // new T(seq.SingleOrDefault()) MethodInfo miFirst = TypeSystem.FindSequenceMethod("SingleOrDefault", new Type[] { seqType }, elemType); System.Diagnostics.Debug.Assert(miFirst != null); gen.Emit(OpCodes.Call, miFirst); ConstructorInfo ci = expectedType.GetConstructor(new Type[] { elemType }); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); } else { throw Error.CannotConvertToEntityRef(actualType); } } // do we have a sequence when we want IQueryable/IOrderedQueryable? else if ((expectedType == typeof(IQueryable) || expectedType == typeof(IOrderedQueryable)) && typeof(IEnumerable).IsAssignableFrom(actualType)) { // seq.AsQueryable() MethodInfo miAsQueryable = TypeSystem.FindQueryableMethod("AsQueryable", new Type[] { typeof(IEnumerable) }); System.Diagnostics.Debug.Assert(miAsQueryable != null); gen.Emit(OpCodes.Call, miAsQueryable); if (genExpectedType == typeof(IOrderedQueryable)) { gen.Emit(OpCodes.Castclass, expectedType); } } // do we have a sequence when we want IQuerayble/IOrderedQueryable? else if ((genExpectedType == typeof(IQueryable<>) || genExpectedType == typeof(IOrderedQueryable<>)) && actualIsSequence ) { if (elemType != genExpectedTypeArgs[0]) { seqType = typeof(IEnumerable<>).MakeGenericType(genExpectedTypeArgs); this.GenerateConvertToType(actualType, seqType); elemType = genExpectedTypeArgs[0]; } // seq.AsQueryable() MethodInfo miAsQueryable = TypeSystem.FindQueryableMethod("AsQueryable", new Type[] { seqType }, elemType); System.Diagnostics.Debug.Assert(miAsQueryable != null); gen.Emit(OpCodes.Call, miAsQueryable); if (genExpectedType == typeof(IOrderedQueryable<>)) { gen.Emit(OpCodes.Castclass, expectedType); } } // do we have a sequence when we want IOrderedEnumerable? else if (genExpectedType == typeof(IOrderedEnumerable<>) && actualIsSequence) { if (elemType != genExpectedTypeArgs[0]) { seqType = typeof(IEnumerable<>).MakeGenericType(genExpectedTypeArgs); this.GenerateConvertToType(actualType, seqType); elemType = genExpectedTypeArgs[0]; } // new OrderedResults(seq) Type orbType = typeof(ObjectMaterializer<>).MakeGenericType(this.compiler.dataReaderType); MethodInfo miCreateOrderedEnumerable = TypeSystem.FindStaticMethod(orbType, "CreateOrderedEnumerable", new Type[] { seqType }, elemType); System.Diagnostics.Debug.Assert(miCreateOrderedEnumerable != null); gen.Emit(OpCodes.Call, miCreateOrderedEnumerable); } // do we have a sequence when we want EntitySet ? else if (genExpectedType == typeof(EntitySet<>) && actualIsSequence) { if (elemType != genExpectedTypeArgs[0]) { seqType = typeof(IEnumerable<>).MakeGenericType(genExpectedTypeArgs); this.GenerateConvertToType(actualType, seqType); actualType = seqType; elemType = genExpectedTypeArgs[0]; } // loc = new EntitySet(); loc.Assign(seq); loc LocalBuilder locSeq = gen.DeclareLocal(actualType); gen.Emit(OpCodes.Stloc, locSeq); ConstructorInfo ci = expectedType.GetConstructor(System.Type.EmptyTypes); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); LocalBuilder locEs = gen.DeclareLocal(expectedType); gen.Emit(OpCodes.Stloc, locEs); gen.Emit(OpCodes.Ldloc, locEs); gen.Emit(OpCodes.Ldloc, locSeq); MethodInfo miAssign = expectedType.GetMethod("Assign", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { seqType }, null); System.Diagnostics.Debug.Assert(miAssign != null); gen.Emit(GetMethodCallOpCode(miAssign), miAssign); gen.Emit(OpCodes.Ldloc, locEs); } // do we have a sequence when we want something assignable from List? else if (typeof(IEnumerable).IsAssignableFrom(expectedType) && actualIsSequence && expectedType.IsAssignableFrom(typeof(List<>).MakeGenericType(elemType)) ) { // new List(seq) Type listType = typeof(List<>).MakeGenericType(elemType); ConstructorInfo ci = listType.GetConstructor(new Type[] { seqType }); System.Diagnostics.Debug.Assert(ci != null); gen.Emit(OpCodes.Newobj, ci); } // do we have a sequence when we want T[]? else if (expectedType.IsArray && expectedType.GetArrayRank() == 1 && !actualType.IsArray && seqType.IsAssignableFrom(actualType) && expectedType.GetElementType().IsAssignableFrom(elemType) ) { // seq.ToArray() MethodInfo miToArray = TypeSystem.FindSequenceMethod("ToArray", new Type[] { seqType }, elemType); System.Diagnostics.Debug.Assert(miToArray != null); gen.Emit(OpCodes.Call, miToArray); } // do we have a sequence when we want some other collection type? else if (expectedType.IsClass && typeof(ICollection<>).MakeGenericType(elemType).IsAssignableFrom(expectedType) && expectedType.GetConstructor(System.Type.EmptyTypes) != null && seqType.IsAssignableFrom(actualType) ) { throw Error.GeneralCollectionMaterializationNotSupported(); } // do we have an int when we want a bool? else if (expectedType == typeof(bool) && actualType == typeof(int)) { // expr != 0 Label labZero = gen.DefineLabel(); Label labExit = gen.DefineLabel(); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue_S, labZero); gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Br_S, labExit); gen.MarkLabel(labZero); gen.Emit(OpCodes.Ldc_I4_0); gen.MarkLabel(labExit); } else { // last-ditch attempt: convert at runtime using DBConvert // DBConvert.ChangeType(type, expr) if (actualType.IsValueType) { gen.Emit(OpCodes.Box, actualType); } gen.Emit(OpCodes.Ldtoken, expectedType); MethodInfo miGetTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Static | BindingFlags.Public); System.Diagnostics.Debug.Assert(miGetTypeFromHandle != null); gen.Emit(OpCodes.Call, miGetTypeFromHandle); MethodInfo miChangeType = typeof(DBConvert).GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(object), typeof(Type) }, null); System.Diagnostics.Debug.Assert(miChangeType != null); gen.Emit(OpCodes.Call, miChangeType); if (expectedType.IsValueType) { gen.Emit(OpCodes.Unbox_Any, expectedType); } else if (expectedType != typeof(object)) { gen.Emit(OpCodes.Castclass, expectedType); } } } } private Type GenerateColumnReference(SqlColumnRef cref) { this.GenerateColumnAccess(cref.ClrType, cref.SqlType, cref.Column.Ordinal, null); return cref.ClrType; } private Type GenerateUserColumn(SqlUserColumn suc) { // if the user column is not named, it must be the only one! if (string.IsNullOrEmpty(suc.Name)) { this.GenerateColumnAccess(suc.ClrType, suc.SqlType, 0, null); return suc.ClrType; } int iName = this.namedColumns.Count; this.namedColumns.Add(new NamedColumn(suc.Name, suc.IsRequired)); Label labNotDefined = gen.DefineLabel(); Label labExit = gen.DefineLabel(); LocalBuilder locOrdinal = gen.DeclareLocal(typeof(int)); // ordinal = session.ordinals[i] this.GenerateAccessOrdinals(); this.GenerateConstInt(iName); this.GenerateArrayAccess(typeof(int), false); gen.Emit(OpCodes.Stloc, locOrdinal); // if (ordinal < 0) goto labNotDefined gen.Emit(OpCodes.Ldloc, locOrdinal); this.GenerateConstInt(0); gen.Emit(OpCodes.Clt); gen.Emit(OpCodes.Brtrue, labNotDefined); // access column at ordinal position this.GenerateColumnAccess(suc.ClrType, suc.SqlType, 0, locOrdinal); gen.Emit(OpCodes.Br_S, labExit); // not defined? gen.MarkLabel(labNotDefined); this.GenerateDefault(suc.ClrType, false); gen.MarkLabel(labExit); return suc.ClrType; } private void GenerateColumnAccess(Type cType, ProviderType pType, int ordinal, LocalBuilder locOrdinal) { Type rType = pType.GetClosestRuntimeType(); MethodInfo readerMethod = this.GetReaderMethod(this.compiler.dataReaderType, rType); MethodInfo bufferMethod = this.GetReaderMethod(typeof(DbDataReader), rType); Label labIsNull = gen.DefineLabel(); Label labExit = gen.DefineLabel(); Label labReadFromBuffer = gen.DefineLabel(); // if (buffer != null) goto ReadFromBuffer this.GenerateAccessBufferReader(); gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brfalse, labReadFromBuffer); // read from DataReader // this.reader.IsNull? this.GenerateAccessDataReader(); if (locOrdinal != null) gen.Emit(OpCodes.Ldloc, locOrdinal); else this.GenerateConstInt(ordinal); gen.Emit(GetMethodCallOpCode(this.compiler.miDRisDBNull), this.compiler.miDRisDBNull); gen.Emit(OpCodes.Brtrue, labIsNull); // this.reader.GetXXX() this.GenerateAccessDataReader(); if (locOrdinal != null) gen.Emit(OpCodes.Ldloc, locOrdinal); else this.GenerateConstInt(ordinal); gen.Emit(GetMethodCallOpCode(readerMethod), readerMethod); this.GenerateConvertToType(rType, cType, readerMethod.ReturnType); gen.Emit(OpCodes.Br_S, labExit); // read from BUFFER gen.MarkLabel(labReadFromBuffer); // this.bufferReader.IsNull? this.GenerateAccessBufferReader(); if (locOrdinal != null) gen.Emit(OpCodes.Ldloc, locOrdinal); else this.GenerateConstInt(ordinal); gen.Emit(GetMethodCallOpCode(this.compiler.miBRisDBNull), this.compiler.miBRisDBNull); gen.Emit(OpCodes.Brtrue, labIsNull); // this.bufferReader.GetXXX() this.GenerateAccessBufferReader(); if (locOrdinal != null) gen.Emit(OpCodes.Ldloc, locOrdinal); else this.GenerateConstInt(ordinal); gen.Emit(GetMethodCallOpCode(bufferMethod), bufferMethod); this.GenerateConvertToType(rType, cType, bufferMethod.ReturnType); gen.Emit(OpCodes.Br_S, labExit); // return NULL gen.MarkLabel(labIsNull); this.GenerateDefault(cType); gen.MarkLabel(labExit); } private Type GenerateClientCase(SqlClientCase scc, bool isDeferred, LocalBuilder locInstance) { LocalBuilder locDiscriminator = gen.DeclareLocal(scc.Expression.ClrType); this.GenerateExpressionForType(scc.Expression, scc.Expression.ClrType); gen.Emit(OpCodes.Stloc, locDiscriminator); Label labNext = gen.DefineLabel(); Label labEnd = gen.DefineLabel(); for (int i = 0, n = scc.Whens.Count; i < n; i++) { if (i > 0) { gen.MarkLabel(labNext); labNext = gen.DefineLabel(); } SqlClientWhen when = scc.Whens[i]; if (when.Match != null) { gen.Emit(OpCodes.Ldloc, locDiscriminator); this.GenerateExpressionForType(when.Match, scc.Expression.ClrType); this.GenerateEquals(locDiscriminator.LocalType); gen.Emit(OpCodes.Brfalse, labNext); } if (isDeferred) { this.GenerateDeferredSource(when.Value, locInstance); } else { this.GenerateExpressionForType(when.Value, scc.ClrType); } gen.Emit(OpCodes.Br, labEnd); } gen.MarkLabel(labEnd); return scc.ClrType; } private Type GenerateTypeCase(SqlTypeCase stc) { LocalBuilder locDiscriminator = gen.DeclareLocal(stc.Discriminator.ClrType); this.GenerateExpressionForType(stc.Discriminator, stc.Discriminator.ClrType); gen.Emit(OpCodes.Stloc, locDiscriminator); Label labNext = gen.DefineLabel(); Label labEnd = gen.DefineLabel(); bool hasDefault = false; for (int i = 0, n = stc.Whens.Count; i < n; i++) { if (i > 0) { gen.MarkLabel(labNext); labNext = gen.DefineLabel(); } SqlTypeCaseWhen when = stc.Whens[i]; if (when.Match != null) { gen.Emit(OpCodes.Ldloc, locDiscriminator); SqlValue vMatch = when.Match as SqlValue; System.Diagnostics.Debug.Assert(vMatch != null); this.GenerateConstant(locDiscriminator.LocalType, vMatch.Value); this.GenerateEquals(locDiscriminator.LocalType); gen.Emit(OpCodes.Brfalse, labNext); } else { System.Diagnostics.Debug.Assert(i == n - 1); hasDefault = true; } this.GenerateExpressionForType(when.TypeBinding, stc.ClrType); gen.Emit(OpCodes.Br, labEnd); } gen.MarkLabel(labNext); if (!hasDefault) { this.GenerateConstant(stc.ClrType, null); } gen.MarkLabel(labEnd); return stc.ClrType; } private Type GenerateDiscriminatedType(SqlDiscriminatedType dt) { System.Diagnostics.Debug.Assert(dt.ClrType == typeof(Type)); LocalBuilder locDiscriminator = gen.DeclareLocal(dt.Discriminator.ClrType); this.GenerateExpressionForType(dt.Discriminator, dt.Discriminator.ClrType); gen.Emit(OpCodes.Stloc, locDiscriminator); return this.GenerateDiscriminatedType(dt.TargetType, locDiscriminator, dt.Discriminator.SqlType); } private Type GenerateDiscriminatedType(MetaType targetType, LocalBuilder locDiscriminator, ProviderType discriminatorType) { System.Diagnostics.Debug.Assert(targetType != null && locDiscriminator != null); MetaType defType = null; Label labNext = gen.DefineLabel(); Label labEnd = gen.DefineLabel(); foreach (MetaType imt in targetType.InheritanceTypes) { if (imt.InheritanceCode != null) { if (imt.IsInheritanceDefault) { defType = imt; } // disc == code? gen.Emit(OpCodes.Ldloc, locDiscriminator); object code = InheritanceRules.InheritanceCodeForClientCompare(imt.InheritanceCode, discriminatorType); this.GenerateConstant(locDiscriminator.LocalType, code); this.GenerateEquals(locDiscriminator.LocalType); gen.Emit(OpCodes.Brfalse, labNext); this.GenerateConstant(typeof(Type), imt.Type); gen.Emit(OpCodes.Br, labEnd); gen.MarkLabel(labNext); labNext = gen.DefineLabel(); } } gen.MarkLabel(labNext); if (defType != null) { this.GenerateConstant(typeof(Type), defType.Type); } else { this.GenerateDefault(typeof(Type)); } gen.MarkLabel(labEnd); return typeof(Type); } private Type GenerateSearchedCase(SqlSearchedCase ssc) { Label labNext = gen.DefineLabel(); Label labEnd = gen.DefineLabel(); for (int i = 0, n = ssc.Whens.Count; i < n; i++) { if (i > 0) { gen.MarkLabel(labNext); labNext = gen.DefineLabel(); } SqlWhen when = ssc.Whens[i]; if (when.Match != null) { this.GenerateExpressionForType(when.Match, typeof(bool)); // test this.GenerateConstInt(0); gen.Emit(OpCodes.Ceq); gen.Emit(OpCodes.Brtrue, labNext); } this.GenerateExpressionForType(when.Value, ssc.ClrType); gen.Emit(OpCodes.Br, labEnd); } gen.MarkLabel(labNext); if (ssc.Else != null) { this.GenerateExpressionForType(ssc.Else, ssc.ClrType); } gen.MarkLabel(labEnd); return ssc.ClrType; } private void GenerateEquals(Type type) { switch (Type.GetTypeCode(type)) { case TypeCode.Object: case TypeCode.String: case TypeCode.DBNull: if (type.IsValueType) { LocalBuilder locLeft = gen.DeclareLocal(type); LocalBuilder locRight = gen.DeclareLocal(type); gen.Emit(OpCodes.Stloc, locRight); gen.Emit(OpCodes.Stloc, locLeft); gen.Emit(OpCodes.Ldloc, locLeft); gen.Emit(OpCodes.Box, type); gen.Emit(OpCodes.Ldloc, locRight); gen.Emit(OpCodes.Box, type); } MethodInfo miEquals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public); System.Diagnostics.Debug.Assert(miEquals != null); gen.Emit(GetMethodCallOpCode(miEquals), miEquals); break; default: gen.Emit(OpCodes.Ceq); break; } } private void GenerateDefault(Type type) { this.GenerateDefault(type, true); } private void GenerateDefault(Type type, bool throwIfNotNullable) { if (type.IsValueType) { if (!throwIfNotNullable || TypeSystem.IsNullableType(type)) { LocalBuilder loc = gen.DeclareLocal(type); gen.Emit(OpCodes.Ldloca, loc); gen.Emit(OpCodes.Initobj, type); gen.Emit(OpCodes.Ldloc, loc); } else { gen.Emit(OpCodes.Ldtoken, type); gen.Emit(OpCodes.Call, typeof(Type).GetMethod( "GetTypeFromHandle", BindingFlags.Static | BindingFlags.Public)); MethodInfo mi = typeof(ObjectMaterializer<>) .MakeGenericType(this.compiler.dataReaderType) .GetMethod("ErrorAssignmentToNull", BindingFlags.Static | BindingFlags.Public); System.Diagnostics.Debug.Assert(mi != null); gen.Emit(OpCodes.Call, mi); gen.Emit(OpCodes.Throw); } } else { gen.Emit(OpCodes.Ldnull); } } private static Type[] readMethodSignature = new Type[] { typeof(int) }; [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Unknown reason.")] private MethodInfo GetReaderMethod(Type readerType, Type valueType) { if (valueType.IsEnum) valueType = valueType.BaseType; TypeCode tc = Type.GetTypeCode(valueType); string name; if (tc == TypeCode.Single) { name = "GetFloat"; } else { name = "Get" + valueType.Name; } MethodInfo readerMethod = readerType.GetMethod( name, BindingFlags.Instance | BindingFlags.Public, null, readMethodSignature, null ); if (readerMethod == null) { readerMethod = readerType.GetMethod( "GetValue", BindingFlags.Instance | BindingFlags.Public, null, readMethodSignature, null ); } System.Diagnostics.Debug.Assert(readerMethod != null); return readerMethod; } private void GenerateHasValue(Type nullableType) { MethodInfo mi = nullableType.GetMethod("get_HasValue", BindingFlags.Instance | BindingFlags.Public); gen.Emit(OpCodes.Call, mi); } private void GenerateGetValue(Type nullableType) { MethodInfo mi = nullableType.GetMethod("get_Value", BindingFlags.Instance | BindingFlags.Public); gen.Emit(OpCodes.Call, mi); } private void GenerateGetValueOrDefault(Type nullableType) { MethodInfo mi = nullableType.GetMethod("GetValueOrDefault", System.Type.EmptyTypes); gen.Emit(OpCodes.Call, mi); } private Type GenerateGlobalAccess(int iGlobal, Type type) { this.GenerateAccessGlobals(); if (type.IsValueType) { this.GenerateConstInt(iGlobal); gen.Emit(OpCodes.Ldelem_Ref); Type varType = typeof(StrongBox<>).MakeGenericType(type); gen.Emit(OpCodes.Castclass, varType); FieldInfo fi = varType.GetField("Value", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); gen.Emit(OpCodes.Ldfld, fi); } else { this.GenerateConstInt(iGlobal); gen.Emit(OpCodes.Ldelem_Ref); this.GenerateConvertToType(typeof(object), type); gen.Emit(OpCodes.Castclass, type); } return type; } private int AddGlobal(Type type, object value) { int iGlobal = this.globals.Count; if (type.IsValueType) { this.globals.Add(Activator.CreateInstance(typeof(StrongBox<>).MakeGenericType(type), new object[] { value })); } else { this.globals.Add(value); } return iGlobal; } private int AllocateLocal() { return this.nLocals++; } private void GenerateStoreMember(MemberInfo mi) { FieldInfo fi = mi as FieldInfo; if (fi != null) { gen.Emit(OpCodes.Stfld, fi); } else { PropertyInfo pi = (PropertyInfo)mi; MethodInfo meth = pi.GetSetMethod(true); System.Diagnostics.Debug.Assert(meth != null); gen.Emit(GetMethodCallOpCode(meth), meth); } } private void GenerateLoadMember(MemberInfo mi) { FieldInfo fi = mi as FieldInfo; if (fi != null) { gen.Emit(OpCodes.Ldfld, fi); } else { PropertyInfo pi = (PropertyInfo)mi; MethodInfo meth = pi.GetGetMethod(true); gen.Emit(GetMethodCallOpCode(meth), meth); } } [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", Justification = "[....]: The variable tc for which the rule fires is used in both a Debug.Assert and in a switch statement")] private void GenerateArrayAssign(Type type) { // This method was copied out of the expression compiler codebase. // Since DLINQ doesn't currently consume array indexers most of this // function goes unused. Currently, the DLINQ materializer only // accesses only ararys of objects and array of integers. // The code is comment out to improve code coverage test. // If you see one of the following assert fails, try to enable // the comment out code. if (type.IsEnum) { gen.Emit(OpCodes.Stelem, type); } else { TypeCode tc = Type.GetTypeCode(type); switch (tc) { case TypeCode.SByte: case TypeCode.Byte: gen.Emit(OpCodes.Stelem_I1); break; case TypeCode.Int16: case TypeCode.UInt16: gen.Emit(OpCodes.Stelem_I2); break; case TypeCode.Int32: case TypeCode.UInt32: gen.Emit(OpCodes.Stelem_I4); break; case TypeCode.Int64: case TypeCode.UInt64: gen.Emit(OpCodes.Stelem_I8); break; case TypeCode.Single: gen.Emit(OpCodes.Stelem_R4); break; case TypeCode.Double: gen.Emit(OpCodes.Stelem_R8); break; default: if (type.IsValueType) { gen.Emit(OpCodes.Stelem, type); } else { gen.Emit(OpCodes.Stelem_Ref); } break; } } } [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "address", Justification = "[....]: See comments in source. Usage commented out to improve code coverage test")] private Type GenerateArrayAccess(Type type, bool address) { // This method was copied out of the expression compiler codebase. // Since DLINQ doesn't currently consume array indexers most of this // function goes unused. Currently, the DLINQ materializer only // accesses arrays of objects and array of integers. // The code is comment out to improve code coverage test. // If you see one of the following asserts fails, try to enable // the comment out code. System.Diagnostics.Debug.Assert(address == false); // if (address) // { // gen.Emit(OpCodes.Ldelema); // return type.MakeByRefType(); // } // else { if (type.IsEnum) { System.Diagnostics.Debug.Assert(false); // gen.Emit(OpCodes.Ldelem, type); } else { TypeCode tc = Type.GetTypeCode(type); System.Diagnostics.Debug.Assert(tc == TypeCode.Int32); switch (tc) { //case TypeCode.SByte: // gen.Emit(OpCodes.Ldelem_I1); // break; //case TypeCode.Int16: // gen.Emit(OpCodes.Ldelem_I2); // break; case TypeCode.Int32: gen.Emit(OpCodes.Ldelem_I4); break; //case TypeCode.Int64: // gen.Emit(OpCodes.Ldelem_I8); // break; //case TypeCode.Single: // gen.Emit(OpCodes.Ldelem_R4); // break; //case TypeCode.Double: // gen.Emit(OpCodes.Ldelem_R8); // break; //default: // if (type.IsValueType) { // gen.Emit(OpCodes.Ldelem, type); // } // else { // gen.Emit(OpCodes.Ldelem_Ref); // } // break; } } return type; } } private Type GenerateConstant(Type type, object value) { if (value == null) { if (type.IsValueType) { LocalBuilder loc = gen.DeclareLocal(type); gen.Emit(OpCodes.Ldloca, loc); gen.Emit(OpCodes.Initobj, type); gen.Emit(OpCodes.Ldloc, loc); } else { gen.Emit(OpCodes.Ldnull); } } else { TypeCode tc = Type.GetTypeCode(type); switch (tc) { case TypeCode.Boolean: this.GenerateConstInt((bool)value ? 1 : 0); break; case TypeCode.SByte: this.GenerateConstInt((SByte)value); gen.Emit(OpCodes.Conv_I1); break; case TypeCode.Int16: this.GenerateConstInt((Int16)value); gen.Emit(OpCodes.Conv_I2); break; case TypeCode.Int32: this.GenerateConstInt((Int32)value); break; case TypeCode.Int64: gen.Emit(OpCodes.Ldc_I8, (Int64)value); break; case TypeCode.Single: gen.Emit(OpCodes.Ldc_R4, (float)value); break; case TypeCode.Double: gen.Emit(OpCodes.Ldc_R8, (double)value); break; default: int iGlobal = this.AddGlobal(type, value); return this.GenerateGlobalAccess(iGlobal, type); } } return type; } private void GenerateConstInt(int value) { switch (value) { case 0: gen.Emit(OpCodes.Ldc_I4_0); break; case 1: gen.Emit(OpCodes.Ldc_I4_1); break; case 2: gen.Emit(OpCodes.Ldc_I4_2); break; case 3: gen.Emit(OpCodes.Ldc_I4_3); break; case 4: gen.Emit(OpCodes.Ldc_I4_4); break; case 5: gen.Emit(OpCodes.Ldc_I4_5); break; case 6: gen.Emit(OpCodes.Ldc_I4_6); break; case 7: gen.Emit(OpCodes.Ldc_I4_7); break; case 8: gen.Emit(OpCodes.Ldc_I4_8); break; default: if (value == -1) { gen.Emit(OpCodes.Ldc_I4_M1); } else if (value >= -127 && value < 128) { gen.Emit(OpCodes.Ldc_I4_S, (sbyte)value); } else { gen.Emit(OpCodes.Ldc_I4, value); } break; } } } struct NamedColumn { string name; bool isRequired; internal NamedColumn(string name, bool isRequired) { this.name = name; this.isRequired = isRequired; } internal string Name { get { return this.name; } } internal bool IsRequired { get { return this.isRequired; } } } class ObjectReaderFactory : IObjectReaderFactory where TDataReader : DbDataReader { Func, TObject> fnMaterialize; NamedColumn[] namedColumns; object[] globals; int nLocals; internal ObjectReaderFactory( Func, TObject> fnMaterialize, NamedColumn[] namedColumns, object[] globals, int nLocals ) { this.fnMaterialize = fnMaterialize; this.namedColumns = namedColumns; this.globals = globals; this.nLocals = nLocals; } public IObjectReader Create(DbDataReader dataReader, bool disposeDataReader, IReaderProvider provider, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries) { ObjectReaderSession session = new ObjectReaderSession((TDataReader)dataReader, provider, parentArgs, userArgs, subQueries); return session.CreateReader(this.fnMaterialize, this.namedColumns, this.globals, this.nLocals, disposeDataReader); } public IObjectReader GetNextResult(IObjectReaderSession session, bool disposeDataReader) { ObjectReaderSession ors = (ObjectReaderSession)session; IObjectReader reader = ors.GetNextResult(this.fnMaterialize, this.namedColumns, this.globals, this.nLocals, disposeDataReader); if (reader == null && disposeDataReader) { ors.Dispose(); } return reader; } } abstract class ObjectReaderBase : ObjectMaterializer where TDataReader : DbDataReader { protected ObjectReaderSession session; bool hasRead; bool hasCurrentRow; bool isFinished; IDataServices services; internal ObjectReaderBase( ObjectReaderSession session, NamedColumn[] namedColumns, object[] globals, object[] arguments, int nLocals ) : base() { this.session = session; this.services = session.Provider.Services; this.DataReader = session.DataReader; this.Globals = globals; this.Arguments = arguments; if (nLocals > 0) { this.Locals = new object[nLocals]; } if (this.session.IsBuffered) { this.Buffer(); } this.Ordinals = this.GetColumnOrdinals(namedColumns); } // This method is called from within this class's constructor (through a call to Buffer()) so it is sealed to prevent // derived classes from overriding it. See FxCop rule CA2214 for more information on why this is necessary. public override sealed bool Read() { if (this.isFinished) { return false; } if (this.BufferReader != null) { this.hasCurrentRow = this.BufferReader.Read(); } else { this.hasCurrentRow = this.DataReader.Read(); } if (!this.hasCurrentRow) { this.isFinished = true; this.session.Finish(this); } this.hasRead = true; return this.hasCurrentRow; } internal bool IsBuffered { get { return this.BufferReader != null; } } [SuppressMessage("Microsoft.Globalization", "CA1306:SetLocaleForDataTypes", Justification = "[....]: Used only as a buffer and never used for string comparison.")] internal void Buffer() { if (this.BufferReader == null && (this.hasCurrentRow || !this.hasRead)) { if (this.session.IsBuffered) { this.BufferReader = this.session.GetNextBufferedReader(); } else { DataSet ds = new DataSet(); ds.EnforceConstraints = false; DataTable bufferTable = new DataTable(); ds.Tables.Add(bufferTable); string[] names = this.session.GetActiveNames(); bufferTable.Load(new Rereader(this.DataReader, this.hasCurrentRow, null), LoadOption.OverwriteChanges); this.BufferReader = new Rereader(bufferTable.CreateDataReader(), false, names); } if (this.hasCurrentRow) { this.Read(); } } } public override object InsertLookup(int iMetaType, object instance) { MetaType mType = (MetaType)this.Globals[iMetaType]; return this.services.InsertLookupCachedObject(mType, instance); } public override void SendEntityMaterialized(int iMetaType, object instance) { MetaType mType = (MetaType)this.Globals[iMetaType]; this.services.OnEntityMaterialized(mType, instance); } public override IEnumerable ExecuteSubQuery(int iSubQuery, object[] parentArgs) { if (this.session.ParentArguments != null) { // Create array to accumulate args, and add both parent // args and the supplied args to the array int nParent = this.session.ParentArguments.Length; object[] tmp = new object[nParent + parentArgs.Length]; Array.Copy(this.session.ParentArguments, tmp, nParent); Array.Copy(parentArgs, 0, tmp, nParent, parentArgs.Length); parentArgs = tmp; } ICompiledSubQuery subQuery = this.session.SubQueries[iSubQuery]; IEnumerable results = (IEnumerable)subQuery.Execute(this.session.Provider, parentArgs, this.session.UserArguments).ReturnValue; return results; } public override bool CanDeferLoad { get { return this.services.Context.DeferredLoadingEnabled; } } public override IEnumerable GetLinkSource(int iGlobalLink, int iLocalFactory, object[] keyValues) { IDeferredSourceFactory factory = (IDeferredSourceFactory)this.Locals[iLocalFactory]; if (factory == null) { MetaDataMember member = (MetaDataMember)this.Globals[iGlobalLink]; factory = this.services.GetDeferredSourceFactory(member); this.Locals[iLocalFactory] = factory; } return (IEnumerable)factory.CreateDeferredSource(keyValues); } public override IEnumerable GetNestedLinkSource(int iGlobalLink, int iLocalFactory, object instance) { IDeferredSourceFactory factory = (IDeferredSourceFactory)this.Locals[iLocalFactory]; if (factory == null) { MetaDataMember member = (MetaDataMember)this.Globals[iGlobalLink]; factory = this.services.GetDeferredSourceFactory(member); this.Locals[iLocalFactory] = factory; } return (IEnumerable)factory.CreateDeferredSource(instance); } private int[] GetColumnOrdinals(NamedColumn[] namedColumns) { DbDataReader reader = null; if (this.BufferReader != null) { reader = this.BufferReader; } else { reader = this.DataReader; } if (namedColumns == null || namedColumns.Length == 0) { return null; } int[] columnOrdinals = new int[namedColumns.Length]; Dictionary lookup = new Dictionary(StringComparer.OrdinalIgnoreCase); //we need to compare the quoted names on both sides //because the designer might quote the name unnecessarily for (int i = 0, n = reader.FieldCount; i < n; i++) { lookup[SqlIdentifier.QuoteCompoundIdentifier(reader.GetName(i))] = i; } for (int i = 0, n = namedColumns.Length; i < n; i++) { int ordinal; if (lookup.TryGetValue(SqlIdentifier.QuoteCompoundIdentifier(namedColumns[i].Name), out ordinal)) { columnOrdinals[i] = ordinal; } else if (namedColumns[i].IsRequired) { throw Error.RequiredColumnDoesNotExist(namedColumns[i].Name); } else { columnOrdinals[i] = -1; } } return columnOrdinals; } } class ObjectReader : ObjectReaderBase, IEnumerator, IObjectReader, IDisposable where TDataReader : DbDataReader { Func, TObject> fnMaterialize; TObject current; bool disposeSession; internal ObjectReader( ObjectReaderSession session, NamedColumn[] namedColumns, object[] globals, object[] arguments, int nLocals, bool disposeSession, Func, TObject> fnMaterialize ) : base(session, namedColumns, globals, arguments, nLocals) { this.disposeSession = disposeSession; this.fnMaterialize = fnMaterialize; } public IObjectReaderSession Session { get { return this.session; } } public void Dispose() { #if PERFORMANCE_BUILD if (this.CollectQueryPerf) { timer.Stop(); started = false; pcSqlQueryEnumGetCurrent.IncrementBy(timer.Duration); bpcSqlQueryEnumGetCurrent.Increment(); } #endif // Technically, calling GC.SuppressFinalize is not required because the class does not // have a finalizer, but it does no harm, protects against the case where a finalizer is added // in the future, and prevents an FxCop warning. GC.SuppressFinalize(this); if (this.disposeSession) { this.session.Dispose(); } } public bool MoveNext() { #if PERFORMANCE_BUILD if (this.CollectQueryPerf) { if (!started) { started = true; timer.Start(); } } #endif if (this.Read()) { this.current = this.fnMaterialize(this); return true; } else { this.current = default(TObject); this.Dispose(); return false; } } public TObject Current { get { return this.current; } } public void Reset() { } object IEnumerator.Current { get { return this.Current; } } #if PERFORMANCE_BUILD PerformanceCounter pcSqlQueryEnumGetCurrent = null; PerformanceCounter bpcSqlQueryEnumGetCurrent = null; PerfTimer timer = null; bool collectQueryPerf; bool collectQueryPerfInitialized = false; bool started; private bool CollectQueryPerf { get { if (!collectQueryPerfInitialized) { collectQueryPerf = this.enumerable.session.context.CollectQueryPerf; if (collectQueryPerf) { pcSqlQueryEnumGetCurrent = new PerformanceCounter("DLinq", "SqlQueryEnumGetCurrentElapsedTime", false); bpcSqlQueryEnumGetCurrent = new PerformanceCounter("DLinq", "SqlQueryEnumGetCurrentElapsedTimeBase", false); timer = new PerfTimer(); } collectQueryPerfInitialized = true; } return this.collectQueryPerf; } } #endif } class ObjectReaderSession : IObjectReaderSession, IDisposable, IConnectionUser where TDataReader : DbDataReader { TDataReader dataReader; ObjectReaderBase currentReader; IReaderProvider provider; List buffer; int iNextBufferedReader; bool isDisposed; bool isDataReaderDisposed; bool hasResults; object[] parentArgs; object[] userArgs; ICompiledSubQuery[] subQueries; internal ObjectReaderSession( TDataReader dataReader, IReaderProvider provider, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries ) { this.dataReader = dataReader; this.provider = provider; this.parentArgs = parentArgs; this.userArgs = userArgs; this.subQueries = subQueries; this.hasResults = true; } internal ObjectReaderBase CurrentReader { get { return this.currentReader; } } internal TDataReader DataReader { get { return this.dataReader; } } internal IReaderProvider Provider { get { return this.provider; } } internal object[] ParentArguments { get { return this.parentArgs; } } internal object[] UserArguments { get { return this.userArgs; } } internal ICompiledSubQuery[] SubQueries { get { return this.subQueries; } } internal void Finish(ObjectReaderBase finishedReader) { if (this.currentReader == finishedReader) { this.CheckNextResults(); } } private void CheckNextResults() { this.hasResults = !this.dataReader.IsClosed && this.dataReader.NextResult(); this.currentReader = null; if (!this.hasResults) { this.Dispose(); } } internal DbDataReader GetNextBufferedReader() { if (this.iNextBufferedReader < this.buffer.Count) { return this.buffer[this.iNextBufferedReader++]; } System.Diagnostics.Debug.Assert(false); return null; } public bool IsBuffered { get { return this.buffer != null; } } [SuppressMessage("Microsoft.Globalization", "CA1306:SetLocaleForDataTypes", Justification = "[....]: Used only as a buffer and never used for string comparison.")] public void Buffer() { if (this.buffer == null) { if (this.currentReader != null && !this.currentReader.IsBuffered) { this.currentReader.Buffer(); this.CheckNextResults(); } // buffer anything remaining in the session this.buffer = new List(); while (this.hasResults) { DataSet ds = new DataSet(); ds.EnforceConstraints = false; DataTable tb = new DataTable(); ds.Tables.Add(tb); string[] names = this.GetActiveNames(); tb.Load(new Rereader(this.dataReader, false, null), LoadOption.OverwriteChanges); this.buffer.Add(new Rereader(tb.CreateDataReader(), false, names)); this.CheckNextResults(); } } } internal string[] GetActiveNames() { string[] names = new string[this.DataReader.FieldCount]; for (int i = 0, n = this.DataReader.FieldCount; i < n; i++) { names[i] = this.DataReader.GetName(i); } return names; } public void CompleteUse() { this.Buffer(); } public void Dispose() { if (!this.isDisposed) { // Technically, calling GC.SuppressFinalize is not required because the class does not // have a finalizer, but it does no harm, protects against the case where a finalizer is added // in the future, and prevents an FxCop warning. GC.SuppressFinalize(this); this.isDisposed = true; if (!this.isDataReaderDisposed) { this.isDataReaderDisposed = true; this.dataReader.Dispose(); } this.provider.ConnectionManager.ReleaseConnection(this); } } internal ObjectReader CreateReader( Func, TObject> fnMaterialize, NamedColumn[] namedColumns, object[] globals, int nLocals, bool disposeDataReader ) { ObjectReader objectReader = new ObjectReader(this, namedColumns, globals, this.userArgs, nLocals, disposeDataReader, fnMaterialize); this.currentReader = objectReader; return objectReader; } internal ObjectReader GetNextResult( Func, TObject> fnMaterialize, NamedColumn[] namedColumns, object[] globals, int nLocals, bool disposeDataReader ) { // skip forward to next results if (this.buffer != null) { if (this.iNextBufferedReader >= this.buffer.Count) { return null; } } else { if (this.currentReader != null) { // buffer current reader this.currentReader.Buffer(); this.CheckNextResults(); } if (!this.hasResults) { return null; } } ObjectReader objectReader = new ObjectReader(this, namedColumns, globals, this.userArgs, nLocals, disposeDataReader, fnMaterialize); this.currentReader = objectReader; return objectReader; } } class Rereader : DbDataReader, IDisposable { bool first; DbDataReader reader; string[] names; internal Rereader(DbDataReader reader, bool hasCurrentRow, string[] names) { this.reader = reader; this.first = hasCurrentRow; this.names = names; } public override bool Read() { if (this.first) { this.first = false; return true; } return this.reader.Read(); } public override string GetName(int i) { if (this.names != null) { return this.names[i]; } return reader.GetName(i); } public override void Close() { } public override bool NextResult() { return false; } public override int Depth { get { return reader.Depth; } } public override bool IsClosed { get { return reader.IsClosed; } } public override int RecordsAffected { get { return reader.RecordsAffected; } } public override DataTable GetSchemaTable() { return reader.GetSchemaTable(); } public override int FieldCount { get { return reader.FieldCount; } } public override object this[int i] { get { return reader[i]; } } public override object this[string name] { get { return reader[name]; } } public override bool GetBoolean(int i) { return reader.GetBoolean(i); } public override byte GetByte(int i) { return reader.GetByte(i); } public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length) { return reader.GetBytes(i, fieldOffset, buffer, bufferOffset, length); } public override char GetChar(int i) { return reader.GetChar(i); } public override long GetChars(int i, long fieldOffset, char[] buffer, int bufferOffset, int length) { return reader.GetChars(i, fieldOffset, buffer, bufferOffset, length); } public override string GetDataTypeName(int i) { return reader.GetDataTypeName(i); } public override DateTime GetDateTime(int i) { return reader.GetDateTime(i); } public override decimal GetDecimal(int i) { return reader.GetDecimal(i); } public override double GetDouble(int i) { return reader.GetDouble(i); } public override Type GetFieldType(int i) { return reader.GetFieldType(i); } public override float GetFloat(int i) { return reader.GetFloat(i); } public override Guid GetGuid(int i) { return reader.GetGuid(i); } public override short GetInt16(int i) { return reader.GetInt16(i); } public override int GetInt32(int i) { return reader.GetInt32(i); } public override long GetInt64(int i) { return reader.GetInt64(i); } public override int GetOrdinal(string name) { return reader.GetOrdinal(name); } public override string GetString(int i) { return reader.GetString(i); } public override object GetValue(int i) { return reader.GetValue(i); } public override int GetValues(object[] values) { return reader.GetValues(values); } public override bool IsDBNull(int i) { return reader.IsDBNull(i); } public override IEnumerator GetEnumerator() { return this.reader.GetEnumerator(); } public override bool HasRows { get { return this.first || this.reader.HasRows; } } } internal class Group : IGrouping, IEnumerable, IEnumerable { K key; IEnumerable items; internal Group(K key, IEnumerable items) { this.key = key; this.items = items; } K IGrouping.Key { get { return this.key; } } IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)this.GetEnumerator(); } public IEnumerator GetEnumerator() { return this.items.GetEnumerator(); } } internal class OrderedResults : IOrderedEnumerable, IEnumerable { List values; internal OrderedResults(IEnumerable results) { this.values = results as List; if (this.values == null) this.values = new List(results); } IOrderedEnumerable IOrderedEnumerable.CreateOrderedEnumerable(Func keySelector, IComparer comparer, bool descending) { throw Error.NotSupported(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this.values).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.values.GetEnumerator(); } } } #endif }