/// <summary>
        /// Write a DeletedRecord in deleteIn dataset for the specified key
        /// instead of actually deleting the record. This ensures that
        /// a record in another dataset does not become visible during
        /// lookup in a sequence of datasets.
        ///
        /// To avoid an additional roundtrip to the data store, the delete
        /// marker is written even when the record does not exist.
        /// </summary>
        public override void Delete <TKey, TRecord>(TypedKey <TKey, TRecord> key, TemporalId deleteIn)
        {
            // Error message if data source is readonly or has CutoffTime set
            CheckNotReadOnly(deleteIn);

            // For pinned records, delete in root dataset irrespective of the value of saveTo
            if (IsPinned <TRecord>())
            {
                deleteIn = TemporalId.Empty;
            }

            // Create DeletedRecord with the specified key
            var record = new DeletedRecord {
                Key = key.Value
            };

            // Get collection
            var collection = GetOrCreateCollection <TRecord>();

            // This method guarantees that TemporalIds will be in strictly increasing
            // order for this instance of the data source class always, and across
            // all processes and machine if they are not created within the same
            // second.
            var recordId = CreateOrderedTemporalId();

            record.Id = recordId;

            // Assign dataset and then initialize, as the results of
            // initialization may depend on record.DataSet
            record.DataSet = deleteIn;

            // By design, insert will fail if TemporalId is not unique within the collection
            collection.BaseCollection.InsertOne(record);
        }
