我们'得到了一些工作'......不优雅但可以完成工作。
Gotcha - 我和我的同事没有办法通过主实现来使用 SelectMany,这在从单独的集合中获取数组时是必需的,例如在 SQL 世界中:
select s.foo from c join s in c.someArray
这是有效的:
- 创建了自己的子类关闭规范
- 实现了 hacky 评估器
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);
}
}