Skip to content

Commit 4555f6f

Browse files
flattening array contains
1 parent 3ffe75a commit 4555f6f

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Magic.IndexedDb.Helpers;
22
using Magic.IndexedDb.Models.UniversalOperations;
33
using System;
4+
using System.Collections;
45
using System.Collections.Generic;
56
using System.Linq;
67
using System.Linq.Expressions;
@@ -137,6 +138,18 @@ void AddBinary(BinaryExpression bin, bool isInOr)
137138

138139
void AddMethodCall(MethodCallExpression call, bool isInOr)
139140
{
141+
// Flatten value-array.Contains(x.Property) or x.Collection.Contains(value)
142+
if (TryFlattenContains(call, isInOr, out var flattened))
143+
{
144+
foreach (var cond in flattened)
145+
{
146+
var individualGroup = new AndFilterGroup();
147+
individualGroup.conditions.Add(cond);
148+
orGroup.andGroups.Add(individualGroup); // Treat them as parallel OR options
149+
}
150+
return;
151+
}
152+
140153
string name = call.Method.Name;
141154
string resolvedOp = name switch
142155
{
@@ -207,6 +220,83 @@ void AddConditionInternal(MemberExpression? left, ConstantExpression? right, str
207220
return orGroup;
208221
}
209222

223+
224+
private bool TryFlattenContains(MethodCallExpression call, bool isOr, out IEnumerable<FilterCondition> flattened)
225+
{
226+
flattened = Enumerable.Empty<FilterCondition>();
227+
228+
if (call.Method.Name != "Contains")
229+
return false;
230+
231+
// Case 1: Static-style — myArray.Contains(x._Age)
232+
if (call.Object == null && call.Arguments.Count == 2)
233+
{
234+
var maybeCollection = call.Arguments[0];
235+
var maybeProp = call.Arguments[1];
236+
237+
if ((maybeCollection is MemberExpression || maybeCollection is ConstantExpression) &&
238+
maybeProp is MemberExpression propExpr &&
239+
IsParamMember(propExpr))
240+
{
241+
var collectionExpr = maybeCollection;
242+
var collection = Expression.Lambda(collectionExpr).Compile().DynamicInvoke();
243+
244+
if (collection is IEnumerable enumerable)
245+
{
246+
var propInfo = typeof(T).GetProperty(propExpr.Member.Name);
247+
if (propInfo == null)
248+
return false;
249+
250+
string jsProp = PropertyMappingCache.GetJsPropertyName<T>(propInfo);
251+
252+
flattened = enumerable
253+
.Cast<object?>()
254+
.Select(val =>
255+
new FilterCondition(
256+
jsProp,
257+
"Equal",
258+
val != null ? JsonValue.Create(val) : null,
259+
val is string,
260+
false
261+
)
262+
);
263+
264+
return true;
265+
}
266+
}
267+
}
268+
269+
// Case 2: Instance-style — x.CollectionProperty.Contains(10)
270+
if (call.Object is MemberExpression collectionMember &&
271+
call.Arguments.Count == 1 &&
272+
call.Arguments[0] is ConstantExpression constant)
273+
{
274+
var propInfo = typeof(T).GetProperty(collectionMember.Member.Name);
275+
if (propInfo == null) return false;
276+
277+
string jsProp = PropertyMappingCache.GetJsPropertyName<T>(propInfo);
278+
279+
flattened = new[]
280+
{
281+
new FilterCondition(
282+
jsProp,
283+
"ArrayContains",
284+
JsonValue.Create(constant.Value),
285+
constant.Value is string,
286+
false
287+
)
288+
};
289+
290+
return true;
291+
}
292+
293+
return false;
294+
}
295+
296+
297+
298+
299+
210300
private static bool SupportedMethodNameForNegation(string name) =>
211301
name is "Contains" or "StartsWith" or "EndsWith" or "Equals";
212302

@@ -241,6 +331,21 @@ private static bool ExtractCaseSensitivity(MethodCallExpression call)
241331
"LessThanOrEqual" => "GreaterThanOrEqual",
242332
_ => op
243333
};
334+
335+
private static ConstantExpression ToConst(Expression expr) => expr switch
336+
{
337+
ConstantExpression c => c,
338+
MemberExpression m => Expression.Constant(Expression.Lambda(m).Compile().DynamicInvoke()),
339+
_ => throw new InvalidOperationException($"Unsupported or non-constant: {expr}")
340+
};
341+
342+
private static bool IsParamMember(Expression expr)
343+
{
344+
return expr is MemberExpression member &&
345+
member.Expression is ParameterExpression;
346+
}
347+
348+
244349
}
245350
// Your supporting model classes (OrFilterGroup, AndFilterGroup, FilterCondition, etc.)
246351
// should stay as they are in their own files or namespaces.

MagicIndexDbWiki/Getting-Started-Blazor/P1-Introduction.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ await personQuery.Where(x => (x.Age > 40 && x.TestInt == 9) || x.Name.Contains("
360360

361361
- **IndexedDB does not natively support this!**
362362
- **Magic IndexedDBs flattening algorithm makes it possible**.
363+
- Though it's highly advised to limit your nested || conditions. And when you are utilizing such nested conditions, it's best to utilize as many indexed queries as possible.
363364

364365
---
365366

@@ -520,6 +521,8 @@ The following **table defines the operations available** and how they interact.
520521
| `x == null` | Field is `null` or `undefined` | 🚫 Cursor Required |
521522
| `x != null` | Field is **not** `null` or `undefined` | 🚫 Cursor Required |
522523

524+
* **Contains for non strings** - when working with arrays the system tries to flatten the predicate to the universal language on your behalf.
525+
523526
---
524527

525528
### **🕒 Date & Time Query Support (C# Style)**

0 commit comments

Comments
 (0)