/// <summary> /// Returns an <see cref="List{TEntity}"/> based on a custom filtering function applied to the query, as well as /// optional select and expand arguments, checking the user READ permissions along the way /// </summary> /// <param name="filterFunc">Allows any kind of filtering on the query</param> /// <param name="expand">Optional expand argument</param> /// <param name="select">Optional select argument</param> /// <param name="orderby">Optional select argument</param> /// <param name="permissionsFilter">Optional filter argument, if null is passed the q</param> /// <param name="cancellation">Optional select argument</param> protected async Task <List <TEntity> > GetEntitiesByCustomQuery( Func <Query <TEntity>, Query <TEntity> > filterFunc, ExpressionExpand expand, ExpressionSelect select, ExpressionOrderBy orderby, ExpressionFilter permissionsFilter, CancellationToken cancellation) { // Prepare a query of the result, and clone it var repo = GetRepository(); var query = repo.Query <TEntity>(); // Apply custom filter function query = filterFunc(query); // Apply read permissions permissionsFilter ??= await UserPermissionsFilter(Constants.Read, cancellation); query = query.Filter(permissionsFilter); // Expand, Select and Order the result as specified in the OData agruments var expandedQuery = query.Expand(expand).Select(select).OrderBy(orderby ?? ExpressionOrderBy.Parse("Id")); // Required // Load the result into memory var data = await expandedQuery.ToListAsync(cancellation); // this is potentially unordered, should that be a concern? // TODO: This is slow and unused // Apply the permission masks (setting restricted fields to null) and adjust the metadata accordingly // await ApplyReadPermissionsMask(data, query, permissions, GetDefaultMask(), cancellation); // Return return(data); }
/// <summary> /// Returns a <see cref="TEntity"/> as per the Id and the specifications in the <see cref="GetByIdArguments"/>, after verifying the user's permissions /// </summary> public virtual async Task <(TEntity, Extras)> GetById(TKey id, GetByIdArguments args, CancellationToken cancellation) { // Parse the parameters var expand = ExpressionExpand.Parse(args?.Expand); var select = ParseSelect(args?.Select); // Load the data var data = await GetEntitiesByIds(new List <TKey> { id }, expand, select, null, cancellation); // Check that the entity exists, else return NotFound var entity = data.SingleOrDefault(); if (entity == null) { throw new NotFoundException <TKey>(id); } // Load the extras var extras = await GetExtras(data, cancellation); // Return return(entity, extras); }
/// <summary> /// Returns a <see cref="List{TEntity}"/> as per the propName, values and the specifications in the <see cref="ExpressionExpand"/> and <see cref="ExpressionSelect"/>, /// after verifying the user's READ permissions, returns the entities in the same order as the supplied Ids /// </summary> public virtual async Task <(List <TEntity>, Extras)> GetByPropertyValues(string propName, IEnumerable <object> values, SelectExpandArguments args, CancellationToken cancellation) { if (propName is null) { throw new ArgumentNullException(nameof(propName)); } // Load the data List <TEntity> data; if (values == null || !values.Any()) { data = new List <TEntity>(); } else { // Parse the parameters var expand = ExpressionExpand.Parse(args?.Expand); var select = ParseSelect(args?.Select); data = await GetEntitiesByCustomQuery(q => q.FilterByPropertyValues(propName, values), expand, select, null, null, cancellation); } // Load the extras var extras = await GetExtras(data, cancellation); // Return return(data, extras); }
/// <summary> /// Returns an <see cref="List{TEntity}"/> based on a custom <paramref name="filterFunc"/> applied to the query, as well as /// optional <paramref name="expand"/> and <paramref name="select"/> arguments, checking the user's READ permissions along the way. /// </summary> /// <param name="filterFunc">Allows any kind of filtering on the query</param> /// <param name="expand">Optional expand argument.</param> /// <param name="select">Optional select argument.</param> /// <param name="orderby">Optional orderby argument.</param> /// <param name="permissionsFilter">Optional filter argument, if null is passed the query uses the read permissions filter of the current user.</param> /// <param name="cancellation">The cancellation instruction.</param> protected async Task <List <TEntity> > GetEntitiesByCustomQuery( Func <EntityQuery <TEntity>, EntityQuery <TEntity> > filterFunc, ExpressionExpand expand, ExpressionSelect select, ExpressionOrderBy orderby, ExpressionFilter permissionsFilter, CancellationToken cancellation) { // Prepare a query of the result, and clone it var factory = QueryFactory(); var query = factory.EntityQuery <TEntity>(); // Apply custom filter function query = filterFunc(query); // Apply read permissions permissionsFilter ??= await UserPermissionsFilter(PermissionActions.Read, cancellation); query = query.Filter(permissionsFilter); // Expand, Select and Order the result as specified in the Queryex agruments var expandedQuery = query.Expand(expand).Select(select).OrderBy(orderby ?? ExpressionOrderBy.Parse("Id")); // Required // Load the result into memory var data = await expandedQuery.ToListAsync(QueryContext(), cancellation); // this is potentially unordered, should that be a concern? // Return return(data); }
/// <summary> /// Returns a <see cref="List{TEntity}"/> as per the Ids and the specifications in the <see cref="SelectExpandArguments"/>, after verifying the user's permissions /// </summary> public virtual async Task <(List <TEntity>, Extras)> GetByIds(List <TKey> ids, SelectExpandArguments args, string action, CancellationToken cancellation) { // Parse the parameters var expand = ExpressionExpand.Parse(args?.Expand); var select = ParseSelect(args?.Select); // Prepare the permissions filter var permissionsFilter = await UserPermissionsFilter(action, cancellation); // Load the data var data = await GetEntitiesByIds(ids, expand, select, permissionsFilter, cancellation); var extras = await GetExtras(data, cancellation); // Return result return(data, extras); }
/// <summary> /// Returns a list of entities as per the specifications in the <see cref="GetChildrenArguments{TKey}"/> /// </summary> public virtual async Task <(List <TEntity>, Extras)> GetChildrenOf(GetChildrenArguments <TKey> args, CancellationToken cancellation) { // Parse the parameters var expand = ExpressionExpand.Parse(args.Expand); var select = ParseSelect(args.Select); var filter = ExpressionFilter.Parse(args.Filter); var orderby = ExpressionOrderBy.Parse("Node"); var ids = args.I ?? new List <TKey>(); // Load the data var data = await GetEntitiesByCustomQuery(q => q.FilterByParentIds(ids, args.Roots).Filter(filter), expand, select, orderby, null, cancellation); var extras = await GetExtras(data, cancellation); // Transform and Return return(data, extras); }
/// <summary> /// Returns a <see cref="List{TEntity}"/> as per the Ids and the specifications /// in <paramref name="args"/>, after verifying the user's permissions. /// </summary> /// <remarks> /// This function does not call <see cref="ServiceBase.Initialize"/>. That is the responsibility of the caller. /// </remarks> protected virtual async Task <TEntitiesResult> GetByIds(IList <TKey> ids, SelectExpandArguments args, string action, CancellationToken cancellation) { await Initialize(cancellation); // Parse the parameters var expand = ExpressionExpand.Parse(args?.Expand); var select = ParseSelect(args?.Select); // Prepare the permissions filter var permissionsFilter = await UserPermissionsFilter(action, cancellation); // Load the data var data = await GetEntitiesByIds(ids, expand, select, permissionsFilter, cancellation); // Return result return(await ToEntitiesResult(data, data.Count, cancellation)); }
/// <summary> /// Returns a <see cref="List{TEntity}"/> as per the Ids and the specifications in the <see cref="ExpressionExpand"/> and <see cref="ExpressionSelect"/>, /// after verifying the user's permissions, returns the entities in the same order as the supplied Ids. /// If null was supplied for permission filter, the function by default uses the filter for the read permissions filter /// </summary> protected virtual async Task <List <TEntity> > GetEntitiesByIds( List <TKey> ids, ExpressionExpand expand, ExpressionSelect select, ExpressionFilter permissionsFilter, CancellationToken cancellation) { if (ids == null || ids.Count == 0) { return(new List <TEntity>()); } else { var data = await GetEntitiesByCustomQuery(q => q.FilterByIds(ids), expand, select, null, permissionsFilter, cancellation); // If the data is only if (ids.Count == 1 && data.Count == 1) { // No need to sort return(data); } else { // Sort the entities according to the original Ids, as a good practice TEntity[] dataSorted = new TEntity[ids.Count]; Dictionary <TKey, TEntity> dataDic = data.ToDictionary(e => e.Id); for (int i = 0; i < ids.Count; i++) { var id = ids[i]; if (dataDic.TryGetValue(id, out TEntity entity)) { dataSorted[i] = entity; } } return(dataSorted.Where(e => e != null).ToList()); } } }
/// <summary> /// Returns a list of entities and optionally their count as per the specifications in <paramref name="args"/>. /// </summary> public virtual async Task <TEntitiesResult> GetEntities(GetArguments args, CancellationToken cancellation) { await Initialize(cancellation); // Parse the parameters var filter = ExpressionFilter.Parse(args.Filter); var orderby = ExpressionOrderBy.Parse(args.OrderBy); var expand = ExpressionExpand.Parse(args.Expand); var select = ParseSelect(args.Select); // Prepare the query var query = QueryFactory().EntityQuery <TEntity>(); // Apply read permissions var permissionsFilter = await UserPermissionsFilter(PermissionActions.Read, cancellation); query = query.Filter(permissionsFilter); // Apply search query = await Search(query, args, cancellation); // Apply filter query = query.Filter(filter); // Apply orderby orderby ??= await DefaultOrderBy(cancellation); query = query.OrderBy(orderby); // Apply the paging (Protect against DOS attacks by enforcing a maximum page size) var top = args.Top; var skip = args.Skip; top = Math.Min(top, MaximumPageSize()); query = query.Skip(skip).Top(top); // Apply the expand, which has the general format 'Expand=A,B.C,D' query = query.Expand(expand); // Apply the select, which has the general format 'Select=A,B.C,D' query = query.Select(select); // Load the data and count in memory List <TEntity> data; int? count = null; if (args.CountEntities) { var output = await query.ToListAndCountAsync(MaximumCount, QueryContext, cancellation); data = output.Entities; count = output.Count; } else { data = await query.ToListAsync(QueryContext, cancellation); } // Return return(await ToEntitiesResult(data, count, cancellation)); }