Skip to content

Commit 0c0ac68

Browse files
CecilyKCecily King'ori
andauthored
Using PredicateBuilder class for querying Users and Groups (#74)
* Using PredicateBuilder for querying Users and Groups * Update InMemoryUserProvider.cs Co-authored-by: Cecily King'ori <[email protected]>
1 parent 673364f commit 0c0ac68

File tree

3 files changed

+92
-88
lines changed

3 files changed

+92
-88
lines changed

Microsoft.SCIM.WebHostSample/Provider/InMemoryGroupProvider.cs

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Microsoft.SCIM.WebHostSample.Provider
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Linq.Expressions;
89
using System.Net;
910
using System.Threading.Tasks;
1011
using System.Web.Http;
@@ -90,12 +91,17 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
9091
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
9192
}
9293

93-
Resource[] results;
94+
IEnumerable<Resource> results;
9495
IFilter queryFilter = parameters.AlternateFilters.SingleOrDefault();
95-
IEnumerable<Core2Group> buffer = Enumerable.Empty<Core2Group>();
96+
97+
var predicate = PredicateBuilder.False<Core2Group>();
98+
Expression<Func<Core2Group, bool>> predicateAnd;
99+
predicateAnd = PredicateBuilder.True<Core2Group>();
100+
96101
if (queryFilter == null)
97102
{
98-
buffer = this.storage.Groups.Values;
103+
results = this.storage.Groups.Values.Select(
104+
(Core2Group user) => user as Resource);
99105
}
100106
else
101107
{
@@ -114,51 +120,24 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
114120
throw new NotSupportedException(string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, queryFilter.FilterOperator));
115121
}
116122

123+
117124
if (queryFilter.AttributePath.Equals(AttributeNames.DisplayName))
118125
{
119-
buffer =
120-
this.storage.Groups.Values
121-
.Where(
122-
(Core2Group item) =>
123-
string.Equals(
124-
item.DisplayName,
125-
parameters.AlternateFilters.Single().ComparisonValue,
126-
StringComparison.OrdinalIgnoreCase));
126+
127+
string displayName = queryFilter.ComparisonValue;
128+
predicateAnd = predicateAnd.And(p => string.Equals(p.DisplayName, displayName, StringComparison.OrdinalIgnoreCase));
129+
127130
}
128131
else
129132
{
130133
throw new NotSupportedException(string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterAttributePathNotSupportedTemplate, queryFilter.AttributePath));
131134
}
132135
}
136+
137+
predicate = predicate.Or(predicateAnd);
138+
results = this.storage.Groups.Values.Where(predicate.Compile());
133139

134-
results =
135-
buffer
136-
.Select((Core2Group item) =>
137-
{
138-
Core2Group bufferItem =
139-
new Core2Group
140-
{
141-
DisplayName = item.DisplayName,
142-
ExternalIdentifier = item.ExternalIdentifier,
143-
Identifier = item.Identifier,
144-
Members = item.Members,
145-
Metadata = item.Metadata
146-
};
147-
148-
if (parameters?.ExcludedAttributePaths?.Any(
149-
(string excludedAttributes) =>
150-
excludedAttributes.Equals(AttributeNames.Members, StringComparison.OrdinalIgnoreCase))
151-
== true)
152-
{
153-
bufferItem.Members = null;
154-
}
155-
156-
return bufferItem;
157-
})
158-
.Select((Core2Group item) => item as Resource)
159-
.ToArray();
160-
161-
return Task.FromResult(results);
140+
return Task.FromResult(results.ToArray());
162141
}
163142

164143
public override Task<Resource> ReplaceAsync(Resource resource, string correlationIdentifier)

Microsoft.SCIM.WebHostSample/Provider/InMemoryStorage.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace Microsoft.SCIM.WebHostSample.Provider
44
{
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Linq.Expressions;
79
using Microsoft.SCIM;
810

911
public class InMemoryStorage
@@ -24,4 +26,43 @@ private InMemoryStorage()
2426

2527
public static InMemoryStorage Instance => InMemoryStorage.InstanceValue.Value;
2628
}
29+
30+
/// <summary>
31+
/// Enables the efficient, dynamic composition of query predicates.
32+
/// Source: C# 9.0 in a Nutshell http://www.albahari.com/nutshell/predicatebuilder.aspx
33+
/// </summary>
34+
public static class PredicateBuilder
35+
{
36+
/// <summary>
37+
/// Creates a predicate that evaluates to true.
38+
/// </summary>
39+
public static Expression<Func<T, bool>> True<T>() { return f => true; }
40+
/// <summary>
41+
/// Creates a predicate that evaluates to false.
42+
/// </summary>
43+
public static Expression<Func<T, bool>> False<T>() { return f => false; }
44+
45+
/// <summary>
46+
/// Combines the first predicate with the second using the logical "or".
47+
/// </summary>
48+
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
49+
Expression<Func<T, bool>> expr2)
50+
{
51+
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
52+
return Expression.Lambda<Func<T, bool>>
53+
(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
54+
}
55+
56+
/// <summary>
57+
/// Combines the first predicate with the second using the logical "and".
58+
/// </summary>
59+
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
60+
Expression<Func<T, bool>> expr2)
61+
{
62+
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
63+
return Expression.Lambda<Func<T, bool>>
64+
(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
65+
}
66+
}
67+
2768
}

