/// <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; }
//--- 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>(); }
//--- PRIVATE /// <summary> /// Returned object holds two collection references - one for the base /// type of all records and the other for the record type specified /// as generic parameter. /// /// The need to hold two collection arises from the requirement /// that query for a derived type takes into account that another /// record with the same key and later dataset or object timestamp /// may exist. For this reason, the typed collection is used for /// LINQ constraints and base collection is used to iterate over /// objects. /// /// This method also creates indices if they do not exist. The /// two default indices are always created: one for optimizing /// loading by key and the other by query. /// /// Additional indices may be created using class attribute /// [IndexElements] for further performance optimization. /// </summary> private TemporalMongoCollection <TRecord> GetOrCreateCollection <TRecord>() where TRecord : Record { // Check if collection object has already been cached // for this type and return cached result if found if (collectionDict_.TryGetValue(typeof(TRecord), out object collectionObj)) { var cachedResult = collectionObj.CastTo <TemporalMongoCollection <TRecord> >(); return(cachedResult); } // Check that hierarchical discriminator convention is set for TRecord var discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(typeof(TRecord)); if (!discriminatorConvention.Is <HierarchicalDiscriminatorConvention>()) { throw new Exception( $"Hierarchical discriminator convention is not set for type {typeof(TRecord).Name}. " + $"The convention should have been set set in the static constructor of " + $"MongoDataSource"); } // Collection name is root class name of the record without prefix string collectionName = DataTypeInfo.GetOrCreate <TRecord>().GetCollectionName(); // Get interfaces to base and typed collections for the same name var baseCollection = Db.GetCollection <Record>(collectionName); var typedCollection = Db.GetCollection <TRecord>(collectionName); //--- Load standard index types // Each data type has an index for optimized loading by key. // This index consists of Key in ascending order, followed by // DataSet and ID in descending order. var loadIndexKeys = Builders <TRecord> .IndexKeys .Ascending(new StringFieldDefinition <TRecord>("_key")) // .Key .Descending(new StringFieldDefinition <TRecord>("_dataset")) // .DataSet .Descending(new StringFieldDefinition <TRecord>("_id")); // .Id // Use index definition convention to specify the index name var loadIndexName = "Key-DataSet-Id"; var loadIndexModel = new CreateIndexModel <TRecord>(loadIndexKeys, new CreateIndexOptions { Name = loadIndexName }); typedCollection.Indexes.CreateOne(loadIndexModel); //--- Load custom index types // Additional indices are provided using IndexAttribute for the class. // Get a sorted dictionary of (definition, name) pairs // for the inheritance chain of the specified type. var indexDict = IndexElementsAttribute.GetAttributesDict <TRecord>(); // Iterate over the dictionary to define the index foreach (var indexInfo in indexDict) { string indexDefinition = indexInfo.Key; string indexName = indexInfo.Value; // Parse index definition to get a list of (ElementName,SortOrder) tuples List <(string, int)> indexTokens = IndexElementsAttribute.ParseDefinition <TRecord>(indexDefinition); var indexKeysBuilder = Builders <TRecord> .IndexKeys; IndexKeysDefinition <TRecord> indexKeys = null; // Iterate over (ElementName,SortOrder) tuples foreach (var indexToken in indexTokens) { (string elementName, int sortOrder) = indexToken; if (indexKeys == null) { // Create from builder for the first element if (sortOrder == 1) { indexKeys = indexKeysBuilder.Ascending(new StringFieldDefinition <TRecord>(elementName)); } else if (sortOrder == -1) { indexKeys = indexKeysBuilder.Descending(new StringFieldDefinition <TRecord>(elementName)); } else { throw new Exception("Sort order must be 1 or -1."); } } else { // Chain to the previous list of index keys for the remaining elements if (sortOrder == 1) { indexKeys = indexKeys.Ascending(new StringFieldDefinition <TRecord>(elementName)); } else if (sortOrder == -1) { indexKeys = indexKeys.Descending(new StringFieldDefinition <TRecord>(elementName)); } else { throw new Exception("Sort order must be 1 or -1."); } } } if (indexName == null) { throw new Exception("Index name cannot be null."); } var indexModel = new CreateIndexModel <TRecord>(indexKeys, new CreateIndexOptions { Name = indexName }); // Add to indexes for the collection typedCollection.Indexes.CreateOne(indexModel); } // Create result that holds both base and typed collections TemporalMongoCollection <TRecord> result = new TemporalMongoCollection <TRecord>(this, baseCollection, typedCollection); // Add the result to the collection dictionary and return collectionDict_.TryAdd(typeof(TRecord), result); return(result); }