diff --git a/Realtime/Realtime/Conversation/LCIMConversationQuery.cs b/Realtime/Realtime/Conversation/LCIMConversationQuery.cs index 5904ab1..700ba8d 100644 --- a/Realtime/Realtime/Conversation/LCIMConversationQuery.cs +++ b/Realtime/Realtime/Conversation/LCIMConversationQuery.cs @@ -184,7 +184,7 @@ namespace LeanCloud.Realtime { /// /// public LCIMConversationQuery OrderBy(string key) { - Condition.OrderBy(key); + Condition.OrderByAscending(key); return this; } diff --git a/Storage/Storage.Test/QueryTest.cs b/Storage/Storage.Test/QueryTest.cs index 8bd009a..27d3a89 100644 --- a/Storage/Storage.Test/QueryTest.cs +++ b/Storage/Storage.Test/QueryTest.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using System.Collections.ObjectModel; using System.Collections.Generic; using System.Threading.Tasks; using LeanCloud; @@ -21,7 +22,7 @@ namespace Storage.Test { public async Task BaseQuery() { LCQuery query = new LCQuery("Hello"); query.Limit(2); - List list = await query.Find(); + ReadOnlyCollection list = await query.Find(); TestContext.WriteLine(list.Count); Assert.AreEqual(list.Count, 2); @@ -53,8 +54,8 @@ namespace Storage.Test { [Test] public async Task OrderBy() { LCQuery query = new LCQuery("Account"); - query.OrderBy("balance"); - List results = await query.Find(); + query.OrderByAscending("balance"); + ReadOnlyCollection results = await query.Find(); Assert.LessOrEqual((int)results[0]["balance"], (int)results[1]["balance"]); query = new LCQuery("Account"); @@ -63,6 +64,22 @@ namespace Storage.Test { Assert.GreaterOrEqual((int)results[0]["balance"], (int)results[1]["balance"]); } + [Test] + public async Task AddOrder() { + LCQuery query = new LCQuery("Account"); + query.AddAscendingOrder("balance"); + query.AddDescendingOrder("createdAt"); + ReadOnlyCollection results = await query.Find(); + for (int i = 0; i + 1 < results.Count; i++) { + LCObject a1 = results[i]; + LCObject a2 = results[i + 1]; + int b1 = (int)a1["balance"]; + int b2 = (int)a2["balance"]; + Assert.IsTrue(b1 < b2 || + a1.CreatedAt.CompareTo(a2.CreatedAt) >= 0); + } + } + [Test] public async Task Include() { LCQuery query = new LCQuery("Hello"); @@ -91,7 +108,7 @@ namespace Storage.Test { public async Task GreaterQuery() { LCQuery query = new LCQuery("Account"); query.WhereGreaterThan("balance", 200); - List list = await query.Find(); + ReadOnlyCollection list = await query.Find(); TestContext.WriteLine(list.Count); Assert.Greater(list.Count, 0); } @@ -103,12 +120,12 @@ namespace Storage.Test { LCQuery q2 = new LCQuery("Account"); q2.WhereLessThan("balance", 500); LCQuery query = LCQuery.And(new List> { q1, q2 }); - List results = await query.Find(); + ReadOnlyCollection results = await query.Find(); TestContext.WriteLine(results.Count); - results.ForEach(item => { + foreach (LCObject item in results) { int balance = (int)item["balance"]; Assert.IsTrue(balance >= 100 || balance <= 500); - }); + } } [Test] @@ -118,12 +135,12 @@ namespace Storage.Test { LCQuery q2 = new LCQuery("Account"); q2.WhereGreaterThanOrEqualTo("balance", 500); LCQuery query = LCQuery.Or(new List> { q1, q2 }); - List results = await query.Find(); + ReadOnlyCollection results = await query.Find(); TestContext.WriteLine(results.Count); - results.ForEach(item => { + foreach (LCObject item in results) { int balance = (int)item["balance"]; Assert.IsTrue(balance <= 100 || balance >= 500); - }); + } } [Test] @@ -141,28 +158,28 @@ namespace Storage.Test { public async Task Exist() { LCQuery query = new LCQuery("Account"); query.WhereExists("user"); - List results = await query.Find(); - results.ForEach(item => { + ReadOnlyCollection results = await query.Find(); + foreach (LCObject item in results) { Assert.NotNull(item["user"]); - }); + } query = new LCQuery("Account"); query.WhereDoesNotExist("user"); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { Assert.IsNull(item["user"]); - }); + } } [Test] public async Task Select() { LCQuery query = new LCQuery("Account"); query.Select("balance"); - List results = await query.Find(); - results.ForEach(item => { + ReadOnlyCollection results = await query.Find(); + foreach (LCObject item in results) { Assert.NotNull(item["balance"]); Assert.IsNull(item["user"]); - }); + } } [Test] @@ -170,29 +187,29 @@ namespace Storage.Test { // Start LCQuery query = new LCQuery("Hello"); query.WhereStartsWith("stringValue", "hello"); - List results = await query.Find(); - results.ForEach(item => { + ReadOnlyCollection results = await query.Find(); + foreach (LCObject item in results) { string str = item["stringValue"] as string; Assert.IsTrue(str.StartsWith("hello")); - }); + } // End query = new LCQuery("Hello"); query.WhereEndsWith("stringValue", "world"); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { string str = item["stringValue"] as string; Assert.IsTrue(str.EndsWith("world")); - }); + } // Contains query = new LCQuery("Hello"); query.WhereContains("stringValue", ","); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { string str = item["stringValue"] as string; Assert.IsTrue(str.Contains(',')); - }); + } } [Test] @@ -200,46 +217,60 @@ namespace Storage.Test { // equal LCQuery query = new LCQuery("Book"); query.WhereEqualTo("pages", 3); - Listresults = await query.Find(); - results.ForEach(item => { + ReadOnlyCollectionresults = await query.Find(); + foreach (LCObject item in results) { List pages = item["pages"] as List; Assert.IsTrue(pages.Contains(3)); - }); + } // contain all List containAlls = new List { 1, 2, 3, 4, 5, 6, 7 }; query = new LCQuery("Book"); query.WhereContainsAll("pages", containAlls); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { List pages = item["pages"] as List; pages.ForEach(i => { Assert.IsTrue(pages.Contains(i)); }); - }); + } // contain in List containIns = new List { 4, 5, 6 }; query = new LCQuery("Book"); query.WhereContainedIn("pages", containIns); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { List pages = item["pages"] as List; bool f = false; containIns.ForEach(i => { f |= pages.Contains(i); }); Assert.IsTrue(f); - }); + } + + // not contain in + List notContainIns = new List { 1, 2, 3 }; + query = new LCQuery("Book"); + query.WhereNotContainedIn("pages", notContainIns); + results = await query.Find(); + foreach (LCObject item in results) { + List pages = item["pages"] as List; + bool f = true; + notContainIns.ForEach(i => { + f &= !pages.Contains(i); + }); + Assert.IsTrue(f); + } // size query = new LCQuery("Book"); query.WhereSizeEqualTo("pages", 7); results = await query.Find(); - results.ForEach(item => { + foreach (LCObject item in results) { List pages = item["pages"] as List; Assert.AreEqual(pages.Count, 7); - }); + } } [Test] @@ -253,7 +284,7 @@ namespace Storage.Test { LCQuery query = new LCQuery("Todo"); LCGeoPoint point = new LCGeoPoint(39.91, 116.41); query.WhereNear("location", point); - List results = await query.Find(); + ReadOnlyCollection results = await query.Find(); Assert.Greater(results.Count, 0); // in box @@ -264,5 +295,47 @@ namespace Storage.Test { results = await query.Find(); Assert.Greater(results.Count, 0); } + + [Test] + public async Task Regex() { + LCQuery query = new LCQuery("Hello"); + query.WhereMatches("stringValue", "^HEllo.*", modifiers: "i"); + ReadOnlyCollection results = await query.Find(); + Assert.Greater(results.Count, 0); + foreach (LCObject item in results) { + string str = item["stringValue"] as string; + Assert.IsTrue(str.StartsWith("hello")); + } + } + + [Test] + public async Task InQuery() { + LCQuery worldQuery = new LCQuery("World"); + worldQuery.WhereEqualTo("content", "7788"); + LCQuery helloQuery = new LCQuery("Hello"); + helloQuery.WhereMatchesQuery("objectValue", worldQuery); + helloQuery.Include("objectValue"); + ReadOnlyCollection hellos = await helloQuery.Find(); + Assert.Greater(hellos.Count, 0); + foreach (LCObject item in hellos) { + LCObject world = item["objectValue"] as LCObject; + Assert.AreEqual(world["content"], "7788"); + } + } + + [Test] + public async Task NotInQuery() { + LCQuery worldQuery = new LCQuery("World"); + worldQuery.WhereEqualTo("content", "7788"); + LCQuery helloQuery = new LCQuery("Hello"); + helloQuery.WhereDoesNotMatchQuery("objectValue", worldQuery); + helloQuery.Include("objectValue"); + ReadOnlyCollection hellos = await helloQuery.Find(); + Assert.Greater(hellos.Count, 0); + foreach (LCObject item in hellos) { + LCObject world = item["objectValue"] as LCObject; + Assert.IsTrue(world == null || world["content"] != "7788"); + } + } } } diff --git a/Storage/Storage.Test/RelationTest.cs b/Storage/Storage.Test/RelationTest.cs index 5ba3813..748bbcf 100644 --- a/Storage/Storage.Test/RelationTest.cs +++ b/Storage/Storage.Test/RelationTest.cs @@ -1,6 +1,6 @@ using NUnit.Framework; using System.Threading.Tasks; -using System.Collections.Generic; +using System.Collections.ObjectModel; using LeanCloud; using LeanCloud.Storage; @@ -56,11 +56,11 @@ namespace Storage.Test { Assert.NotNull(relation.TargetClass); LCQuery relationQuery = relation.Query; - List list = await relationQuery.Find(); - list.ForEach(item => { + ReadOnlyCollection results = await relationQuery.Find(); + foreach (LCObject item in results) { TestContext.WriteLine(item.ObjectId); Assert.NotNull(item.ObjectId); - }); + } } } } diff --git a/Storage/Storage.Test/RoleTest.cs b/Storage/Storage.Test/RoleTest.cs index 51460af..2a6b119 100644 --- a/Storage/Storage.Test/RoleTest.cs +++ b/Storage/Storage.Test/RoleTest.cs @@ -1,6 +1,6 @@ using NUnit.Framework; using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading.Tasks; using LeanCloud; using LeanCloud.Storage; @@ -34,8 +34,8 @@ namespace Storage.Test { [Test] public async Task Query() { LCQuery query = LCRole.GetQuery(); - List list = await query.Find(); - list.ForEach(item => { + ReadOnlyCollection results = await query.Find(); + foreach (LCRole item in results) { TestContext.WriteLine($"{item.ObjectId} : {item.Name}"); Assert.NotNull(item.ObjectId); Assert.NotNull(item.Name); @@ -43,7 +43,7 @@ namespace Storage.Test { TestContext.WriteLine(item.Users.GetType()); Assert.IsTrue(item.Roles is LCRelation); Assert.IsTrue(item.Users is LCRelation); - }); + } } } } diff --git a/Storage/Storage.Test/SubClassTest.cs b/Storage/Storage.Test/SubClassTest.cs index fb599af..8c67142 100644 --- a/Storage/Storage.Test/SubClassTest.cs +++ b/Storage/Storage.Test/SubClassTest.cs @@ -1,6 +1,6 @@ using NUnit.Framework; using System.Threading.Tasks; -using System.Collections.Generic; +using System.Collections.ObjectModel; using LeanCloud; using LeanCloud.Storage; @@ -63,7 +63,7 @@ namespace Storage.Test { LCObject.RegisterSubclass("Account", () => new Account()); LCQuery query = new LCQuery("Account"); query.WhereGreaterThan("balance", 500); - List list = await query.Find(); + ReadOnlyCollection list = await query.Find(); TestContext.WriteLine(list.Count); Assert.Greater(list.Count, 0); foreach (Account account in list) { diff --git a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs index 74a8039..54098e3 100644 --- a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs +++ b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs @@ -44,7 +44,7 @@ namespace LeanCloud.Storage.Internal.Query { } public void WhereNotContainedIn(string key, IEnumerable values) { - AddOperation(key, "nin", values); + AddOperation(key, "$nin", values); } public void WhereContainsAll(string key, IEnumerable values) { @@ -106,6 +106,32 @@ namespace LeanCloud.Storage.Internal.Query { AddOperation(key, "$regex", $".*{subString}.*"); } + public void WhereMatches(string key, string regex, string modifiers) { + Dictionary value = new Dictionary { + { "$regex", regex } + }; + if (modifiers != null) { + value["$options"] = modifiers; + } + Add(new LCEqualCondition(key, value)); + } + + public void WhereMatchesQuery(string key, LCQuery query) where K : LCObject { + Dictionary inQuery = new Dictionary { + { "where", query.condition }, + { "className", query.ClassName } + }; + AddOperation(key, "$inQuery", inQuery); + } + + public void WhereDoesNotMatchQuery(string key, LCQuery query) where K : LCObject { + Dictionary inQuery = new Dictionary { + { "where", query.condition }, + { "className", query.ClassName } + }; + AddOperation(key, "$notInQuery", inQuery); + } + void AddOperation(string key, string op, object value) { LCOperationCondition cond = new LCOperationCondition(key, op, value); Add(cond); @@ -123,15 +149,24 @@ namespace LeanCloud.Storage.Internal.Query { } // 筛选条件 - public void OrderBy(string key) { + public void OrderByAscending(string key) { + orderByList = new List(); + orderByList.Add(key); + } + + public void OrderByDescending(string key) { + OrderByAscending($"-{key}"); + } + + public void AddAscendingOrder(string key) { if (orderByList == null) { orderByList = new List(); } orderByList.Add(key); } - public void OrderByDescending(string key) { - OrderBy($"-{key}"); + public void AddDescendingOrder(string key) { + AddAscendingOrder($"-{key}"); } public void Include(string key) { diff --git a/Storage/Storage/LCQuery.cs b/Storage/Storage/LCQuery.cs index 78ae9df..f86d203 100644 --- a/Storage/Storage/LCQuery.cs +++ b/Storage/Storage/LCQuery.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading.Tasks; using LeanCloud.Storage.Internal.Query; using LeanCloud.Storage.Internal.Object; @@ -16,7 +17,7 @@ namespace LeanCloud.Storage { get; private set; } - LCCompositionalCondition condition; + internal LCCompositionalCondition condition; public LCQuery(string className) { ClassName = className; @@ -29,7 +30,7 @@ namespace LeanCloud.Storage { /// /// /// - public LCQuery WhereEqualTo(string key, object value) { + public LCQuery WhereEqualTo(string key, object value) { condition.WhereEqualTo(key, value); return this; } @@ -56,6 +57,17 @@ namespace LeanCloud.Storage { return this; } + /// + /// 不包含 + /// + /// + /// + /// + public LCQuery WhereNotContainedIn(string key, IEnumerable values) { + condition.WhereNotContainedIn(key, values); + return this; + } + /// /// 包含全部 /// @@ -209,13 +221,48 @@ namespace LeanCloud.Storage { return this; } + /// + /// 正则匹配 + /// + /// + /// + /// + /// + public LCQuery WhereMatches(string key, string regex, string modifiers = null) { + condition.WhereMatches(key, regex, modifiers); + return this; + } + + /// + /// 关系查询 + /// + /// + /// + /// + public LCQuery WhereMatchesQuery(string key, LCQuery query) where K : LCObject { + condition.WhereMatchesQuery(key, query); + return this; + } + + /// + /// 不满足子查询 + /// + /// + /// + /// + /// + public LCQuery WhereDoesNotMatchQuery(string key, LCQuery query) where K : LCObject { + condition.WhereDoesNotMatchQuery(key, query); + return this; + } + /// /// 按 key 升序 /// /// /// - public LCQuery OrderBy(string key) { - condition.OrderBy(key); + public LCQuery OrderByAscending(string key) { + condition.OrderByAscending(key); return this; } @@ -229,6 +276,26 @@ namespace LeanCloud.Storage { return this; } + /// + /// 增加按 key 升序 + /// + /// + /// + public LCQuery AddAscendingOrder(string key) { + condition.AddAscendingOrder(key); + return this; + } + + /// + /// 增加按 key 降序 + /// + /// + /// + public LCQuery AddDescendingOrder(string key) { + condition.AddDescendingOrder(key); + return this; + } + /// /// 拉取 key 的完整对象 /// @@ -284,7 +351,7 @@ namespace LeanCloud.Storage { } WhereEqualTo("objectId", objectId); Limit(1); - List results = await Find(); + ReadOnlyCollection results = await Find(); if (results != null) { if (results.Count == 0) { return null; @@ -294,7 +361,7 @@ namespace LeanCloud.Storage { return null; } - public async Task> Find() { + public async Task> Find() { string path = $"classes/{ClassName}"; Dictionary parameters = BuildParams(); Dictionary response = await LCApplication.HttpClient.Get>(path, queryParams: parameters); @@ -306,12 +373,12 @@ namespace LeanCloud.Storage { obj.Merge(objectData); list.Add(obj); } - return list; + return list.AsReadOnly(); } public async Task First() { Limit(1); - List results = await Find(); + ReadOnlyCollection results = await Find(); if (results != null && results.Count > 0) { return results[0]; }