internal BlBinaryExpression ApplySoftDeleteFilterIfApplicable(bool includeDeleted, BlBinaryExpression filter, BlsPawn pawn) { string containerName = Graph.GetStorageContainerNameForPawn(pawn); BlGraphContainer container = Graph.CompiledCollections.FirstOrDefault(c => c.StorageContainerName == containerName); BlContainerProp softDeleteProp = container?.Properties.FirstOrDefault(prop => prop.IsSoftDeleteProp); if (softDeleteProp == null) { return(filter); } var softDeleteClause = new BlBinaryExpression { PropName = softDeleteProp.Name, Operator = BlOperator.Eq, Value = includeDeleted }; if (filter == null) { return(softDeleteClause); } var newRoot = new BlBinaryExpression { Left = filter, Operator = BlOperator.And, Right = softDeleteClause }; return(newRoot); }
public void ResolveContainerMetadataFromPawnSubClass(BlsPawn pawn) { BlGraphContainer container = new BlGraphContainer { Properties = new List <BlContainerProp>(), BlContainerName = pawn.GetType().Name, StorageContainerName = _storageNamingEncoder.EncodePawnContainerName(pawn.GetType().Name) }; container = ResolveContainerProps(container, pawn); _compiledCollections.Add(container); }
internal string ResolveSortExpression <TPawn>(Expression <Func <TPawn, IComparable> > sortProperty) where TPawn : BlsPawn, new() { if (sortProperty == null) { string containerName = Graph.GetStorageContainerNameForPawn(new TPawn()); BlGraphContainer container = Graph.CompiledCollections.FirstOrDefault(c => c.StorageContainerName == containerName); BlContainerProp defaultSortProperty = container?.Properties.FirstOrDefault(p => p.IsDefaultSort); return(defaultSortProperty?.Name); } Expression expression = sortProperty.Body; if (expression is MemberExpression) { return(expression.ToString().Split('.')[1]); } throw new InvalidSortPropertyError($"Only property access expression are supported; you provided {expression.GetType().Name}"); }
private BlGraphContainer ResolveContainerProps(BlGraphContainer container, BlsPawn pawn) { var softDeleteFlagUsed = false; List <PropertyInfo> properties = pawn.GetType().GetProperties().ToList(); foreach (PropertyInfo property in properties) { Type propType = property.PropertyType; if (BlUtils.IsEnumerableType(propType) && propType != typeof(string)) { throw new DisallowedPawnPropertyError( $"Collection properties are not allowed in pawns: {property.Name} of {pawn.GetType().Name}"); } Attribute[] attributes = property.GetCustomAttributes().ToArray(); var blProp = new BlContainerProp(); if (property.CanRead && property.CanWrite) { container.Properties.Add(blProp); blProp.Name = property.Name; blProp.PropType = propType; if (attributes.Any()) { foreach (Attribute attribute in attributes) { if (attribute is UsedForSoftDeletes) { if (blProp.PropType != typeof(bool)) { throw new InvalidPropertyTypeForSoftDelete($"Only boolean type is allowed for the soft delete flag. You are trying to apply it to the property {blProp.Name}, which is of type {blProp.PropType}"); } if (softDeleteFlagUsed) { throw new DuplicateSoftDeletionFlagError( $"Attempting to declare second soft deletion flag in pawn {blProp.Name}. Only one soft deletion property is allowed per pawn"); } blProp.IsSoftDeleteProp = true; softDeleteFlagUsed = true; } if (attribute is FullTextSearchable) { if (blProp.PropType != typeof(string)) { throw new InvalidFullTextSearchAttributeError( $"Attempting to apply a full text search attribute to a non-string property {blProp.Name} of {container.BlContainerName}"); } blProp.IsSearchable = true; } if (attribute is StringLengthRestriction sRes) { if (blProp.PropType != typeof(string)) { throw new InvalidRestrictiveAttributeError( $"Attempting to apply a string attribute to a non-string property {blProp.Name} of {container.BlContainerName}"); } blProp.MinChar = sRes.MinCharacters; blProp.MaxChar = sRes.MaxCharacters == 0 ? int.MaxValue : sRes.MaxCharacters; } if (attribute is NumberRestriction nRes) { if (!BlUtils.IsNumericType(blProp.PropType)) { throw new InvalidRestrictiveAttributeError( $"Attempting to apply a numeric attribute to a non-number property {blProp.Name} of {container.BlContainerName}"); } blProp.MinValue = nRes.Minimum; blProp.MaxValue = Math.Abs(nRes.Maximum) < 0.000001 ? float.MaxValue : nRes.Maximum; } if (attribute is DateRestriction dRes) { if (blProp.PropType != typeof(DateTime)) { throw new InvalidRestrictiveAttributeError( $"Attempting to apply a date restriction attribute to a non-date property {blProp.Name} of {container.BlContainerName}"); } DateTime earliestValue; DateTime latestValue; if (string.IsNullOrEmpty(dRes.Earliest)) { earliestValue = DateTime.MinValue; } else { var parsed = DateTime.TryParse(dRes.Earliest, out earliestValue); if (!parsed) { throw new InvalidRestrictiveAttributeError( $"Date restriction attribute is not in correct format: {blProp.Name} of {container.BlContainerName}"); } } if (string.IsNullOrEmpty(dRes.Latest)) { latestValue = DateTime.MaxValue; } else { var parsed = DateTime.TryParse(dRes.Latest, out latestValue); if (!parsed) { throw new InvalidRestrictiveAttributeError( $"Date restriction attribute is not in correct format: {blProp.Name} of {container.BlContainerName}"); } } blProp.EarliestDate = earliestValue; blProp.LatestDate = latestValue; } } } } } return(container); }
/// <summary> /// Use this method to retrieve related objects of the certain <typeparam name="T"></typeparam> type. The method /// returns a cursor which contains pawns in storage as well as pawns currently sitting in the BLS memory /// </summary> /// <param name="filter">Optional filter to apply to the result set; the filter will be applied to both the im-memory /// and in-storage data</param> /// <param name="sortDir"></param> /// <param name="includeSoftDeleted">If set to true, the method ignores soft-delete flag if one is available on the /// pawn model. Set to false by default</param> /// <param name="batchSize">Controls the size of the batch of each incremental retrieval of pawns from storage. Defaults to 200 objects</param> /// <param name="sortProperty"></param> /// <returns>Cursor containing the result set</returns> /// <exception cref="InvalidOperationException"></exception> public StorageCursor <T> Find( Expression <Func <T, bool> > filter = null, Expression <Func <T, IComparable> > sortProperty = null, Sort sortDir = Sort.Asc, bool includeSoftDeleted = false, int batchSize = 200) { Connection[] connections = SourcePawn.SystemRef.ToConnect .Where(c => c.From == SourcePawn) .ToArray(); BlGraphContainer container = SourcePawn.SystemRef.Graph.CompiledCollections .FirstOrDefault(c => c.BlContainerName == typeof(T).Name); if (container == null) { throw new InvalidOperationException("collection not found"); } string id = SourcePawn.GetId(); string relationName = SourcePawn.SystemRef.Graph.GetStorageRelationName(this); string containerName = SourcePawn.SystemRef.Graph.GetStorageContainerNameForPawn(SourcePawn); if (filter == null) { var filterExpression = SourcePawn.SystemRef .ApplySoftDeleteFilterIfApplicable(includeSoftDeleted, null, new T()); List <T> connectedPawns = connections.Select(c => (T)c.To).ToList(); // if there is no id, the pawn has not been saved yet so there only return in-memory relations if (string.IsNullOrEmpty(id)) { return(new StorageCursor <T>().AttachInMemoryPawns(connectedPawns).InjectBls(SourcePawn.SystemRef)); } // otherwise, also check the storage string sort = SourcePawn.SystemRef.ResolveSortExpression(sortProperty); var cursorFromStorage = SourcePawn.SystemRef.StorageProvider.GetByRelation <T>(id, relationName, containerName, filterExpression, sort, sortDir, batchSize); return(cursorFromStorage.AttachInMemoryPawns(connectedPawns).InjectBls(SourcePawn.SystemRef)); } // the rest of the code assumes the filter is not null if (string.IsNullOrEmpty(id)) { List <T> foundRelations = connections.Select(c => (T)c.To).ToList(); List <T> connectedPawns = foundRelations.Where(filter.Compile()).ToList(); return(new StorageCursor <T>().AttachInMemoryPawns(connectedPawns).InjectBls(SourcePawn.SystemRef)); } else { List <T> foundRelations = connections.Select(c => (T)c.To).ToList(); List <T> connectedPawns = foundRelations.Where(filter.Compile()).ToList(); BlBinaryExpression storageFilterExpression = SourcePawn.SystemRef.ResolveFilterExpression(filter); storageFilterExpression = SourcePawn.SystemRef .ApplySoftDeleteFilterIfApplicable(includeSoftDeleted, storageFilterExpression, new T()); string sort = SourcePawn.SystemRef.ResolveSortExpression(sortProperty); StorageCursor <T> cursorFromStorage = SourcePawn.SystemRef.StorageProvider.GetByRelation <T>(id, relationName, containerName, storageFilterExpression, sort, sortDir, batchSize); return(cursorFromStorage.AttachInMemoryPawns(connectedPawns).InjectBls(SourcePawn.SystemRef)); } }