Esempio n. 2
0
        /// <summary>
        /// The returned TemporalIds have the following order guarantees:
        ///
        /// * For this data source instance, to arbitrary resolution; and
        /// * Across all processes and machines, to one second resolution
        ///
        /// One second resolution means that two TemporalIds created within
        /// the same second by different instances of the data source
        /// class may not be ordered chronologically unless they are at
        /// least one second apart.
        /// </summary>
        public override TemporalId CreateOrderedTemporalId()
        {
            // Generate TemporalId and check that it is later
            // than the previous generated TemporalId
            TemporalId result       = TemporalId.Next();
            int        retryCounter = 0;

            while (result <= prevTemporalId_)
            {
                // Getting inside the while loop will be very rare as this would
                // require the increment to roll from max int to min int within
                // the same second, therefore it is a good idea to log the event
                if (retryCounter++ == 0)
                {
                    Context.Log.Warning("MongoDB generated TemporalId not in increasing order, retrying.");
                }

                // If new TemporalId is not strictly greater than the previous one,
                // keep generating new TemporalIds until it changes
                result = TemporalId.Next();
            }

            // Report the number of retries
            if (retryCounter != 0)
            {
                Context.Log.Warning($"Generated TemporalId in increasing order after {retryCounter} retries.");
            }

            // Update previous TemporalId and return
            prevTemporalId_ = result;
            return(result);
        }
 /// <summary>
 /// Save one record to the specified dataset. After the method exits,
 /// record.DataSet will be set to the value of the dataSet parameter.
 ///
 /// All Save methods ignore the value of record.DataSet before the
 /// Save method is called. When dataset is not specified explicitly,
 /// the value of dataset from the context, not from the record, is used.
 /// The reason for this behavior is that the record may be stored from
 /// a different dataset than the one where it is used.
 ///
 /// This method guarantees that TemporalIds of the saved records will be in
 /// strictly increasing order.
 /// </summary>
 public static void SaveOne <TRecord>(this DataSource obj, TRecord record, TemporalId saveTo)
     where TRecord : Record
 {
     // Pass a list of records with one element to SaveMany
     obj.SaveMany(new List <TRecord> {
         record
     }, saveTo);
 }
        /// <summary>
        /// Load record by its TemporalId.
        ///
        /// Error message if there is no record for the specified TemporalId,
        /// or if the record exists but is not derived from TRecord.
        /// </summary>
        public static TRecord Load <TRecord>(this DataSource obj, TemporalId id)
            where TRecord : Record
        {
            var result = obj.LoadOrNull <TRecord>(id);

            if (result == null)
            {
                throw new Exception(
                          $"Record with TemporalId={id} is not found in data store {obj.DataSourceName}.");
            }
            return(result);
        }
        /// <summary>
        /// Create from collection for the base type Record, the lookup
        /// list of datasets, and IOrderedQueryable(TRecord).
        ///
        /// This constructor is private and is intended for use by the
        /// implementation of this class only.
        /// </summary>
        private TemporalMongoQuery(TemporalMongoCollection <TRecord> collection, TemporalId loadFrom, IOrderedQueryable <TRecord> orderedQueryable)
        {
            if (orderedQueryable == null)
            {
                throw new Exception(
                          "Attempting to create a query from a null orderedQueryable.");
            }

            collection_       = collection;
            loadFrom_         = loadFrom;
            orderedQueryable_ = orderedQueryable;
        }
        /// <summary>
        /// Get query for the specified type.
        ///
        /// After applying query parameters, the lookup occurs first in
        /// descending order of dataset TemporalIds, and then in the descending
        /// order of record TemporalIds within the first dataset that
        /// has at least one record. Both dataset and record TemporalIds
        /// are ordered chronologically to one second resolution,
        /// and are unique within the database server or cluster.
        ///
        /// The root dataset has empty TemporalId value that is less
        /// than any other TemporalId value. Accordingly, the root
        /// dataset is the last one in the lookup order of datasets.
        ///
        /// Generic parameter TRecord is not necessarily the root data type;
        /// it may also be a type derived from the root data type.
        /// </summary>
        public override IQuery <TRecord> GetQuery <TRecord>(TemporalId loadFrom)
        {
            // For pinned records, load from root dataset irrespective of the value of loadFrom
            if (IsPinned <TRecord>())
            {
                loadFrom = TemporalId.Empty;
            }

            // Get or create collection, then create query from collection
            var collection = GetOrCreateCollection <TRecord>();

            return(new TemporalMongoQuery <TRecord>(collection, loadFrom));
        }
        /// <summary>
        /// Error message if either ReadOnly flag or CutoffTime is set
        /// for the data source.
        /// </summary>
        private void CheckNotReadOnly(TemporalId dataSetId)
        {
            if (ReadOnly != null && ReadOnly.Value)
            {
                throw new Exception(
                          $"Attempting write operation for data source {DataSourceName} where ReadOnly flag is set.");
            }

            if (CutoffTime != null)
            {
                throw new Exception(
                          $"Attempting write operation for data source {DataSourceName} where " +
                          $"CutoffTime is set. Historical view of the data cannot be written to.");
            }
        }
        //--- CONSTRUCTORS

        /// <summary>
        /// Create query from collection and dataset.
        /// </summary>
        public TemporalMongoQuery(TemporalMongoCollection <TRecord> collection, TemporalId loadFrom)
        {
            collection_ = collection;
            loadFrom_   = loadFrom;

            // Create queryable from typed collection rather than base
            // collection so LINQ queries can be applied to properties
            // of the generic parameter type TRecord
            var queryable = collection_.TypedCollection.AsQueryable();

            // Without explicitly applying OfType, the match for _t
            // may or may not be added. This step ensures the match
            // for _t exists and is the first stage in the aggregation
            // pipeline
            queryable_ = queryable.OfType <TRecord>();
        }
        /// <summary>
        /// Load record by its TemporalId.
        ///
        /// Return null if there is no record for the specified TemporalId;
        /// however an exception will be thrown if the record exists but
        /// is not derived from TRecord.
        /// </summary>
        public override TRecord LoadOrNull <TRecord>(TemporalId id)
        {
            if (CutoffTime != null)
            {
                // Return null if argument TemporalId is greater than
                // or equal to CutoffTime.
                if (id >= CutoffTime.Value)
                {
                    return(null);
                }
            }

            // Find last record in last dataset without constraining record type.
            // The result may not be derived from TRecord.
            var baseResult = GetOrCreateCollection <TRecord>()
                             .BaseCollection
                             .AsQueryable()
                             .FirstOrDefault(p => p.Id == id);

            // Check not only for null but also for the DeletedRecord
            if (baseResult != null && !baseResult.Is <DeletedRecord>())
            {
                // Record is found but we do not yet know if it has the right type.
                // Attempt to cast Record to TRecord and check if the result is null.
                TRecord result = baseResult.As <TRecord>();
                if (result == null)
                {
                    // If cast result is null, the record was found but it is an instance
                    // of class that is not derived from TRecord, in this case the API
                    // requires error message, not returning null
                    throw new Exception(
                              $"Stored type {result.GetType().Name} for TemporalId={id} and " +
                              $"Key={result.Key} is not an instance of the requested type {typeof(TRecord).Name}.");
                }

                // Initialize before returning
                result.Init(Context);
                return(result);
            }
            else
            {
                // Record not found or is a DeletedRecord, return null
                return(null);
            }
        }
        /// <summary>
        /// Returns enumeration of import datasets for specified dataset data,
        /// including imports of imports to unlimited depth with cyclic
        /// references and duplicates removed.
        ///
        /// The list will not include datasets that are after the value of
        /// CutoffTime if specified, or their imports (including
        /// even those imports that are earlier than the constraint).
        /// </summary>
        public IEnumerable <TemporalId> GetDataSetLookupList(TemporalId dataSetId)
        {
            // Root dataset has no imports (there is not even a record
            // where these imports can be specified).
            //
            // Return list containing only the root dataset (TemporalId.Empty) and exit
            if (dataSetId == TemporalId.Empty)
            {
                return(new TemporalId[] { TemporalId.Empty });
            }

            if (importDict_.TryGetValue(dataSetId, out HashSet <TemporalId> result))
            {
                // Check if the lookup list is already cached, return if yes
                return(result);
            }
            else
            {
                // Otherwise load from storage (returns null if not found)
                DataSet dataSetRecord = LoadOrNull <DataSet>(dataSetId);

                if (dataSetRecord == null)
                {
                    throw new Exception($"Dataset with TemporalId={dataSetId} is not found.");
                }
                if (dataSetRecord.DataSet != TemporalId.Empty)
                {
                    throw new Exception($"Dataset with TemporalId={dataSetId} is not stored in root dataset.");
                }

                // Build the lookup list
                result = BuildDataSetLookupList(dataSetRecord);

                // Add to dictionary and return
                importDict_.Add(dataSetId, result);
                return(result);
            }
        }
