Skip to content

Instantly share code, notes, and snippets.

@dfch
Last active February 27, 2016 17:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfch/f00938e2cc3057e5957a to your computer and use it in GitHub Desktop.
Save dfch/f00938e2cc3057e5957a to your computer and use it in GitHub Desktop.
[AppclusiveAuthorize]
public async Task<IHttpActionResult> GetNodes(ODataQueryOptions<Node> queryOptions)
{
Contract.Requires(null != queryOptions, "|400|");
queryOptions.Validate(_validationSettings);
db.DisableGeneralTenantFilterForUberAdmin();
var pageableFilterResult = db.Nodes.PageableEntityFilter(queryOptions, (entity, context) =>
{
if(context.AccessManager.HasPermission(entity, Permissions.NodesCanRead))
{
return true;
}
return false;
});
this.SetNextLink(pageableFilterResult.EntitySet, pageableFilterResult.SkipCount);
return Ok<IEnumerable<Node>>(pageableFilterResult.EntitySet);
}
GET http://localhost:53422/api/Core/Nodes()?$filter=Name%20ne%20'Root%20Node'&$orderby=Modified,%20Created%20desc,%20Id%20asc&$skip=5&$top=35 HTTP/1.1
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
Accept: application/json;odata=minimalmetadata
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
Authorization: Basic UXVlc3Rpb246d2hvLXJlYWxseS1wb3N0cy1CQVNFNjQtZW5jb2RlZC1jcmVkZW50aWFscy10by1wdWJsaWMtd2ViLXNpdGVz
Host: localhost:53422
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata=minimalmetadata; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Set-Cookie: biz.dfch.CS.Appclusive.Core.Security.AuthenticationFilters.MultiAuthenticationFilter=D4-05-FC-92-41-10-BB-31-6F-7C-18-B9-1F-0C-91-62-B8-E0-4F-2C-0C-F6-BE-A8-F2-53-19-76-06-A3-B4-5A
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Sat, 27 Feb 2016 15:57:30 GMT
Content-Length: 5819
{
"odata.metadata":"http://localhost:53422/api/Core/$metadata#Nodes","value":[
{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"18","Tid":"22222222-2222-2222-2222-222222222222","Name":"still","Description":"still is a basque for mainland purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:51.2250094+01:00","Modified":"2016-02-19T13:05:51.2250094+01:00","RowVersion":"AAAAAAAAjWM="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"20","Tid":"22222222-2222-2222-2222-222222222222","Name":"mariner","Description":"mariner is a covert for derma purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:51.8220436+01:00","Modified":"2016-02-19T13:05:51.8220436+01:00","RowVersion":"AAAAAAAAjW8="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"20","Tid":"22222222-2222-2222-2222-222222222222","Name":"mariner","Description":"mariner is a covert for derma purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:51.8220436+01:00","Modified":"2016-02-19T13:05:51.8220436+01:00","RowVersion":"AAAAAAAAjW8="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"22","Tid":"22222222-2222-2222-2222-222222222222","Name":"vitellin","Description":"vitellin is a squeeze for varices purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:52.3860758+01:00","Modified":"2016-02-19T13:05:52.3860758+01:00","RowVersion":"AAAAAAAAjXs="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"24","Tid":"22222222-2222-2222-2222-222222222222","Name":"missal","Description":"missal is a resale for vendace purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:53.0281126+01:00","Modified":"2016-02-19T13:05:53.0281126+01:00","RowVersion":"AAAAAAAAjYc="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"26","Tid":"22222222-2222-2222-2222-222222222222","Name":"oxidase","Description":"oxidase is a ultra for mannish purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:53.6611488+01:00","Modified":"2016-02-19T13:05:53.6611488+01:00","RowVersion":"AAAAAAAAjZM="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"28","Tid":"22222222-2222-2222-2222-222222222222","Name":"terrapin","Description":"terrapin is a fraenum for caroche purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:54.259183+01:00","Modified":"2016-02-19T13:05:54.259183+01:00","RowVersion":"AAAAAAAAjZ8="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"30","Tid":"22222222-2222-2222-2222-222222222222","Name":"stagnate","Description":"stagnate is a potheen for pitman purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:55.0032255+01:00","Modified":"2016-02-19T13:05:55.0032255+01:00","RowVersion":"AAAAAAAAjas="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"32","Tid":"22222222-2222-2222-2222-222222222222","Name":"fraenum","Description":"fraenum is a hindmost for vendace purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:55.5642576+01:00","Modified":"2016-02-19T13:05:55.5642576+01:00","RowVersion":"AAAAAAAAjbc="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"34","Tid":"22222222-2222-2222-2222-222222222222","Name":"matins","Description":"matins is a baronet for overtake purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:56.1272898+01:00","Modified":"2016-02-19T13:05:56.1272898+01:00","RowVersion":"AAAAAAAAjcM="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"36","Tid":"22222222-2222-2222-2222-222222222222","Name":"unwashed","Description":"unwashed is a petrosal for aside purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:56.9203352+01:00","Modified":"2016-02-19T13:05:56.9203352+01:00","RowVersion":"AAAAAAAAjc8="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"38","Tid":"22222222-2222-2222-2222-222222222222","Name":"revolute","Description":"revolute is a mannish for leal purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:57.721381+01:00","Modified":"2016-02-19T13:05:57.721381+01:00","RowVersion":"AAAAAAAAjds="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"40","Tid":"22222222-2222-2222-2222-222222222222","Name":"toneme","Description":"toneme is a papaya for lollygag purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:05:59.0604576+01:00","Modified":"2016-02-19T13:05:59.0604576+01:00","RowVersion":"AAAAAAAAjec="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"42","Tid":"22222222-2222-2222-2222-222222222222","Name":"leaky","Description":"leaky is a pitman for leal purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:06:00.3735327+01:00","Modified":"2016-02-19T13:06:00.3735327+01:00","RowVersion":"AAAAAAAAjfM="
},{
"EntityId":null,"Parameters":"{}","EntityKindId":"23","ParentId":"1","Id":"44","Tid":"22222222-2222-2222-2222-222222222222","Name":"lurdan","Description":"lurdan is a resale for oodles purposes","CreatedById":"3","ModifiedById":"3","Created":"2016-02-19T13:06:01.2015801+01:00","Modified":"2016-02-19T13:06:01.2015801+01:00","RowVersion":"AAAAAAAAjf8="
}
],"odata.nextLink":"http://localhost:53422/api/Core/Nodes()?$filter=Name%20ne%20'Root%20Node'&$orderby=Modified,%20Created%20desc,%20Id%20asc&$top=35&$skip=41"
}
2016-02-27 18:36:15,206 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__0: Root Node
2016-02-27 18:36:15,207 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__1: 0
2016-02-27 18:36:15,207 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__2: 16
2016-02-27 18:36:15,213 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - Intercepted on: ReaderExecuted :- IsAsync: False, Command Text: SELECT
[Project1].[Id] AS [Id],
[Project1].[EntityId] AS [EntityId],
[Project1].[Parameters] AS [Parameters],
[Project1].[EntityKindId] AS [EntityKindId],
[Project1].[ParentId] AS [ParentId],
[Project1].[Tid] AS [Tid],
[Project1].[Name] AS [Name],
[Project1].[Description] AS [Description],
[Project1].[CreatedById] AS [CreatedById],
[Project1].[ModifiedById] AS [ModifiedById],
[Project1].[Created] AS [Created],
[Project1].[Modified] AS [Modified],
[Project1].[RowVersion] AS [RowVersion]
FROM ( SELECT
[Limit1].[Id] AS [Id],
[Limit1].[EntityId] AS [EntityId],
[Limit1].[Parameters] AS [Parameters],
[Limit1].[EntityKindId] AS [EntityKindId],
[Limit1].[ParentId] AS [ParentId],
[Limit1].[Tid] AS [Tid],
[Limit1].[Name] AS [Name],
[Limit1].[Description] AS [Description],
[Limit1].[CreatedById] AS [CreatedById],
[Limit1].[ModifiedById] AS [ModifiedById],
[Limit1].[Created] AS [Created],
[Limit1].[Modified] AS [Modified],
[Limit1].[RowVersion] AS [RowVersion]
FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[EntityId] AS [EntityId], [Extent1].[Parameters] AS [Parameters], [Extent1].[EntityKindId] AS [EntityKindId], [Extent1].[ParentId] AS [ParentId], [Extent1].[Tid] AS [Tid], [Extent1].[Name] AS [Name], [Extent1].[Description] AS [Description], [Extent1].[CreatedById] AS [CreatedById], [Extent1].[ModifiedById] AS [ModifiedById], [Extent1].[Created] AS [Created], [Extent1].[Modified] AS [Modified], [Extent1].[RowVersion] AS [RowVersion]
FROM [core].[Node] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
OFFSET 15 ROWS FETCH NEXT 16 ROWS ONLY
) AS [Limit1]
WHERE (([Limit1].[Tid] = @DynamicFilterParam_1) OR (@DynamicFilterParam_2 IS NOT NULL)) AND (([Limit1].[Tid] = @DynamicFilterParam_3) OR (@DynamicFilterParam_4 IS NOT NULL)) AND ( NOT (([Limit1].[Name] = @p__linq__0) AND (0 = (CASE WHEN (@p__linq__0 IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END))))
) AS [Project1]
ORDER BY [Project1].[Modified] ASC, [Project1].[Created] DESC, [Project1].[Id] ASC
OFFSET @p__linq__1 ROWS FETCH NEXT @p__linq__2 ROWS ONLY
2016-02-27 18:36:15,222 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__0: Root Node
2016-02-27 18:36:15,223 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__1: 0
2016-02-27 18:36:15,223 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - p__linq__2: 16
2016-02-27 18:36:15,230 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - Intercepted on: ReaderExecuted :- IsAsync: False, Command Text: SELECT
[Project1].[Id] AS [Id],
[Project1].[EntityId] AS [EntityId],
[Project1].[Parameters] AS [Parameters],
[Project1].[EntityKindId] AS [EntityKindId],
[Project1].[ParentId] AS [ParentId],
[Project1].[Tid] AS [Tid],
[Project1].[Name] AS [Name],
[Project1].[Description] AS [Description],
[Project1].[CreatedById] AS [CreatedById],
[Project1].[ModifiedById] AS [ModifiedById],
[Project1].[Created] AS [Created],
[Project1].[Modified] AS [Modified],
[Project1].[RowVersion] AS [RowVersion]
FROM ( SELECT
[Limit1].[Id] AS [Id],
[Limit1].[EntityId] AS [EntityId],
[Limit1].[Parameters] AS [Parameters],
[Limit1].[EntityKindId] AS [EntityKindId],
[Limit1].[ParentId] AS [ParentId],
[Limit1].[Tid] AS [Tid],
[Limit1].[Name] AS [Name],
[Limit1].[Description] AS [Description],
[Limit1].[CreatedById] AS [CreatedById],
[Limit1].[ModifiedById] AS [ModifiedById],
[Limit1].[Created] AS [Created],
[Limit1].[Modified] AS [Modified],
[Limit1].[RowVersion] AS [RowVersion]
FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[EntityId] AS [EntityId], [Extent1].[Parameters] AS [Parameters], [Extent1].[EntityKindId] AS [EntityKindId], [Extent1].[ParentId] AS [ParentId], [Extent1].[Tid] AS [Tid], [Extent1].[Name] AS [Name], [Extent1].[Description] AS [Description], [Extent1].[CreatedById] AS [CreatedById], [Extent1].[ModifiedById] AS [ModifiedById], [Extent1].[Created] AS [Created], [Extent1].[Modified] AS [Modified], [Extent1].[RowVersion] AS [RowVersion]
FROM [core].[Node] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
OFFSET 31 ROWS FETCH NEXT 16 ROWS ONLY
) AS [Limit1]
WHERE (([Limit1].[Tid] = @DynamicFilterParam_1) OR (@DynamicFilterParam_2 IS NOT NULL)) AND (([Limit1].[Tid] = @DynamicFilterParam_3) OR (@DynamicFilterParam_4 IS NOT NULL)) AND ( NOT (([Limit1].[Name] = @p__linq__0) AND (0 = (CASE WHEN (@p__linq__0 IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END))))
) AS [Project1]
ORDER BY [Project1].[Modified] ASC, [Project1].[Created] DESC, [Project1].[Id] ASC
OFFSET @p__linq__1 ROWS FETCH NEXT @p__linq__2 ROWS ONLY
2016-02-27 18:36:15,233 [36] INFO biz.dfch.CS.Utilities.Logging.LogBase [(null)] <(null)> - 00000000-0000-0000-5300-0080020000fb-END [200]
public static PageableEntityFilterResult<TEntity> PageableEntityFilter<TEntity>
(
this IQueryable<TEntity> queryable
,
ODataQueryOptions<TEntity> queryOptions
,
Func<TEntity, PageableEntityFilterContext, bool> filter
)
where TEntity : BaseEntity
{
Contract.Requires(null != queryable);
Contract.Requires(null != queryOptions);
Contract.Requires(null != filter);
Contract.Ensures(null != Contract.Result<PageableEntityFilterResult<TEntity>>());
// accessManager will be passed down to the filter
var accessManager = new AccessManager();
var context = new PageableEntityFilterContext();
context.AccessManager = accessManager;
// get skip count and top count if specified
// round down top count to max pageSize+1
var originalSkipCount = null != queryOptions.Skip ? queryOptions.Skip.Value : 0;
var skipCount = 0;
var topCount = null != queryOptions.Top ?
Math.Min(queryOptions.Top.Value, Constants.ODATA_ENABLEQUERY_PAGESIZE + 1) :
(Constants.ODATA_ENABLEQUERY_PAGESIZE + 1);
// entitySet holds all entities that have passed the filter expression
var entitySet = new List<TEntity>();
var orderedEntitySetCount = 0;
do
{
// get the result set with filter and order applied
// for queries with top < pageSize we end up with a bad query
// as we potentially have re-query the database too often.
// Therefore we always query pageSize+1 records from the table and
// break the loop if we either have top records -or- pageSize+1 records
IQueryable<TEntity> orderedResultSet = queryOptions.ApplyOrderedTo<TEntity>(queryable, skipCount, Constants.ODATA_ENABLEQUERY_PAGESIZE + 1);
orderedEntitySetCount = 0;
foreach (TEntity entity in orderedResultSet)
{
// count returned entities (regardless of filter expression)
orderedEntitySetCount++;
var isEntityToBeAdded = filter(entity, context);
if(isEntityToBeAdded)
{
// apply the skip count
if(originalSkipCount > 0)
{
--originalSkipCount;
}
else
{
// add entity to cache
CacheManager.Default.Add<TEntity>(entity);
// add entity to resultSet
entitySet.Add(entity);
}
}
// abort if page size (+1) is reached, +1 is necessary to determine if we need a next link
if (entitySet.Count >= Constants.ODATA_ENABLEQUERY_PAGESIZE + 1 || entitySet.Count >= topCount)
{
break;
}
}
// adjust skip count for next iteration (regardless of actually returned entities)
skipCount += orderedEntitySetCount;
}
// continue while
// we have returned entries AND
// we have not yet reached the specified topCount
while (0 < orderedEntitySetCount && topCount > entitySet.Count);
// decrease skip count as we are now off by one
--skipCount;
// return list a IQueryable
return new PageableEntityFilterResult<TEntity>()
{
EntitySet = entitySet
,
SkipCount = skipCount
};
}
public class PageableEntityFilterContext
{
public AccessManager AccessManager;
}
public class PageableEntityFilterResult<TEntity>
where TEntity : BaseEntity
{
public IList<TEntity> EntitySet;
public int SkipCount;
}
public class ODataQueryOptionsExtensionsImpl
{
public IQueryable ApplyOrderedTo<TSource>(ODataQueryOptions<TSource> oDataQueryOptions, IQueryable<TSource> entitySet, ODataQuerySettings querySettings)
where TSource : BaseEntity
{
var topCount = querySettings.PageSize.HasValue ? querySettings.PageSize.Value : 0;
var result = ApplyOrderedTo<TSource>(oDataQueryOptions, entitySet, 0, topCount);
return result;
}
public IQueryable<TSource> ApplyOrderedTo<TSource>(ODataQueryOptions<TSource> oDataQueryOptions, IQueryable<TSource> entitySet, int skipCount, int topCount)
where TSource : BaseEntity
{
Contract.Requires(null != oDataQueryOptions);
Contract.Requires(null != entitySet);
Contract.Ensures(null != Contract.Result<IQueryable>());
var originalSkipCount = null != oDataQueryOptions.Skip ? oDataQueryOptions.Skip.Value : 0;
var originalTopCount = null != oDataQueryOptions.Top ? oDataQueryOptions.Top.Value : 0;
SetSkipOption(oDataQueryOptions, 0);
SetTopOption(oDataQueryOptions, topCount);
var orderByPropertyExtractor = new ODataOrderByPropertyExtractor();
IQueryable resultSet;
if (null != oDataQueryOptions.OrderBy && null != oDataQueryOptions.OrderBy.OrderByClause)
{
IOrderedQueryable<TSource> orderedQueryable;
// get first orderby clause
var orderByClauseIndex = 0;
var orderByPropertyName = orderByPropertyExtractor.GetPropertyName(oDataQueryOptions.OrderBy.RawValue, orderByClauseIndex);
var orderByPropertyType = orderByPropertyExtractor.GetPropertyType(oDataQueryOptions.OrderBy, orderByClauseIndex);
#region entitySet.OrderBy(orderByPropertyName)
if (oDataQueryOptions.OrderBy.OrderByClause.Direction == Microsoft.Data.OData.Query.OrderByDirection.Ascending)
{
if (typeof(DateTimeOffset) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, DateTimeOffset>(orderByPropertyName);
}
else if (typeof(long) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, long>(orderByPropertyName);
}
else if (typeof(string) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, string>(orderByPropertyName);
}
else if (typeof(int) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, int>(orderByPropertyName);
}
else if (typeof(Guid) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, Guid>(orderByPropertyName);
}
else if (typeof(DateTime) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderBy<TSource, DateTime>(orderByPropertyName);
}
else
{
Contract.Assert(null != orderByPropertyType, "Unsupported orderBy type");
orderedQueryable = null;
}
}
else
{
if (typeof(DateTimeOffset) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, DateTimeOffset>(orderByPropertyName);
}
else if (typeof(long) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, long>(orderByPropertyName);
}
else if (typeof(string) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, string>(orderByPropertyName);
}
else if (typeof(int) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, int>(orderByPropertyName);
}
else if (typeof(Guid) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, Guid>(orderByPropertyName);
}
else if (typeof(DateTime) == orderByPropertyType)
{
orderedQueryable = entitySet.OrderByDescending<TSource, DateTime>(orderByPropertyName);
}
else
{
Contract.Assert(null != orderByPropertyType, "Unsupported orderBy type");
orderedQueryable = null;
}
}
#endregion
// get subsequent orderby clauses
var thenBy = oDataQueryOptions.OrderBy.OrderByClause.ThenBy;
while (null != thenBy)
{
orderByClauseIndex++;
var thenByPropertyName = orderByPropertyExtractor.GetPropertyName(oDataQueryOptions.OrderBy.RawValue, orderByClauseIndex);
var thenByPropertyType = orderByPropertyExtractor.GetPropertyType(oDataQueryOptions.OrderBy, orderByClauseIndex);
#region orderedQueryable.OrderBy(thenByPropertyName);
if (thenBy.Direction == Microsoft.Data.OData.Query.OrderByDirection.Ascending)
{
if (typeof(DateTimeOffset) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, DateTimeOffset>(thenByPropertyName);
}
else if (typeof(long) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, long>(thenByPropertyName);
}
else if (typeof(string) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, string>(thenByPropertyName);
}
else if (typeof(int) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, int>(thenByPropertyName);
}
else if (typeof(Guid) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, Guid>(thenByPropertyName);
}
else if (typeof(DateTime) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderBy<TSource, DateTime>(thenByPropertyName);
}
else
{
Contract.Assert(null != thenByPropertyName, "Unsupported orderBy type");
orderedQueryable = null;
}
}
else
{
if (typeof(DateTimeOffset) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, DateTimeOffset>(thenByPropertyName);
}
else if (typeof(long) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, long>(thenByPropertyName);
}
else if (typeof(string) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, string>(thenByPropertyName);
}
else if (typeof(int) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, int>(thenByPropertyName);
}
else if (typeof(Guid) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, Guid>(thenByPropertyName);
}
else if (typeof(DateTime) == thenByPropertyType)
{
orderedQueryable = orderedQueryable.OrderByDescending<TSource, DateTime>(thenByPropertyName);
}
else
{
Contract.Assert(null != thenByPropertyName, "Unsupported orderBy type");
orderedQueryable = null;
}
}
#endregion
// get next orderby clause
thenBy = thenBy.ThenBy;
}
// execute ordered query
resultSet = oDataQueryOptions.ApplyTo
(
orderedQueryable
.Skip(skipCount)
.Take(topCount)
);
}
else
{
// append default orderby and execute query
resultSet = oDataQueryOptions.ApplyTo
(entitySet
.OrderBy(e => e.Id)
.Skip(skipCount)
.Take(topCount)
);
}
SetSkipOption(oDataQueryOptions, originalSkipCount);
SetTopOption(oDataQueryOptions, originalTopCount);
return resultSet.Cast<TSource>();
}
public bool SetSkipOption<TSource>(ODataQueryOptions<TSource> oDataQueryOptions, int value)
where TSource : BaseEntity
{
Contract.Requires(null != oDataQueryOptions);
Contract.Requires(0 <= value);
if (null == oDataQueryOptions.Skip)
{
return false;
}
if(oDataQueryOptions.Skip.Value == value)
{
return true;
}
var fieldInfoRawValue = GetBackingFieldInfo(oDataQueryOptions.Skip, "RawValue");
fieldInfoRawValue.SetValue(oDataQueryOptions.Skip, value.ToString());
var runtimeFieldInfoValue = oDataQueryOptions.Skip.GetType().GetRuntimeFields().FirstOrDefault(e => e.Name == "_value");
runtimeFieldInfoValue.SetValue(oDataQueryOptions.Skip, value);
Contract.Assert(oDataQueryOptions.Skip.Value == value);
Contract.Assert(oDataQueryOptions.Skip.RawValue == value.ToString());
return true;
}
public bool SetTopOption<TSource>(ODataQueryOptions<TSource> oDataQueryOptions, int value)
where TSource : BaseEntity
{
Contract.Requires(null != oDataQueryOptions);
Contract.Requires(0 <= value);
if (null == oDataQueryOptions.Top)
{
return false;
}
if (oDataQueryOptions.Top.Value == value)
{
return true;
}
var fieldInfoRawValue = GetBackingFieldInfo(oDataQueryOptions.Top, "RawValue");
fieldInfoRawValue.SetValue(oDataQueryOptions.Top, value.ToString());
var runtimeFieldInfoValue = oDataQueryOptions.Top.GetType().GetRuntimeFields().FirstOrDefault(e => e.Name == "_value");
runtimeFieldInfoValue.SetValue(oDataQueryOptions.Top, value);
Contract.Assert(oDataQueryOptions.Top.Value == value);
Contract.Assert(oDataQueryOptions.Top.RawValue == value.ToString());
return true;
}
public int GetTakeCount<TSource>(ODataQueryOptions<TSource> queryOptions, int pageSize = Constants.ODATA_ENABLEQUERY_PAGESIZE)
where TSource : BaseEntity
{
Contract.Requires(null != queryOptions);
var count = null != queryOptions.Top ? Math.Min(queryOptions.Top.Value, pageSize) : pageSize;
return ++count;
}
private string GetBackingFieldName(string propertyName)
{
Contract.Ensures(!string.IsNullOrWhiteSpace(Contract.Result<string>()));
var result = string.Format("<{0}>k__BackingField", propertyName);
return result;
}
private FieldInfo GetBackingFieldInfo(object obj, string propertyName)
{
Contract.Ensures(null != Contract.Result<FieldInfo>());
var fieldName = GetBackingFieldName(propertyName);
var type = obj.GetType();
var backingFieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
return backingFieldInfo;
}
}
public class ODataControllerExtensionsImpl
{
public Uri SetNextLink<TEntity>(ODataController controller, IList<TEntity> entitySet, int pageSize, int skipCount)
where TEntity : BaseEntity
{
Contract.Requires(null != controller);
Contract.Requires(null != entitySet);
Contract.Requires(0 < pageSize);
var oDataProperties = controller.Request.ODataProperties();
if(0 >= entitySet.Count)
{
return oDataProperties.NextLink;
}
if (pageSize >= entitySet.Count)
{
return oDataProperties.NextLink;
}
Contract.Assert(0 <= skipCount);
// DFTODO - currently we have an issue when the resultSet.Count is 1 AND the pagesoze is 1
Contract.Assert(1 <= entitySet.Count);
entitySet.RemoveAt(entitySet.Count - 1);
var absoluteUri = controller.Request.RequestUri.AbsoluteUri;
var nextLinkBase = Regex.Replace(absoluteUri, "\\$skip=\\d+", "").TrimEnd('&');
var nextLinkUri = string.Format("{0}&$skip={1}", nextLinkBase, skipCount).Replace("&&", "&");
oDataProperties.NextLink = new Uri(nextLinkUri);
return oDataProperties.NextLink;
}
}
/**
* Copyright 2014-2016 d-fens GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment