|
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 {
/// <summary>
/// Internal interface type defining the operations dynamic materialization functions need to perform when
/// materializing objects, without reflecting/invoking privates.
/// <remarks>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.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Materializer", Justification = "Spelling is correct.")]
[SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors", Justification = "Unknown reason.")]
public abstract class ObjectMaterializer<TDataReader> where TDataReader : DbDataReader {
// These are public fields rather than properties for access speed
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Microsoft: 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 = "Microsoft: This is a public type that is not intended for public use.")]
public object[] Globals;
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Microsoft: This is a public type that is not intended for public use.")]
public object[] Locals;
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Microsoft: This is a public type that is not intended for public use.")]
public object[] Arguments;
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Microsoft: This is a public type that is not intended for public use.")]
public TDataReader DataReader;
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Microsoft: 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 = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
public abstract IEnumerable<T> GetLinkSource<T>(int globalLink, int localFactory, object[] keyValues);
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
public abstract IEnumerable<T> GetNestedLinkSource<T>(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 = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
public static IEnumerable<TOutput> Convert<TOutput>(IEnumerable source) {
foreach (object value in source) {
yield return DBConvert.ChangeType<TOutput>(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<TKey, TElement> CreateGroup<TKey, TElement>(TKey key, IEnumerable<TElement> items) {
return new ObjectReaderCompiler.Group<TKey, TElement>(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<TElement> CreateOrderedEnumerable<TElement>(IEnumerable<TElement> items) {
return new ObjectReaderCompiler.OrderedResults<TElement>(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<CacheInfo> 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<CacheInfo>();
}
internal IObjectReaderFactory GetFactory(Type elementType, Type dataReaderType, object mapping, DataLoadOptions options, SqlExpression projection) {
for (LinkedListNode<CacheInfo> 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<CacheInfo>(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<object> globals;
List<NamedColumn> namedColumns;
LocalBuilder locDataReader;
Type elementType;
int nLocals;
Dictionary<MetaAssociation, int> associationSubQueries;
SideEffectChecker sideEffectChecker = new SideEffectChecker();
internal Generator(ObjectReaderCompiler compiler, Type elementType) {
this.compiler = compiler;
this.elementType = elementType;
this.associationSubQueries = new Dictionary<MetaAssociation,int>();
}
internal void GenerateBody(ILGenerator generator, SqlExpression expression) {
this.gen = generator;
this.globals = new List<object>();
this.namedColumns = new List<NamedColumn>();
// 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 = "Microsoft: 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;
}
/// <summary>
/// Cannot use Call for virtual methods - it results in unverifiable code. Ensure we're using the correct op code.
/// </summary>
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<T>(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<E>()
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<E>(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<T>(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<T>/IOrderedQueryable<T>?
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<E>(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<T> ?
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<E>(); 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<T>?
else if (typeof(IEnumerable).IsAssignableFrom(expectedType) &&
actualIsSequence &&
expectedType.IsAssignableFrom(typeof(List<>).MakeGenericType(elemType))
) {
// new List<E>(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 = "Microsoft: 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 = "Microsoft: 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<TDataReader, TObject> : IObjectReaderFactory
where TDataReader : DbDataReader {
Func<ObjectMaterializer<TDataReader>, TObject> fnMaterialize;
NamedColumn[] namedColumns;
object[] globals;
int nLocals;
internal ObjectReaderFactory(
Func<ObjectMaterializer<TDataReader>, 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<TDataReader> session = new ObjectReaderSession<TDataReader>((TDataReader)dataReader, provider, parentArgs, userArgs, subQueries);
return session.CreateReader<TObject>(this.fnMaterialize, this.namedColumns, this.globals, this.nLocals, disposeDataReader);
}
public IObjectReader GetNextResult(IObjectReaderSession session, bool disposeDataReader) {
ObjectReaderSession<TDataReader> ors = (ObjectReaderSession<TDataReader>)session;
IObjectReader reader = ors.GetNextResult<TObject>(this.fnMaterialize, this.namedColumns, this.globals, this.nLocals, disposeDataReader);
if (reader == null && disposeDataReader) {
ors.Dispose();
}
return reader;
}
}
abstract class ObjectReaderBase<TDataReader> : ObjectMaterializer<TDataReader>
where TDataReader : DbDataReader {
protected ObjectReaderSession<TDataReader> session;
bool hasRead;
bool hasCurrentRow;
bool isFinished;
IDataServices services;
internal ObjectReaderBase(
ObjectReaderSession<TDataReader> 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 = "Microsoft: 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<T> GetLinkSource<T>(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<T>)factory.CreateDeferredSource(keyValues);
}
public override IEnumerable<T> GetNestedLinkSource<T>(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<T>)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<string, int> lookup = new Dictionary<string, int>(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<TDataReader, TObject>
: ObjectReaderBase<TDataReader>, IEnumerator<TObject>, IObjectReader, IDisposable
where TDataReader : DbDataReader {
Func<ObjectMaterializer<TDataReader>, TObject> fnMaterialize;
TObject current;
bool disposeSession;
internal ObjectReader(
ObjectReaderSession<TDataReader> session,
NamedColumn[] namedColumns,
object[] globals,
object[] arguments,
int nLocals,
bool disposeSession,
Func<ObjectMaterializer<TDataReader>, 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<TDataReader> : IObjectReaderSession, IDisposable, IConnectionUser
where TDataReader : DbDataReader {
TDataReader dataReader;
ObjectReaderBase<TDataReader> currentReader;
IReaderProvider provider;
List<DbDataReader> 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<TDataReader> 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<TDataReader> 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 = "Microsoft: 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<DbDataReader>();
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<TDataReader, TObject> CreateReader<TObject>(
Func<ObjectMaterializer<TDataReader>, TObject> fnMaterialize,
NamedColumn[] namedColumns,
object[] globals,
int nLocals,
bool disposeDataReader
) {
ObjectReader<TDataReader, TObject> objectReader =
new ObjectReader<TDataReader, TObject>(this, namedColumns, globals, this.userArgs, nLocals, disposeDataReader, fnMaterialize);
this.currentReader = objectReader;
return objectReader;
}
internal ObjectReader<TDataReader, TObject> GetNextResult<TObject>(
Func<ObjectMaterializer<TDataReader>, 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<TDataReader, TObject> objectReader =
new ObjectReader<TDataReader, TObject>(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<K, T> : IGrouping<K, T>, IEnumerable<T>, IEnumerable {
K key;
IEnumerable<T> items;
internal Group(K key, IEnumerable<T> items) {
this.key = key;
this.items = items;
}
K IGrouping<K, T>.Key {
get { return this.key; }
}
IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)this.GetEnumerator();
}
public IEnumerator<T> GetEnumerator() {
return this.items.GetEnumerator();
}
}
internal class OrderedResults<T> : IOrderedEnumerable<T>, IEnumerable<T> {
List<T> values;
internal OrderedResults(IEnumerable<T> results) {
this.values = results as List<T>;
if (this.values == null)
this.values = new List<T>(results);
}
IOrderedEnumerable<T> IOrderedEnumerable<T>.CreateOrderedEnumerable<K>(Func<T, K> keySelector, IComparer<K> comparer, bool descending) {
throw Error.NotSupported();
}
IEnumerator IEnumerable.GetEnumerator() {
return ((IEnumerable)this.values).GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
return this.values.GetEnumerator();
}
}
}
#endif
}
|