Esempio n. 11
0
 /// <summary>
 /// Write a DeletedRecord in deleteIn dataset for the specified key
 /// instead of actually deleting the record. This ensures that
 /// a record in another dataset does not become visible during
 /// lookup in a sequence of datasets.
 ///
 /// To avoid an additional roundtrip to the data store, the delete
 /// marker is written even when the record does not exist.
 /// </summary>
 public static void Delete <TKey, TRecord>(this Context obj, TypedKey <TKey, TRecord> key, TemporalId deleteIn)
     where TKey : TypedKey <TKey, TRecord>, new()
     where TRecord : TypedRecord <TKey, TRecord>
 {
     obj.DataSource.Delete(key, deleteIn);
 }
Esempio n. 12
0
 /// <summary>
 /// Save multiple records to the specified dataset. After the method exits,
 /// for each record the property record.DataSet will be set to the value of
 /// the saveTo parameter.
 ///
 /// All Save methods ignore the value of record.DataSet before the
 /// Save method is called. When dataset is not specified explicitly,
 /// the value of dataset from the context, not from the record, is used.
 /// The reason for this behavior is that the record may be stored from
 /// a different dataset than the one where it is used.
 ///
 /// This method guarantees that TemporalIds of the saved records will be in
 /// strictly increasing order.
 /// </summary>
 public static void SaveMany <TRecord>(this Context obj, IEnumerable <TRecord> records, TemporalId saveTo)
     where TRecord : Record
 {
     obj.DataSource.SaveMany(records, saveTo);
 }
Esempio n. 13
0
 /// <summary>
 /// Get query for the specified type.
 ///
 /// After applying query parameters, the lookup occurs first in
 /// descending order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// Generic parameter TRecord is not necessarily the root data type;
 /// it may also be a type derived from the root data type.
 /// </summary>
 public override IQuery <TRecord> GetQuery <TRecord>(TemporalId loadFrom)
 {
     throw MethodCalledForNullDataSourceError();
 }
Esempio n. 14
0
        /// <summary>
        /// Load record from context.DataSource, overriding the dataset
        /// specified in the context with the value specified as the
        /// second parameter. The lookup occurs in the specified dataset
        /// and its imports, expanded to arbitrary depth with repetitions
        /// and cyclic references removed.
        ///
        /// IMPORTANT - this overload of the method loads from loadFrom
        /// dataset, not from context.DataSet.
        ///
        /// If Record property is set, its value is returned without
        /// performing lookup in the data store; otherwise the record
        /// is loaded from storage and cached in Record and the
        /// cached value is returned from subsequent calls.
        ///
        /// Once the record has been cached, the same version will be
        /// returned in subsequent calls with the same key instance.
        /// Create a new key or call earRecord() method to force
        /// reloading new version of the record from storage.
        ///
        /// Error message if the record is not found or is a DeletedRecord.
        /// </summary>
        public static TRecord Load <TKey, TRecord>(this DataSource obj, TypedKey <TKey, TRecord> key, TemporalId loadFrom)
            where TKey : TypedKey <TKey, TRecord>, new()
            where TRecord : TypedRecord <TKey, TRecord>
        {
            // This method will return null if the record is
            // not found or the found record is a DeletedRecord
            var result = obj.LoadOrNull(key, loadFrom);

            // Error message if null, otherwise return
            if (result == null)
            {
                throw new Exception(
                          $"Record with key {key} is not found in dataset with TemporalId={loadFrom}.");
            }

            return(result);
        }
Esempio n. 15
0
 /// <summary>
 /// Write a DeletedRecord in deleteIn dataset for the specified key
 /// instead of actually deleting the record. This ensures that
 /// a record in another dataset does not become visible during
 /// lookup in a sequence of datasets.
 ///
 /// To avoid an additional roundtrip to the data store, the delete
 /// marker is written even when the record does not exist.
 /// </summary>
 public abstract void Delete <TKey, TRecord>(TypedKey <TKey, TRecord> key, TemporalId deleteIn)
     where TKey : TypedKey <TKey, TRecord>, new()
     where TRecord : TypedRecord <TKey, TRecord>;
Esempio n. 16
0
 /// <summary>
 /// Get query for the specified type.
 ///
 /// After applying query parameters, the lookup occurs first in
 /// descending order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// Generic parameter TRecord is not necessarily the root data type;
 /// it may also be a type derived from the root data type.
 /// </summary>
 public abstract IQuery <TRecord> GetQuery <TRecord>(TemporalId loadFrom)
     where TRecord : Record;
