我想知道是否可以在 WebAPI 中为扩展子句中的项目预过滤 OData 结果。我只希望它基于带有 Deleted 标志的预定义接口(interface)进行过滤。
public interface IDbDeletedDateTime
{
DateTime? DeletedDateTime { get; set; }
}
public static class IDbDeletedDateTimeExtensions
{
public static IQueryable<T> FilterDeleted<T>(this IQueryable<T> self)
where T : IDbDeletedDateTime
{
return self.Where(s => s.DeletedDateTime == null);
}
}
public class Person : IDbDeletedDateTime
{
[Key]
public int PersonId { get; set }
public DateTime? DeletedDateTime { get; set; }
public virtual ICollection<Pet> Pets { get; set; }
}
public class Pet : IDbDeletedDateTime
{
[Key]
public int PetId { get; set }
public int PersonId { get; set }
public DateTime? DeletedDateTime { get; set; }
}
public class PersonController : ApiController
{
private PersonEntities db = new PersonEntities();
[EnableQuery]
// GET: api/Persons
public IQueryable<Person> GetPersons()
{
return db.Persons.FilterDeleted();
}
}
您可以看到我很容易过滤已删除的人。当有人从 /api/Persons?$expand=Pets
这样的查询中删除 Pets 时,问题就来了有没有办法检查“Pets”的这个扩展是否是 IDbDeletedDateTime 并相应地过滤它们?也许有更好的方法来解决这个问题?
编辑:
我试图根据在 this answer 中找到的内容解决这个问题.我不认为这是可以做到的,至少不是在所有情况下。 ExpandedNavigationSelectItem
中唯一看起来与过滤器相关的部分是 FilterClause
。当它没有过滤器时,它可以为 null,它只是一个 getter 属性,这意味着如果我们愿意,我们不能用新过滤器设置它。不管天气如何,修改当前过滤器的可能性仅涵盖一个小用例,如果我不能重新添加过滤器,我对它不是特别感兴趣。
我有一个扩展方法,它将递归遍历所有扩展子句,您至少可以看到每个扩展的 FilterOption 是什么。如果有人能完全实现这 90% 的代码,那将是惊人的,但我不会屏住呼吸。
public static void FilterDeletables(this ODataQueryOptions queryOptions)
{
//Define a recursive function here.
//I chose to do it this way as I didn't want a utility method for this functionality. Break it out at your discretion.
Action<SelectExpandClause> filterDeletablesRecursive = null;
filterDeletablesRecursive = (selectExpandClause) =>
{
//No clause? Skip.
if (selectExpandClause == null)
{
return;
}
foreach (var selectedItem in selectExpandClause.SelectedItems)
{
//We're only looking for the expanded navigation items.
var expandItem = (selectedItem as ExpandedNavigationSelectItem);
if (expandItem != null)
{
//https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
//The documentation states: "Gets the Path for this expand level. This path includes zero or more type segments followed by exactly one Navigation Property."
//Assuming the documentation is correct, we can assume there will always be one NavigationPropertySegment at the end that we can use.
var edmType = expandItem.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType;
string stringType = null;
IEdmCollectionType edmCollectionType = edmType as IEdmCollectionType;
if (edmCollectionType != null)
{
stringType = edmCollectionType.ElementType.Definition.FullTypeName();
}
else
{
IEdmEntityType edmEntityType = edmType as IEdmEntityType;
if (edmEntityType != null)
{
stringType = edmEntityType.FullTypeName();
}
}
if (!String.IsNullOrEmpty(stringType))
{
Type actualType = typeof(PetStoreEntities).Assembly.GetType(stringType);
if (actualType != null && typeof (IDbDeletable).IsAssignableFrom(actualType))
{
var filter = expandItem.FilterOption;
//expandItem.FilterOption = new FilterClause(new BinaryOperatorNode(BinaryOperatorKind.Equal, new , ));
}
}
filterDeletablesRecursive(expandItem.SelectAndExpand);
}
}
};
filterDeletablesRecursive(queryOptions.SelectExpand?.SelectExpandClause);
}
最佳答案
如果我理解有误,请纠正我:如果实体实现了接口(interface) IDbDeletedDateTime
,您希望始终过滤实体,因此当用户想要扩展导航属性时,您也希望过滤该导航属性实现接口(interface),对吧?
在您当前的代码中,您使用 [EnableQuery]
属性启用了 OData 查询选项,因此 OData 将为您处理扩展查询选项,并且宠物不会按照您想要的方式进行过滤。
您可以选择实现自己的 [MyEnableQuery]
属性,并覆盖 ApplyQuery
方法:检查用户是否设置了 $expand 查询选项,如果是,则检查请求的实体是否实现了 IDbDeletedDateTime
并进行相应的过滤。
可以查看here [EnableQuery]
属性的代码并在 ApplyQuery
方法中看到您有权访问将包含所有查询选项的对象 ODataQueryOptions
由用户设置(WebApi 从 URI 查询字符串填充此对象)。
这将是一个通用的解决方案,如果您要让多个实体与您的自定义筛选接口(interface)具有该接口(interface),则您可以在所有 Controller 方法中使用该解决方案。如果您只想将其用于单个 Controller 方法,您还可以删除 [EnableQuery]
属性,并直接在 Controller 方法中调用查询选项:添加 ODataQueryOptions
参数到您的方法并手动处理查询选项。
那会是这样的:
// GET: api/Persons
public IQueryable<Person> GetPersons(ODataQueryOptions queryOptions)
{
// Inspect queryOptions and apply the query options as you want
// ...
return db.Persons.FilterDeleted();
}
参见 Invoking Query Options directly 部分了解更多如何使用该对象。如果您阅读整篇文章,请注意 [Queryable]
属性是您的 [EnableQuery]
属性,因为这篇文章来自较低版本的 OData。
希望它能为您指明正确的方向以实现您的目标 ;)。
编辑:有关 $expand 子句中嵌套过滤的一些信息:
OData V4 支持过滤扩展内容。这意味着您可以将文件管理器嵌套在扩展子句中,例如:
GET api/user()?$expand=followers($top=2;$select=gender).
在这种情况下,您可以再次选择让 OData 处理它,或者自己探索 ODataQueryOptions
参数来处理它:
在您的 Controller 中,您可以检查扩展选项以及它们是否具有使用此代码的嵌套过滤器:
if (queryOptions.SelectExpand != null) {
foreach (SelectItem item in queryOptions.SelectExpand.SelectExpandClause.SelectedItems) {
if (item.GetType() == typeof(ExpandedNavigationSelectItem)) {
ExpandedNavigationSelectItem navigationProperty = (ExpandedNavigationSelectItem)item;
// Get the name of the property expanded (this way you can control which navigation property you are about to expand)
var propertyName = (navigationProperty.PathToNavigationProperty.FirstSegment as NavigationPropertySegment).NavigationProperty.Name.ToLowerInvariant();
// Get skip and top nested filters:
var skip = navigationProperty.SkipOption;
var top = navigationProperty.TopOption;
/* Here you should retrieve from your DB the entities that you
will return as a result of the requested expand clause with nested filters
... */
}
}
}
关于c# - WebAPI OData 预过滤扩展查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33126251/