context: //------------------------------------------------------------------------------------------------- 
  // <copyright file="Prefetcher.cs" company="Allors bvba">
  // Copyright 2002-2017 Allors bvba.
  // 
  // Dual Licensed under
  //   a) the Lesser General Public Licence v3 (LGPL)
  //   b) the Allors License
  // 
  // The LGPL License is included in the file lgpl.txt.
  // The Allors License is an addendum to your contract.
  // 
  // Allors Platform is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  // 
  // For more information visit http://www.allors.com/legal
  // </copyright>
  // <summary>Defines the Session type.</summary>
  //-------------------------------------------------------------------------------------------------
  namespace Allors.Adapters.Object.SqlClient
  {
      using System;
      using System.Collections.Generic;
      using System.Data;
      using System.Data.Common;
      using System.Linq;
  
      using Allors.Meta;
  
      internal class Prefetcher
      {
          private static readonly long[] EmptyObjectIds = { };
  
          private Dictionary<IClass, Command> prefetchUnitRolesByClass;
          private Dictionary<IRoleType, Command> prefetchCompositeRoleByRoleType;
          private Dictionary<IRoleType, Command> prefetchCompositesRoleByRoleType;
          private Dictionary<IAssociationType, Command> prefetchCompositeAssociationByAssociationType;
  
          public Prefetcher(Session session)
          {
              this.Session = session;
          }
  
          public Session Session { get; }
  
          public Database Database => this.Session.Database;
  
          private Dictionary<IClass, Command> PrefetchUnitRolesByClass => this.prefetchUnitRolesByClass ?? (this.prefetchUnitRolesByClass = new Dictionary<IClass, Command>());
  
          private Dictionary<IRoleType, Command> PrefetchCompositeRoleByRoleType => this.prefetchCompositeRoleByRoleType ?? (this.prefetchCompositeRoleByRoleType = new Dictionary<IRoleType, Command>());
  
          private Dictionary<IRoleType, Command> PrefetchCompositesRoleByRoleType => this.prefetchCompositesRoleByRoleType ?? (this.prefetchCompositesRoleByRoleType = new Dictionary<IRoleType, Command>());
  
          private Dictionary<IAssociationType, Command> PrefetchCompositeAssociationByAssociationType => this.prefetchCompositeAssociationByAssociationType ?? (this.prefetchCompositeAssociationByAssociationType = new Dictionary<IAssociationType, Command>());
  
          internal HashSet<Reference> GetReferencesForPrefetching(IEnumerable<long> objectIds)
          {
              var references = new HashSet<Reference>();
  
              HashSet<long> referencesToInstantiate = null;
              foreach (var objectId in objectIds)
              {
                  this.Session.State.ReferenceByObjectId.TryGetValue(objectId, out var reference);
                  if (reference != null && reference.ExistsKnown && !reference.IsUnknownVersion)
                  {
                      if (reference.Exists && !reference.IsNew)
                      {
                          references.Add(reference);
                      }
                  }
                  else
                  {
                      if (referencesToInstantiate == null)
                      {
                          referencesToInstantiate = new HashSet<long>();
                      }
  
                      referencesToInstantiate.Add(objectId);
                  }
              }
  
              if (referencesToInstantiate != null)
              {
                  // TODO: Remove dependency from Prefetcher to Commands
                  var newReferences = this.Session.Commands.InstantiateReferences(referencesToInstantiate);
                  references.UnionWith(newReferences);
              }
  
              return references;
          }
  
          internal void ResetCommands()
          {
              this.prefetchUnitRolesByClass = null;
              this.prefetchCompositeRoleByRoleType = null;
              this.prefetchCompositesRoleByRoleType = null;
              this.prefetchCompositeAssociationByAssociationType = null;
          }
  
          internal void PrefetchUnitRoles(IClass @class, HashSet<Reference> associations, IRoleType anyRoleType)
          {
              var references = this.FilterForPrefetchRoles(associations, anyRoleType);
              if (references.Count == 0)
              {
                  return;
              }
  
              if (!this.PrefetchUnitRolesByClass.TryGetValue(@class, out var command))
              {
                  if (!this.Database.Mapping.ProcedureNameForPrefetchUnitRolesByClass.ContainsKey(@class))
                  {
                      Console.WriteLine(0);
                  }
  
                  var sql = this.Database.Mapping.ProcedureNameForPrefetchUnitRolesByClass[@class];
                  command = this.Session.Connection.CreateCommand();
                  command.CommandText = sql;
                  command.CommandType = CommandType.StoredProcedure;
                  command.AddObjectTableParameter(references);
                  this.prefetchUnitRolesByClass[@class] = command;
              }
              else
              {
                  command.Parameters[Mapping.ParamNameForTableType].Value = this.Database.CreateObjectTable(references);
              }
  
              using (DbDataReader reader = command.ExecuteReader())
              {
                  var sortedUnitRoles = this.Database.GetSortedUnitRolesByObjectType(@class);
                  var cache = this.Database.Cache;
  
                  while (reader.Read())
                  {
                      var associatoinId = reader.GetInt64(0);
                      var associationReference = this.Session.State.ReferenceByObjectId[associatoinId];
  
                      Roles modifiedRoles = null;
                      this.Session.State.ModifiedRolesByReference?.TryGetValue(associationReference, out modifiedRoles);
  
                      var cachedObject = cache.GetOrCreateCachedObject(@class, associatoinId, associationReference.Version);
  
                      for (var i = 0; i < sortedUnitRoles.Length; i++)
                      {
                          var roleType = sortedUnitRoles[i];
  
                          var index = i + 1;
                          object unit = null;
                          if (!reader.IsDBNull(index))
                          {
                              var unitTypeTag = ((IUnit)roleType.ObjectType).UnitTag;
                              switch (unitTypeTag)
                              {
                                  case UnitTags.String:
                                      unit = reader.GetString(index);
                                      break;
                                  case UnitTags.Integer:
                                      unit = reader.GetInt32(index);
                                      break;
                                  case UnitTags.Float:
                                      unit = reader.GetDouble(index);
                                      break;
                                  case UnitTags.Decimal:
                                      unit = reader.GetDecimal(index);
                                      break;
                                  case UnitTags.DateTime:
                                      var dateTime = reader.GetDateTime(index);
                                      if (dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue)
                                      {
                                          unit = dateTime;
                                      }
                                      else
                                      {
                                          unit = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, DateTimeKind.Utc);
                                      }
  
                                      break;
                                  case UnitTags.Boolean:
                                      unit = reader.GetBoolean(index);
                                      break;
                                  case UnitTags.Unique:
                                      unit = reader.GetGuid(index);
                                      break;
                                  case UnitTags.Binary:
                                      var byteArray = (byte[])reader.GetValue(index);
                                      unit = byteArray;
                                      break;
                                  default:
                                      throw new ArgumentException("Unknown Unit ObjectType: " + roleType.ObjectType.Name);
                              }
                          }
  
                          if (modifiedRoles == null || !modifiedRoles.ModifiedRoleByRoleType.ContainsKey(roleType))
                          {
                              cachedObject.SetValue(roleType, unit);
                          }
                      }
                  }
              }
          }
  
          internal void PrefetchCompositeRoleObjectTable(HashSet<Reference> associations, IRoleType roleType, HashSet<long> nestedObjectIds, HashSet<long> leafs)
          {
              var references = nestedObjectIds == null ? this.FilterForPrefetchRoles(associations, roleType) : this.FilterForPrefetchCompositeRoles(associations, roleType, nestedObjectIds);
              if (references.Count == 0)
              {
                  return;
              }
  
              if (!this.PrefetchCompositeRoleByRoleType.TryGetValue(roleType, out var command))
              {
                  command = this.Session.Connection.CreateCommand();
                  command.CommandText = this.Database.Mapping.ProcedureNameForPrefetchRoleByRelationType[roleType.RelationType];
                  command.CommandType = CommandType.StoredProcedure;
                  command.AddObjectTableParameter(references);
                  this.prefetchCompositeRoleByRoleType[roleType] = command;
              }
              else
              {
                  command.Parameters[Mapping.ParamNameForTableType].Value = this.Database.CreateObjectTable(references);
              }
  
              using (DbDataReader reader = command.ExecuteReader())
              {
                  var cache = this.Database.Cache;
  
                  while (reader.Read())
                  {
                      var associationId = reader.GetInt64(0);
                      var associationReference = this.Session.State.ReferenceByObjectId[associationId];
  
                      var cachedObject = cache.GetOrCreateCachedObject(associationReference.Class, associationId, associationReference.Version);
  
                      var roleIdValue = reader[1];
  
                      if (roleIdValue == null || roleIdValue == DBNull.Value)
                      {
                          cachedObject.SetValue(roleType, null);
                      }
                      else
                      {
                          var roleId = (long)roleIdValue;
                          cachedObject.SetValue(roleType, roleId);
  
                          nestedObjectIds?.Add(roleId);
                          if (nestedObjectIds == null)
                          {
                              leafs.Add(roleId);
                          }
                      }
                  }
              }
          }
  
          internal void PrefetchCompositeRoleRelationTable(HashSet<Reference> associations, IRoleType roleType, HashSet<long> nestedObjectIds, HashSet<long> leafs)
          {
              var references = nestedObjectIds == null ? this.FilterForPrefetchRoles(associations, roleType) : this.FilterForPrefetchCompositeRoles(associations, roleType, nestedObjectIds);
              if (references.Count == 0)
              {
                  return;
              }
  
              if (!this.PrefetchCompositeRoleByRoleType.TryGetValue(roleType, out var command))
              {
                  var sql = this.Database.Mapping.ProcedureNameForPrefetchRoleByRelationType[roleType.RelationType];
                  command = this.Session.Connection.CreateCommand();
                  command.CommandText = sql;
                  command.CommandType = CommandType.StoredProcedure;
                  command.AddObjectTableParameter(references);
                  this.prefetchCompositeRoleByRoleType[roleType] = command;
              }
              else
              {
                  command.Parameters[Mapping.ParamNameForTableType].Value = this.Database.CreateObjectTable(references);
              }
  
              var roleByAssociation = new Dictionary<Reference, long>();
              using (DbDataReader reader = command.ExecuteReader())
              {
                  while (reader.Read())
                  {
                      var associationId = reader.GetInt64(0);
                      var associationReference = this.Session.State.ReferenceByObjectId[associationId];
                      var roleId = reader.GetInt64(1);
                      roleByAssociation.Add(associationReference, roleId);
                  }
              }
  
              var cache = this.Database.Cache;
              foreach (var reference in references)
              {
                  var cachedObject = cache.GetOrCreateCachedObject(reference.Class, reference.ObjectId, reference.Version);
  
                  if (roleByAssociation.TryGetValue(reference, out var roleId))
                  {
                      cachedObject.SetValue(roleType, roleId);
                      nestedObjectIds?.Add(roleId);
                      if (nestedObjectIds == null)
                      {
                          leafs.Add(roleId);
                      }
                  }
                  else
                  {
                      cachedObject.SetValue(roleType, null);
                  }
              }
          }
  
          internal void PrefetchCompositesRoleObjectTable(HashSet<Reference> associations, IRoleType roleType, HashSet<long> nestedObjectIds, HashSet<long> leafs)
          {
              var references = nestedObjectIds == null ? this.FilterForPrefetchRoles(associations, roleType) : this.FilterForPrefetchCompositesRoles(associations, roleType, nestedObjectIds);
              if (references.Count == 0)
              {
                  return;
              }
  
              if (!this.PrefetchCompositesRoleByRoleType.TryGetValue(roleType, out var command))
              {
                  var sql = this.Database.Mapping.ProcedureNameForPrefetchRoleByRelationType[roleType.RelationType];
  
gt:                 command = this.Session.Connection.CreateCommand();