Esempio n. 17
0
        //--- PRIVATE

        /// <summary>
        /// Populate key elements from an array of tokens starting
        /// at the specified token index. Elements that are themselves
        /// keys may use more than one token.
        ///
        /// This method returns the index of the first unused token.
        /// The returned value is the same as the length of the tokens
        /// array if all tokens are used.
        ///
        /// If key AKey has two elements, B and C, where
        ///
        /// * B has type BKey which has two string elements, and
        /// * C has type string,
        ///
        /// the semicolon delimited key has the following format:
        ///
        /// BToken1;BToken2;CToken
        ///
        /// To avoid serialization format uncertainty, key elements
        /// can have any atomic type except Double.
        /// </summary>
        private int PopulateFrom(string[] tokens, int tokenIndex)
        {
            // Get key elements using reflection
            var elementInfoArray = DataTypeInfo.GetOrCreate(this).DataElements;

            // If singleton is detected process it separately, then exit
            if (elementInfoArray.Length == 0)
            {
                // Check that string key is empty
                if (tokens.Length != 1 || tokens[0] != String.Empty)
                {
                    throw new Exception($"Type {GetType()} has key {string.Join(";", tokens)} while " +
                                        $"for a singleton the key must be an empty string (String.Empty). " +
                                        $"Singleton key is a key that has no key elements.");
                }

                // Return the length of empty key which consists of one (empty) token
                return(1);
            }

            // Check that there are enough remaining tokens in the key for each key element
            if (tokens.Length - tokenIndex < elementInfoArray.Length)
            {
                throw new Exception(
                          $"Key of type {GetType().Name} requires at least {elementInfoArray.Length} elements " +
                          $"{String.Join(";", elementInfoArray.Select(p => p.Name).ToArray())} while there are " +
                          $"only {tokens.Length - tokenIndex} remaining key tokens: {string.Join(";", tokens)}.");
            }

            // Iterate over element info elements, advancing tokenIndex by the required
            // number of tokens for each element. In case of embedded keys, the value of
            // tokenIndex is advanced by the recursive call to InitFromTokens method
            // of the embedded key.
            foreach (var elementInfo in elementInfoArray)
            {
                // Get element type
                Type elementType = elementInfo.PropertyType;

                // Convert string token to value depending on elementType
                if (elementType == typeof(string))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token = tokens[tokenIndex++];
                    elementInfo.SetValue(this, token);
                }
                else if (elementType == typeof(double) || elementType == typeof(double?))
                {
                    throw new Exception(
                              $"Key element {elementInfo.Name} has type Double. Elements of this type " +
                              $"cannot be part of key due to serialization format uncertainty.");
                }
                else if (elementType == typeof(bool) || elementType == typeof(bool?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    bool   tokenValue = bool.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(int) || elementType == typeof(int?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    int    tokenValue = int.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(long) || elementType == typeof(long?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    long   tokenValue = long.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDate is represented as readable int in
                    // non-delimited yyyymmdd format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalDate.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDate and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited yyyymmdd format.");
                    }

                    LocalDate tokenValue = LocalDateUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalTime is represented as readable int in
                    // non-delimited hhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalTime and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmmssfff format.");
                    }

                    LocalTime tokenValue = LocalTimeUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalMinute is represented as readable int in
                    // non-delimited hhmm format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalMinute and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmm format.");
                    }

                    LocalMinute tokenValue = LocalMinuteUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDateTime is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to LocalDateTime.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDateTime and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    LocalDateTime tokenValue = LocalDateTimeUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, Instant is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to Instant.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type Instant and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    Instant tokenValue = InstantUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(TemporalId) || elementType == typeof(TemporalId?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string     token      = tokens[tokenIndex++];
                    TemporalId tokenValue = TemporalId.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType.BaseType == typeof(Enum)) // TODO Support nullable Enum in key
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    object tokenValue = Enum.Parse(elementType, token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (typeof(Key).IsAssignableFrom(elementType))
                {
                    Key keyElement = (Key)Activator.CreateInstance(elementType);
                    tokenIndex = keyElement.PopulateFrom(tokens, tokenIndex);
                    elementInfo.SetValue(this, keyElement);
                }
                else
                {
                    // Field type is unsupported for a key, error message
                    throw new Exception(
                              $"Element {elementInfo.Name} of key type {GetType().Name} has type {elementType} that " +
                              $"is not one of the supported key element types. Available key element types are " +
                              $"string, bool, int, long, LocalDate, LocalTime, LocalMinute, LocalDateTime, Instant, Enum, or Key.");
                }
            }

            return(tokenIndex);
        }
Esempio n. 18
0
 /// <summary>
 /// Load record by string key from the specified dataset or
 /// its list of imports. The lookup occurs first in descending
 /// order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// The first record in this lookup order is returned, or null
 /// if no records are found or if DeletedRecord is the first
 /// record.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public override TRecord LoadOrNull <TKey, TRecord>(TypedKey <TKey, TRecord> key, TemporalId loadFrom)
 {
     throw MethodCalledForNullDataSourceError();
 }
Esempio n. 19
0
 /// <summary>
 /// Load record by its TemporalId.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public override TRecord LoadOrNull <TRecord>(TemporalId id)
 {
     throw MethodCalledForNullDataSourceError();
 }
Esempio n. 20
0
 /// <summary>
 /// Write a DeletedRecord in deleteIn dataset for the specified key
 /// instead of actually deleting the record. This ensures that
 /// a record in another dataset does not become visible during
 /// lookup in a sequence of datasets.
 ///
 /// To avoid an additional roundtrip to the data store, the delete
 /// marker is written even when the record does not exist.
 /// </summary>
 public override void Delete <TKey, TRecord>(TypedKey <TKey, TRecord> key, TemporalId deleteIn)
 {
     throw MethodCalledForNullDataSourceError();
 }
Esempio n. 21
0
 /// <summary>
 /// Save multiple records to the specified dataset. After the method exits,
 /// for each record the property record.DataSet will be set to the value of
 /// the saveTo parameter.
 ///
 /// All Save methods ignore the value of record.DataSet before the
 /// Save method is called. When dataset is not specified explicitly,
 /// the value of dataset from the context, not from the record, is used.
 /// The reason for this behavior is that the record may be stored from
 /// a different dataset than the one where it is used.
 ///
 /// This method guarantees that TemporalIds of the saved records will be in
 /// strictly increasing order.
 /// </summary>
 public override void SaveMany <TRecord>(IEnumerable <TRecord> records, TemporalId saveTo)
 {
     throw MethodCalledForNullDataSourceError();
 }
Esempio n. 22
0
 /// <summary>
 /// Load record by its TemporalId.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public abstract TRecord LoadOrNull <TRecord>(TemporalId id)
     where TRecord : Record;
Esempio n. 23
0
 /// <summary>
 /// Load record by string key from the specified dataset or
 /// its list of imports. The lookup occurs first in descending
 /// order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// The first record in this lookup order is returned, or null
 /// if no records are found or if DeletedRecord is the first
 /// record.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public abstract TRecord LoadOrNull <TKey, TRecord>(TypedKey <TKey, TRecord> key, TemporalId loadFrom)
     where TKey : TypedKey <TKey, TRecord>, new()
     where TRecord : TypedRecord <TKey, TRecord>;
 /// <summary>
 /// Gets ImportsCutoffTime from the dataset detail record.
 /// Returns null if dataset detail record is not found.
 ///
 /// Imported records (records loaded through the Imports list)
 /// where TemporalId is greater than or equal to CutoffTime
 /// will be ignored by load methods and queries, and the latest
 /// available record where TemporalId is less than CutoffTime will
 /// be returned instead.
 ///
 /// This setting only affects records loaded through the Imports
 /// list. It does not affect records stored in the dataset itself.
 ///
 /// Use this feature to freeze Imports as of a given CreatedTime
 /// (part of TemporalId), isolating the dataset from changes to the
 /// data in imported datasets that occur after that time.
 /// </summary>
 public TemporalId?GetImportsCutoffTime(TemporalId dataSetId)
 {
     // TODO - implement when stored in dataset
     return(null);
 }
Esempio n. 25
0
 /// <summary>
 /// Save multiple records to the specified dataset. After the method exits,
 /// for each record the property record.DataSet will be set to the value of
 /// the saveTo parameter.
 ///
 /// All Save methods ignore the value of record.DataSet before the
 /// Save method is called. When dataset is not specified explicitly,
 /// the value of dataset from the context, not from the record, is used.
 /// The reason for this behavior is that the record may be stored from
 /// a different dataset than the one where it is used.
 ///
 /// This method guarantees that TemporalIds of the saved records will be in
 /// strictly increasing order.
 /// </summary>
 public abstract void SaveMany <TRecord>(IEnumerable <TRecord> records, TemporalId saveTo)
     where TRecord : Record;
Esempio n. 26
0
 /// <summary>
 /// Load record by string key from the specified dataset or
 /// its list of imports. The lookup occurs first in descending
 /// order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// The first record in this lookup order is returned, or null
 /// if no records are found or if DeletedRecord is the first
 /// record.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public static TRecord LoadOrNull <TKey, TRecord>(this Context obj, TypedKey <TKey, TRecord> key, TemporalId loadFrom)
     where TKey : TypedKey <TKey, TRecord>, new()
     where TRecord : TypedRecord <TKey, TRecord>
 {
     return(obj.DataSource.LoadOrNull(key, loadFrom));
 }
        /// <summary>
        /// Apply the final constraints after all prior Where clauses but before OrderBy clause:
        ///
        /// * The constraint on dataset lookup list, restricted by CutoffTime (if not null)
        /// * The constraint on ID being strictly less than CutoffTime (if not null)
        /// </summary>
        public IQueryable <TRecord> ApplyFinalConstraints <TRecord>(IQueryable <TRecord> queryable, TemporalId loadFrom)
            where TRecord : Record
        {
            // For pinned records, load from root dataset irrespective of the value of loadFrom
            if (IsPinned <TRecord>())
            {
                loadFrom = TemporalId.Empty;
            }

            // Get lookup list by expanding the list of imports to arbitrary
            // depth with duplicates and cyclic references removed.
            //
            // The list will not include datasets that are after the value of
            // CutoffTime if specified, or their imports (including
            // even those imports that are earlier than the constraint).
            IEnumerable <TemporalId> dataSetLookupList = GetDataSetLookupList(loadFrom);

            // Apply constraint that the value is _dataset is
            // one of the elements of dataSetLookupList_
            var result = queryable.Where(p => dataSetLookupList.Contains(p.DataSet));

            // Apply revision time constraint. By making this constraint the
            // last among the constraints, we optimize the use of the index.
            //
            // The property savedBy_ is set using either CutoffTime element.
            // Only one of these two elements can be set at a given time.
            if (CutoffTime != null)
            {
                result = result.Where(p => p.Id < CutoffTime.Value);
            }

            return(result);
        }
Esempio n. 28
0
 /// <summary>
 /// Get query for the specified type.
 ///
 /// After applying query parameters, the lookup occurs first in
 /// descending order of dataset TemporalIds, and then in the descending
 /// order of record TemporalIds within the first dataset that
 /// has at least one record. Both dataset and record TemporalIds
 /// are ordered chronologically to one second resolution,
 /// and are unique within the database server or cluster.
 ///
 /// The root dataset has empty TemporalId value that is less
 /// than any other TemporalId value. Accordingly, the root
 /// dataset is the last one in the lookup order of datasets.
 ///
 /// Generic parameter TRecord is not necessarily the root data type;
 /// it may also be a type derived from the root data type.
 /// </summary>
 public static IQuery <TRecord> GetQuery <TRecord>(this Context obj, TemporalId loadFrom)
     where TRecord : Record
 {
     return(obj.DataSource.GetQuery <TRecord>(loadFrom));
 }
Esempio n. 29
0
 /// <summary>
 /// Load record by its TemporalId.
 ///
 /// Return null if there is no record for the specified TemporalId;
 /// however an exception will be thrown if the record exists but
 /// is not derived from TRecord.
 /// </summary>
 public static TRecord LoadOrNull <TRecord>(this Context obj, TemporalId id)
     where TRecord : Record
 {
     return(obj.DataSource.LoadOrNull <TRecord>(id));
 }
Esempio n. 30
0
 /// <summary>
 /// Save record to the specified dataset. After the method exits,
 /// record.DataSet will be set to the value of the dataSet parameter.
 ///
 /// All Save methods ignore the value of record.DataSet before the
 /// Save method is called. When dataset is not specified explicitly,
 /// the value of dataset from the context, not from the record, is used.
 /// The reason for this behavior is that the record may be stored from
 /// a different dataset than the one where it is used.
 ///
 /// This method guarantees that TemporalIds will be in strictly increasing
 /// order for this instance of the data source class always, and across
 /// all processes and machine if they are not created within the same
 /// second.
 /// </summary>
 public static void SaveOne <TRecord>(this Context obj, TRecord record, TemporalId saveTo)
     where TRecord : Record
 {
     obj.DataSource.SaveOne(record, saveTo);
 }