我们'得到了一些工作'......不优雅但可以完成工作。
Gotcha - 我的同事和我发现使用主实现时使用SelectMany是不可能的,这在从单独的集合中获取数组时需要,例如在 SQL 世界中:
select s.foo from c join s in c.someArray
这是有效的:
using Ardalis.Specification;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Whatever.Namespace.You.Want
{
public interface ICosmosDbSpecification<T, TMapped> : ISpecification<T, TMapped>
{
Expression<Func<T, IEnumerable<TMapped>>>? ManySelector { get; }
}
}
实现该接口以在堆栈中获取 SelectMany 功能:
using Ardalis.Specification;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Whatever.Namespace.You.Want
{
public abstract class CosmosDbSpecification<T, TMapped> : Specification<T, TMapped>, ICosmosDbSpecification<T, TMapped>
{
protected new virtual ICosmosDbSpecificationBuilder<T, TMapped> Query { get; }
public Expression<Func<T, IEnumerable<TMapped>>>? ManySelector { get; internal set; }
protected CosmosDbSpecification()
: this(InMemorySpecificationEvaluator.Default)
{
}
protected CosmosDbSpecification(IInMemorySpecificationEvaluator inMemorySpecificationEvaluator)
: base(inMemorySpecificationEvaluator)
{
this.Query = new CosmosDbSpecificationBuilder<T, TMapped>(this);
}
}
public interface ICosmosDbSpecificationBuilder<T, TResult> : ISpecificationBuilder<T, TResult>
{
new CosmosDbSpecification<T, TResult> Specification { get; }
}
public class CosmosDbSpecificationBuilder<T, TResult> : SpecificationBuilder<T, TResult>, ICosmosDbSpecificationBuilder<T, TResult>
{
public new CosmosDbSpecification<T, TResult> Specification { get; }
public CosmosDbSpecificationBuilder(CosmosDbSpecification<T, TResult> specification)
: base(specification)
{
this.Specification = specification;
}
}
public static class CosmosDbSpecificationBuilderExtensions
{
/// <summary>
/// Allows CosmosDb SelectMany methods. WARNING can only have Select OR SelectMany ... using both may throw
/// </summary>
public static ICosmosDbSpecificationBuilder<T, TResult> SelectMany<T, TResult>(
this ICosmosDbSpecificationBuilder<T, TResult> specificationBuilder,
Expression<Func<T, IEnumerable<TResult>>> manySelector)
{
specificationBuilder.Specification.ManySelector = manySelector;
return specificationBuilder;
}
}
}
可能应该将InMemorySpecificationEvaluator.Default
单例更改为我们自己的……但是当前的实现可以正常工作并继续进行。
然后,所有这些都使用定制的类似评估器的东西在存储库中缝合在一起:
using Ardalis.Specification;
using Microsoft.Azure.Cosmos;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Whatever.Namespace.You.Want
{
public static class SpecificationEvaluator <T>
{
public static IOrderedQueryable<TResult> ApplySpecification<TResult>(Container container, ISpecification<T, TResult> specification)
{
var queryable = container.GetItemLinqQueryable<T>(
true, default, default,
new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase });
foreach (var criteria in specification.WhereExpressions)
{
queryable = (IOrderedQueryable<T>)queryable.Where(criteria);
}
if (specification.OrderExpressions != null)
{
if (specification.OrderExpressions.Where(x => x.OrderType == OrderTypeEnum.OrderBy ||
x.OrderType == OrderTypeEnum.OrderByDescending).Count() > 1)
{
throw new DuplicateOrderChainException();
}
IOrderedQueryable<T> orderedQuery = null;
foreach (var orderExpression in specification.OrderExpressions)
{
if (orderExpression.OrderType == OrderTypeEnum.OrderBy)
{
orderedQuery = Queryable.OrderBy((dynamic)queryable, (dynamic)RemoveConvert(orderExpression.KeySelector));
}
else if (orderExpression.OrderType == OrderTypeEnum.OrderByDescending)
{
orderedQuery = Queryable.OrderByDescending((dynamic)queryable, (dynamic)RemoveConvert(orderExpression.KeySelector));
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenBy)
{
orderedQuery = Queryable.ThenBy((dynamic)orderedQuery, (dynamic)RemoveConvert(orderExpression.KeySelector));
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenByDescending)
{
orderedQuery = Queryable.ThenByDescending((dynamic)orderedQuery, (dynamic)RemoveConvert(orderExpression.KeySelector));
}
}
if (orderedQuery != null)
{
queryable = orderedQuery;
}
}
if (specification.Skip != null && specification.Skip != 0)
{
queryable = (IOrderedQueryable<T>)queryable.Skip(specification.Skip.Value);
}
if (specification.Take != null)
{
queryable = (IOrderedQueryable<T>)queryable.Take(specification.Take.Value);
}
if (typeof(ICosmosDbSpecification<T, TResult>).IsAssignableFrom(specification.GetType()))
{
var selectMany = ((ICosmosDbSpecification<T, TResult>)specification).ManySelector;
if (selectMany != null)
{
if (specification.Selector != null)
{
throw new ApplicationException("Cannot set both Selector and ManySelector on same specification");
}
if (specification.Take != null || specification.Skip != null)
{
// until figured out how to implement this on final solution instead of inner root request (gives not supported error in sdk)
throw new ApplicationException("Select many does not support take or skip ...");
}
return (IOrderedQueryable<TResult>)queryable.SelectMany(selectMany);
}
}
return (IOrderedQueryable<TResult>)queryable.Select(specification.Selector);
}
private static LambdaExpression RemoveConvert(LambdaExpression source)
{
var body = source.Body;
while (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
return Expression.Lambda(body, source.Parameters);
}
}
}
并在您的通用基础存储库中使用:
IOrderedQueryable<TResult> queryable =
SpecificationEvaluator<T>.ApplySpecification(_container, specification);
using var iterator = queryable.ToFeedIterator<TResult>();
...
使用如下规范:
public class GetDetailSpecification : CosmosDbSpecification<TypeOfData, TypeOfOutput>
{
public GetFooBarSpecification(YourParameterisedFilterObject filter)
{
if (filter == null) throw new ArgumentNullException(nameof(filter));
Query.Select(x => new TypeOfOutput { Foo = x.Bar });
Query.Where(x => x.Id == filter.Id && x.PartitionKey == filter.PartitionKeyValue);
}
}