/// <summary>
 /// Constructs cached representation of a mapping object.
 /// </summary>
 /// <param name="storeMapping">Storage representation of mapping.</param>
 /// <param name="timeToLiveMilliseconds">Mapping expiration time.</param>
 internal CacheMapping(IStoreMapping storeMapping, long timeToLiveMilliseconds)
 {
     this.Mapping                = storeMapping;
     this.CreationTime           = TimerUtils.GetTimestamp();
     this.TimeToLiveMilliseconds = timeToLiveMilliseconds;
 }
 /// <summary>
 /// Whether TimeToLiveMilliseconds have elapsed
 /// since the CreationTime
 /// </summary>
 /// <returns>True if they have</returns>
 public bool HasTimeToLiveExpired()
 {
     return(TimerUtils.ElapsedMillisecondsSince(this.CreationTime) >= this.TimeToLiveMilliseconds);
 }
        /// <summary>
        /// Given a key value, obtains a SqlConnection to the shard in the mapping
        /// that contains the key value.
        /// </summary>
        /// <typeparam name="TMapping">Mapping type.</typeparam>
        /// <typeparam name="TKey">Key type.</typeparam>
        /// <param name="key">Input key value.</param>
        /// <param name="constructMapping">Delegate to construct a mapping object.</param>
        /// <param name="errorCategory">Error category.</param>
        /// <param name="connectionString">
        /// Connection string with credential information, the DataSource and Database are
        /// obtained from the results of the lookup operation for key.
        /// </param>
        /// <param name="options">Options for validation operations to perform on opened connection.</param>
        /// <returns>An opened SqlConnection.</returns>
        protected SqlConnection OpenConnectionForKey <TMapping, TKey>(
            TKey key,
            Func <ShardMapManager, ShardMap, IStoreMapping, TMapping> constructMapping,
            ShardManagementErrorCategory errorCategory,
            string connectionString,
            ConnectionOptions options = ConnectionOptions.Validate) where TMapping : class, IShardProvider
        {
            ShardKey sk = new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), key);

            // Try to find the mapping within the cache.
            ICacheStoreMapping csm = this.Manager.Cache.LookupMappingByKey(this.ShardMap.StoreShardMap, sk);

            IStoreMapping sm;

            if (csm != null)
            {
                sm = csm.Mapping;
            }
            else
            {
                sm = this.LookupMappingForOpenConnectionForKey(
                    sk,
                    CacheStoreMappingUpdatePolicy.OverwriteExisting,
                    errorCategory);
            }

            SqlConnection result;

            try
            {
                // Initially attempt to connect based on lookup results from either cache or GSM.
                result = this.ShardMap.OpenConnection(
                    constructMapping(this.Manager, this.ShardMap, sm),
                    connectionString,
                    options);

                // Reset TTL on successful connection.
                if (csm != null && csm.TimeToLiveMilliseconds > 0)
                {
                    csm.ResetTimeToLive();
                }

                //this.Manager.Cache.IncrementPerformanceCounter(this.ShardMap.StoreShardMap, PerformanceCounterName.DdrOperationsPerSec);
                return(result);
            }
            catch (ShardManagementException smme)
            {
                // If we hit a validation failure due to stale version of mapping, we will perform one more attempt.
                if (((options & ConnectionOptions.Validate) == ConnectionOptions.Validate) &&
                    smme.ErrorCategory == ShardManagementErrorCategory.Validation &&
                    smme.ErrorCode == ShardManagementErrorCode.MappingDoesNotExist)
                {
                    // Assumption here is that this time the attempt should succeed since the cache entry
                    // has already been either evicted, or updated based on latest data from the server.
                    sm = this.LookupMappingForOpenConnectionForKey(
                        sk,
                        CacheStoreMappingUpdatePolicy.OverwriteExisting,
                        errorCategory);

                    result = this.ShardMap.OpenConnection(
                        constructMapping(this.Manager, this.ShardMap, sm),
                        connectionString,
                        options);
                    //this.Manager.Cache.IncrementPerformanceCounter(this.ShardMap.StoreShardMap, PerformanceCounterName.DdrOperationsPerSec);
                    return(result);
                }
                else
                {
                    // The error was not due to validation but something else e.g.
                    // 1) Shard map does not exist
                    // 2) Mapping could not be found.
                    throw;
                }
            }
            catch (SqlException)
            {
                // We failed to connect. If we were trying to connect from an entry in cache and mapping expired in cache.
                if (csm != null && TimerUtils.ElapsedMillisecondsSince(csm.CreationTime) >= csm.TimeToLiveMilliseconds)
                {
                    using (IdLock _idLock = new IdLock(csm.Mapping.StoreShard.Id))
                    {
                        // Similar to DCL pattern, we need to refresh the mapping again to see if we still need to go to the store
                        // to lookup the mapping after acquiring the shard lock. It might be the case that a fresh version has already
                        // been obtained by some other thread.
                        csm = this.Manager.Cache.LookupMappingByKey(this.ShardMap.StoreShardMap, sk);

                        // Only go to store if the mapping is stale even after refresh.
                        if (csm == null || TimerUtils.ElapsedMillisecondsSince(csm.CreationTime) >= csm.TimeToLiveMilliseconds)
                        {
                            // Refresh the mapping in cache. And try to open the connection after refresh.
                            sm = this.LookupMappingForOpenConnectionForKey(
                                sk,
                                CacheStoreMappingUpdatePolicy.UpdateTimeToLive,
                                errorCategory);
                        }
                        else
                        {
                            sm = csm.Mapping;
                        }
                    }

                    result = this.ShardMap.OpenConnection(
                        constructMapping(this.Manager, this.ShardMap, sm),
                        connectionString,
                        options);

                    // Reset TTL on successful connection.
                    if (csm != null && csm.TimeToLiveMilliseconds > 0)
                    {
                        csm.ResetTimeToLive();
                    }

                    //this.Manager.Cache.IncrementPerformanceCounter(this.ShardMap.StoreShardMap, PerformanceCounterName.DdrOperationsPerSec);
                    return(result);
                }
                else
                {
                    // Either:
                    // 1) The mapping is still within the TTL. No refresh.
                    // 2) Mapping was not in cache, we originally did a lookup for mapping in GSM and even then could not connect.
                    throw;
                }
            }
        }