* AVQueryController.cs: chore: 简化 AVQuery 逻辑,将查询条件逻辑转移至

QueryCondition

* AVQuery.cs:
* IQueryCondition.cs:
* QueryEqualCondition.cs:
* AVQueryExtensions.cs:
* QueryCompositionalCondition.cs:
oneRain 2019-09-17 11:31:40 +08:00
parent 06707f75fe
commit 3f57be22a2
6 changed files with 230 additions and 147 deletions

View File

@ -7,16 +7,13 @@ using System.Threading.Tasks;
namespace LeanCloud.Storage.Internal {
public class AVQueryController {
public async Task<IEnumerable<IObjectState>> FindAsync<T>(AVQuery<T> query, AVUser user,
CancellationToken cancellationToken) where T : AVObject {
public async Task<IEnumerable<IObjectState>> FindAsync<T>(AVQuery<T> query, CancellationToken cancellationToken) where T : AVObject {
IList<object> items = await FindAsync<IList<object>>(query.Path, query.BuildParameters(), "results", cancellationToken);
return from item in items
select AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
}
public async Task<int> CountAsync<T>(AVQuery<T> query,
AVUser user,
CancellationToken cancellationToken) where T : AVObject {
public async Task<int> CountAsync<T>(AVQuery<T> query, CancellationToken cancellationToken) where T : AVObject {
var parameters = query.BuildParameters();
parameters["limit"] = 0;
parameters["count"] = 1;
@ -24,9 +21,7 @@ namespace LeanCloud.Storage.Internal {
return Convert.ToInt32(ret);
}
public async Task<IObjectState> FirstAsync<T>(AVQuery<T> query,
AVUser user,
CancellationToken cancellationToken) where T : AVObject {
public async Task<IObjectState> FirstAsync<T>(AVQuery<T> query, CancellationToken cancellationToken) where T : AVObject {
var parameters = query.BuildParameters();
parameters["limit"] = 1;
IList<object> items = await FindAsync<IList<object>>(query.Path, query.BuildParameters(), "results", cancellationToken);

View File

@ -1,5 +1,10 @@
using System;
namespace LeanCloud.Storage.Internal {
public interface IQueryCondition : IEquatable<IQueryCondition>, IJsonConvertible {
/// <summary>
/// 查询条件接口
/// IEquatable<IQueryCondition> 用于比对(替换)相同的查询条件
/// IJsonConvertible 用于生成序列化 Dictionary
/// </summary>
internal interface IQueryCondition : IEquatable<IQueryCondition>, IJsonConvertible {
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
namespace LeanCloud.Storage.Internal {
internal class QueryCompositionalCondition : IQueryCondition {
@ -11,10 +11,9 @@ namespace LeanCloud.Storage.Internal {
readonly List<IQueryCondition> conditions;
readonly string composition;
internal ReadOnlyCollection<string> orderBy;
internal List<string> orderBy;
internal HashSet<string> includes;
internal HashSet<string> selectedKeys;
internal string redirectClassNameForKey;
internal int skip;
internal int limit;
@ -25,6 +24,8 @@ namespace LeanCloud.Storage.Internal {
limit = 30;
}
#region IQueryCondition
public bool Equals(IQueryCondition other) {
return false;
}
@ -45,41 +46,149 @@ namespace LeanCloud.Storage.Internal {
};
}
/// <summary>
/// 构建查询字符串
/// </summary>
/// <returns></returns>
public IDictionary<string, object> BuildParameters(string className) {
Dictionary<string, object> result = new Dictionary<string, object>();
if (conditions != null) {
result["where"] = ToJSON();
}
if (orderBy != null) {
result["order"] = string.Join(",", orderBy.ToArray());
}
if (includes != null) {
result["include"] = string.Join(",", includes.ToArray());
}
if (selectedKeys != null) {
result["keys"] = string.Join(",", selectedKeys.ToArray());
}
if (!string.IsNullOrEmpty(className)) {
result["className"] = className;
}
if (redirectClassNameForKey != null) {
result["redirectClassNameForKey"] = redirectClassNameForKey;
}
result["skip"] = skip;
result["limit"] = limit;
return result;
#endregion
#region where
public void WhereContainedIn<T>(string key, IEnumerable<T> values) {
AddCondition(key, "$in", values.ToList());
}
public void WhereContainsAll<T>(string key, IEnumerable<T> values) {
AddCondition(key, "$all", values.ToList());
}
public void WhereContains(string key, string substring) {
AddCondition(key, "$regex", RegexQuote(substring));
}
public void WhereDoesNotExist(string key) {
AddCondition(key, "$exists", false);
}
public void WhereDoesNotMatchQuery<T>(string key, AVQuery<T> query) where T : AVObject {
AddCondition(key, "$notInQuery", query.BuildParameters(query.ClassName));
}
public void WhereEndsWith(string key, string suffix) {
AddCondition(key, "$regex", RegexQuote(suffix) + "$");
}
public void WhereEqualTo(string key, object value) {
AddCondition(new QueryEqualCondition(key, value));
}
public void WhereSizeEqualTo(string key, uint size) {
AddCondition(key, "$size", size);
}
public void WhereExists(string key) {
AddCondition(key, "$exists", true);
}
public void WhereGreaterThan(string key, object value) {
AddCondition(key, "$gt", value);
}
public void WhereGreaterThanOrEqualTo(string key, object value) {
AddCondition(key, "$gte", value);
}
public void WhereLessThan(string key, object value) {
AddCondition(key, "$lt", value);
}
public void WhereLessThanOrEqualTo(string key, object value) {
AddCondition(key, "$lte", value);
}
public void WhereMatches(string key, Regex regex, string modifiers) {
if (!regex.Options.HasFlag(RegexOptions.ECMAScript)) {
throw new ArgumentException(
"Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex.");
}
AddCondition(key, "$regex", regex.ToString());
AddCondition(key, "options", modifiers);
}
public void WhereMatches(string key, Regex regex) {
WhereMatches(key, regex, null);
}
public void WhereMatches(string key, string pattern, string modifiers) {
WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers);
}
public void WhereMatches(string key, string pattern) {
WhereMatches(key, pattern, null);
}
public void WhereMatchesKeyInQuery<T>(string key, string keyInQuery, AVQuery<T> query) where T : AVObject {
var parameters = new Dictionary<string, object> {
{ "query", query.BuildParameters(query.ClassName)},
{ "key", keyInQuery}
};
AddCondition(key, "$select", parameters);
}
public void WhereDoesNotMatchesKeyInQuery<T>(string key, string keyInQuery, AVQuery<T> query) where T : AVObject {
var parameters = new Dictionary<string, object> {
{ "query", query.BuildParameters(query.ClassName)},
{ "key", keyInQuery}
};
AddCondition(key, "$dontSelect", parameters);
}
public void WhereMatchesQuery<T>(string key, AVQuery<T> query) where T : AVObject {
AddCondition(key, "$inQuery", query.BuildParameters(query.ClassName));
}
public void WhereNear(string key, AVGeoPoint point) {
AddCondition(key, "$nearSphere", point);
}
public void WhereNotContainedIn<T>(string key, IEnumerable<T> values) {
AddCondition(key, "$nin", values.ToList());
}
public void WhereNotEqualTo(string key, object value) {
AddCondition(key, "$ne", value);
}
public void WhereStartsWith(string key, string suffix) {
AddCondition(key, "$regex", "^" + RegexQuote(suffix));
}
public void WhereWithinGeoBox(string key, AVGeoPoint southwest, AVGeoPoint northeast) {
Dictionary<string, object> value = new Dictionary<string, object> {
{ "$box", new[] { southwest, northeast } }
};
AddCondition(key, "$within", value);
}
public void WhereWithinDistance(string key, AVGeoPoint point, AVGeoDistance maxDistance) {
AddCondition(key, "$nearSphere", point);
AddCondition(key, "$maxDistance", maxDistance.Radians);
}
public void WhereRelatedTo(AVObject parent, string key) {
AddCondition(new QueryRelatedCondition(parent, key));
}
#endregion
internal void OrderBy(string key) {
orderBy = new ReadOnlyCollection<string>(new List<string> { key });
if (orderBy == null) {
orderBy = new List<string>();
}
orderBy.Add(key);
}
internal void OrderByDescending(string key) {
orderBy = new ReadOnlyCollection<string>(new List<string> { "-" + key });
if (orderBy == null) {
orderBy = new List<string>();
}
orderBy.Add($"-{key}");
}
internal void Include(string key) {
@ -112,6 +221,15 @@ namespace LeanCloud.Storage.Internal {
limit = count;
}
internal void AddCondition(string key, string op, object value) {
QueryOperationCondition cond = new QueryOperationCondition {
Key = key,
Op = op,
Value = value
};
AddCondition(cond);
}
internal void AddCondition(IQueryCondition condition) {
if (condition == null) {
return;
@ -122,5 +240,35 @@ namespace LeanCloud.Storage.Internal {
});
conditions.Add(condition);
}
/// <summary>
/// 构建查询字符串
/// </summary>
/// <returns></returns>
internal IDictionary<string, object> BuildParameters(string className) {
Dictionary<string, object> result = new Dictionary<string, object>();
if (conditions != null) {
result["where"] = ToJSON();
}
if (orderBy != null) {
result["order"] = string.Join(",", orderBy.ToArray());
}
if (includes != null) {
result["include"] = string.Join(",", includes.ToArray());
}
if (selectedKeys != null) {
result["keys"] = string.Join(",", selectedKeys.ToArray());
}
if (!string.IsNullOrEmpty(className)) {
result["className"] = className;
}
result["skip"] = skip;
result["limit"] = limit;
return result;
}
string RegexQuote(string input) {
return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E";
}
}
}

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
namespace LeanCloud.Storage.Internal {
public class QueryEqualCondition : IQueryCondition {
internal class QueryEqualCondition : IQueryCondition {
string key;
object value;

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
namespace LeanCloud.Storage.Internal {
/// <summary>
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
///
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
/// they are no longer a part of the same library, especially as we create things like
/// Installation inside push library.
///
/// So this class contains a bunch of extension methods that can live inside another
/// namespace, which 'wrap' the intenral APIs that already exist.
/// </summary>
public static class AVQueryExtensions {
public static string GetClassName<T>(this AVQuery<T> query) where T: AVObject {
return query.ClassName;
}
public static object GetConstraint<T>(this AVQuery<T> query, string key) where T : AVObject {
return query.GetConstraint(key);
}
}
}

View File

@ -30,20 +30,14 @@ namespace LeanCloud {
internal QueryCompositionalCondition condition;
internal static AVQueryController QueryController {
static AVQueryController QueryController {
get {
return AVPlugins.Instance.QueryController;
}
}
internal static ObjectSubclassingController SubclassingController {
get {
return AVPlugins.Instance.SubclassingController;
}
}
public AVQuery()
: this(SubclassingController.GetClassName(typeof(T))) {
: this(AVPlugins.Instance.SubclassingController.GetClassName(typeof(T))) {
}
public AVQuery(string className) {
@ -54,6 +48,8 @@ namespace LeanCloud {
condition = new QueryCompositionalCondition();
}
#region Composition
public static AVQuery<T> And(IEnumerable<AVQuery<T>> queries) {
AVQuery<T> composition = new AVQuery<T>();
string className = null;
@ -62,7 +58,7 @@ namespace LeanCloud {
if (className != null && className != query.ClassName) {
throw new ArgumentException("All of the queries in an or query must be on the same class.");
}
composition.AddCondition(query.condition);
composition.condition.AddCondition(query.condition);
className = query.ClassName;
}
}
@ -80,7 +76,7 @@ namespace LeanCloud {
if (className != null && className != query.ClassName) {
throw new ArgumentException("All of the queries in an or query must be on the same class.");
}
composition.AddCondition(query.condition);
composition.condition.AddCondition(query.condition);
className = query.ClassName;
}
}
@ -88,14 +84,16 @@ namespace LeanCloud {
return composition;
}
#endregion
public virtual async Task<IEnumerable<T>> FindAsync(CancellationToken cancellationToken = default) {
IEnumerable<IObjectState> states = await QueryController.FindAsync(this, AVUser.CurrentUser, cancellationToken);
IEnumerable<IObjectState> states = await QueryController.FindAsync(this, cancellationToken);
return (from state in states
select AVObject.FromState<T>(state, ClassName));
}
public virtual async Task<T> FirstOrDefaultAsync(CancellationToken cancellationToken = default) {
IObjectState state = await QueryController.FirstAsync<T>(this, AVUser.CurrentUser, cancellationToken);
IObjectState state = await QueryController.FirstAsync<T>(this, cancellationToken);
return state == null ? default : AVObject.FromState<T>(state, ClassName);
}
@ -109,7 +107,7 @@ namespace LeanCloud {
}
public virtual Task<int> CountAsync(CancellationToken cancellationToken = default) {
return QueryController.CountAsync(this, AVUser.CurrentUser, cancellationToken);
return QueryController.CountAsync(this, cancellationToken);
}
public virtual async Task<T> GetAsync(string objectId, CancellationToken cancellationToken) {
@ -224,78 +222,72 @@ namespace LeanCloud {
#region Where
public AVQuery<T> WhereContainedIn<TIn>(string key, IEnumerable<TIn> values) {
AddCondition(key, "$in", values.ToList());
condition.WhereContainedIn(key, values);
return this;
}
public AVQuery<T> WhereContainsAll<TIn>(string key, IEnumerable<TIn> values) {
AddCondition(key, "$all", values.ToList());
condition.WhereContainsAll(key, values);
return this;
}
public AVQuery<T> WhereContains(string key, string substring) {
AddCondition(key, "$regex", RegexQuote(substring));
condition.WhereContains(key, substring);
return this;
}
public AVQuery<T> WhereDoesNotExist(string key) {
AddCondition(key, "$exists", false);
condition.WhereDoesNotExist(key);
return this;
}
public AVQuery<T> WhereDoesNotMatchQuery<TOther>(string key, AVQuery<TOther> query)
where TOther : AVObject {
AddCondition(key, "$notInQuery", query.BuildParameters(query.ClassName));
public AVQuery<T> WhereDoesNotMatchQuery<TOther>(string key, AVQuery<TOther> query) where TOther : AVObject {
condition.WhereDoesNotMatchQuery(key, query);
return this;
}
public AVQuery<T> WhereEndsWith(string key, string suffix) {
AddCondition(key, "$regex", RegexQuote(suffix) + "$");
condition.WhereEndsWith(key, suffix);
return this;
}
public AVQuery<T> WhereEqualTo(string key, object value) {
AddCondition(new QueryEqualCondition(key, value));
condition.WhereEqualTo(key, value);
return this;
}
public AVQuery<T> WhereSizeEqualTo(string key, uint size) {
AddCondition(key, "$size", size);
condition.WhereSizeEqualTo(key, size);
return this;
}
public AVQuery<T> WhereExists(string key) {
AddCondition(key, "$exists", true);
condition.WhereExists(key);
return this;
}
public AVQuery<T> WhereGreaterThan(string key, object value) {
AddCondition(key, "$gt", value);
condition.WhereGreaterThan(key, value);
return this;
}
public AVQuery<T> WhereGreaterThanOrEqualTo(string key, object value) {
AddCondition(key, "$gte", value);
condition.WhereGreaterThanOrEqualTo(key, value);
return this;
}
public AVQuery<T> WhereLessThan(string key, object value) {
AddCondition(key, "$lt", value);
condition.WhereLessThan(key, value);
return this;
}
public AVQuery<T> WhereLessThanOrEqualTo(string key, object value) {
AddCondition(key, "$lte", value);
condition.WhereLessThanOrEqualTo(key, value);
return this;
}
public AVQuery<T> WhereMatches(string key, Regex regex, string modifiers) {
if (!regex.Options.HasFlag(RegexOptions.ECMAScript)) {
throw new ArgumentException(
"Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex.");
}
AddCondition(key, "$regex", regex.ToString());
AddCondition(key, "options", modifiers);
condition.WhereMatches(key, regex, modifiers);
return this;
}
@ -311,68 +303,53 @@ namespace LeanCloud {
return WhereMatches(key, pattern, null);
}
public AVQuery<T> WhereMatchesKeyInQuery<TOther>(string key, string keyInQuery, AVQuery<TOther> query)
where TOther : AVObject {
var parameters = new Dictionary<string, object> {
{ "query", query.BuildParameters(query.ClassName)},
{ "key", keyInQuery}
};
AddCondition(key, "$select", parameters);
public AVQuery<T> WhereMatchesKeyInQuery<TOther>(string key, string keyInQuery, AVQuery<TOther> query) where TOther : AVObject {
condition.WhereMatchesKeyInQuery(key, keyInQuery, query);
return this;
}
public AVQuery<T> WhereDoesNotMatchesKeyInQuery<TOther>(string key, string keyInQuery, AVQuery<TOther> query)
where TOther : AVObject {
var parameters = new Dictionary<string, object> {
{ "query", query.BuildParameters(query.ClassName)},
{ "key", keyInQuery}
};
AddCondition(key, "$dontSelect", parameters);
public AVQuery<T> WhereDoesNotMatchesKeyInQuery<TOther>(string key, string keyInQuery, AVQuery<TOther> query) where TOther : AVObject {
condition.WhereDoesNotMatchesKeyInQuery(key, keyInQuery, query);
return this;
}
public AVQuery<T> WhereMatchesQuery<TOther>(string key, AVQuery<TOther> query)
where TOther : AVObject {
AddCondition(key, "$inQuery", query.BuildParameters(query.ClassName));
public AVQuery<T> WhereMatchesQuery<TOther>(string key, AVQuery<TOther> query) where TOther : AVObject {
condition.WhereMatchesQuery(key, query);
return this;
}
public AVQuery<T> WhereNear(string key, AVGeoPoint point) {
AddCondition(key, "$nearSphere", point);
condition.WhereNear(key, point);
return this;
}
public AVQuery<T> WhereNotContainedIn<TIn>(string key, IEnumerable<TIn> values) {
AddCondition(key, "$nin", values.ToList());
condition.WhereNotContainedIn(key, values);
return this;
}
public AVQuery<T> WhereNotEqualTo(string key, object value) {
AddCondition(key, "$ne", value);
condition.WhereNotEqualTo(key, value);
return this;
}
public AVQuery<T> WhereStartsWith(string key, string suffix) {
AddCondition(key, "$regex", "^" + RegexQuote(suffix));
condition.WhereStartsWith(key, suffix);
return this;
}
public AVQuery<T> WhereWithinGeoBox(string key, AVGeoPoint southwest, AVGeoPoint northeast) {
Dictionary<string, object> value = new Dictionary<string, object> {
{ "$box", new[] { southwest, northeast } }
};
AddCondition(key, "$within", value);
condition.WhereWithinGeoBox(key, southwest, northeast);
return this;
}
public AVQuery<T> WhereWithinDistance(string key, AVGeoPoint point, AVGeoDistance maxDistance) {
AddCondition(key, "$nearSphere", point);
AddCondition(key, "$maxDistance", maxDistance.Radians);
condition.WhereWithinDistance(key, point, maxDistance);
return this;
}
public AVQuery<T> WhereRelatedTo(AVObject parent, string key) {
AddCondition(new QueryRelatedCondition(parent, key));
condition.WhereRelatedTo(parent, key);
return this;
}
@ -382,26 +359,8 @@ namespace LeanCloud {
return condition.BuildParameters(className);
}
private string RegexQuote(string input) {
return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E";
}
public IDictionary<string, object> BuildWhere() {
IDictionary<string, object> where = condition.ToJSON();
return where;
}
void AddCondition(string key, string op, object value) {
QueryOperationCondition cond = new QueryOperationCondition {
Key = key,
Op = op,
Value = value
};
condition.AddCondition(cond);
}
void AddCondition(IQueryCondition cond) {
condition.AddCondition(cond);
return condition.ToJSON();
}
}
}