Microsoft.SCIM.WebHostSample/Provider/InMemoryUserProvider.cs

Lines changed: 33 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Microsoft.SCIM.WebHostSample.Provider
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Linq.Expressions;
89
using System.Net;
910
using System.Threading.Tasks;
1011
using System.Web.Http;
@@ -94,7 +95,10 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
9495
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
9596
}
9697

97-
IEnumerable<Resource> results = new List<Core2EnterpriseUser>();
98+
IEnumerable<Resource> results;
99+
var predicate = PredicateBuilder.False<Core2EnterpriseUser>();
100+
Expression<Func<Core2EnterpriseUser, bool>> predicateAnd;
101+
98102

99103
if (parameters.AlternateFilters.Count <= 0)
100104
{
@@ -103,11 +107,10 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
103107
}
104108
else
105109
{
106-
results = new List<Core2EnterpriseUser>();
107110

108111
foreach (IFilter queryFilter in parameters.AlternateFilters)
109112
{
110-
IEnumerable<Core2EnterpriseUser> users = this.storage.Users.Values;
113+
predicateAnd = PredicateBuilder.True<Core2EnterpriseUser>();
111114

112115
IFilter andFilter = queryFilter;
113116
IFilter currentFilter = andFilter;
@@ -132,13 +135,10 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
132135
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
133136
}
134137

135-
users =
136-
users.Where(
137-
item =>
138-
string.Equals(
139-
item.UserName,
140-
andFilter.ComparisonValue,
141-
StringComparison.OrdinalIgnoreCase));
138+
string userName = andFilter.ComparisonValue;
139+
predicateAnd = predicateAnd.And(p => string.Equals(p.UserName, userName, StringComparison.OrdinalIgnoreCase));
140+
141+
142142
}
143143

144144
// ExternalId filter
@@ -150,34 +150,13 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
150150
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
151151
}
152152

153-
users =
154-
users.Where(
155-
item =>
156-
string.Equals(
157-
item.ExternalIdentifier,
158-
andFilter.ComparisonValue,
159-
StringComparison.OrdinalIgnoreCase)).ToList();
160-
}
161-
162-
// Id filter
163-
else if (andFilter.AttributePath.Equals(AttributeNames.Identifier, StringComparison.OrdinalIgnoreCase))
164-
{
165-
if (andFilter.FilterOperator != ComparisonOperator.Equals)
166-
{
167-
throw new NotSupportedException(
168-
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
169-
}
153+
string externalIdentifier = andFilter.ComparisonValue;
154+
predicateAnd = predicateAnd.And(p => string.Equals(p.ExternalIdentifier, externalIdentifier, StringComparison.OrdinalIgnoreCase));
170155

171-
users =
172-
users.Where(
173-
item =>
174-
string.Equals(
175-
item.Identifier,
176-
andFilter.ComparisonValue,
177-
StringComparison.OrdinalIgnoreCase)).ToList();
156+
178157
}
179158

180-
// Active filter
159+
//Active Filter
181160
else if (andFilter.AttributePath.Equals(AttributeNames.Active, StringComparison.OrdinalIgnoreCase))
182161
{
183162
if (andFilter.FilterOperator != ComparisonOperator.Equals)
@@ -186,32 +165,34 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
186165
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
187166
}
188167

189-
users =
190-
users.Where(
191-
item =>
192-
item.Active == bool.Parse(andFilter.ComparisonValue)).ToList();
168+
bool active = bool.Parse(andFilter.ComparisonValue);
169+
predicateAnd = predicateAnd.And(p => p.Active == active);
170+
193171
}
194172

195-
// LastModified filter
173+
//LastModified filter
196174
else if (andFilter.AttributePath.Equals($"{AttributeNames.Metadata}.{AttributeNames.LastModified}", StringComparison.OrdinalIgnoreCase))
197175
{
198176
if (andFilter.FilterOperator == ComparisonOperator.EqualOrGreaterThan)
199177
{
200-
users =
201-
users.Where(
202-
item =>
203-
item.Metadata.LastModified >= DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime()).ToList();
178+
DateTime comparisonValue = DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime();
179+
predicateAnd = predicateAnd.And(p => p.Metadata.LastModified >= comparisonValue);
180+
181+
204182
}
205183
else if (andFilter.FilterOperator == ComparisonOperator.EqualOrLessThan)
206184
{
207-
users =
208-
users.Where(
209-
item =>
210-
item.Metadata.LastModified <= DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime()).ToList();
185+
DateTime comparisonValue = DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime();
186+
predicateAnd = predicateAnd.And(p => p.Metadata.LastModified <= comparisonValue);
187+
188+
211189
}
212190
else
213191
throw new NotSupportedException(
214192
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
193+
194+
195+
215196
}
216197
else
217198
throw new NotSupportedException(
@@ -222,8 +203,11 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
222203

223204
} while (currentFilter.AdditionalFilter != null);
224205

225-
results = results.Union(users.Select((Core2EnterpriseUser user) => user as Resource).ToList());
206+
predicate = predicate.Or(predicateAnd);
207+
226208
}
209+
210+
results = this.storage.Users.Values.Where(predicate.Compile());
227211
}
228212

229213
if (parameters.PaginationParameters != null)

0 commit comments

Comments
 (0)