diff --git a/APIClient/Connector/V1Connector.cs b/APIClient/Connector/V1Connector.cs index ce8451b..087fa40 100644 --- a/APIClient/Connector/V1Connector.cs +++ b/APIClient/Connector/V1Connector.cs @@ -513,6 +513,11 @@ ICanGetConnector ICanSetEndpointOrGetConnector.UseEndpoint(string endpoint) return this; } + + public FluentQuery Query(string assetTypeName) + { + return new Services(this.Build()).Query(assetTypeName); + } } #endregion @@ -587,6 +592,7 @@ public interface ICanSetProxyOrEndpointOrGetConnector : ICanSetEndpoint, ICanGet /// The ProxyProvider containing the proxy URI, username, and password. /// ICanSetEndpointOrGetConnector ICanSetEndpointOrGetConnector WithProxy(ProxyProvider proxyProvider); + FluentQuery Query(string assetTypeName); } public interface ICanSetEndpointOrGetConnector : ICanGetConnector diff --git a/APIClient/Model/Asset/Asset.cs b/APIClient/Model/Asset/Asset.cs index 6b55daa..802ad01 100644 --- a/APIClient/Model/Asset/Asset.cs +++ b/APIClient/Model/Asset/Asset.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; namespace VersionOne.SDK.APIClient { - public class Asset + public class Asset : DynamicObject { private Oid oid = Oid.Null; private readonly IDictionary attributes = new Dictionary(); private readonly IAssetType assetType; private readonly AssetList children = new AssetList(); + protected string AssetBasePrefix { get; set; } + private IMetaModel metaModel; + private IServices services; public IAssetType AssetType { @@ -56,6 +60,20 @@ public Asset(IAssetType assetType) this.assetType = assetType; } + public Asset(string basePrefix, IMetaModel metaModel, IServices services) + { + Configure(basePrefix, metaModel, services); + } + + public void Configure(string basePrefix, IMetaModel metaModel, IServices services) + { + this.AssetBasePrefix = basePrefix; + this.metaModel = metaModel; + this.services = services; + } + + public object this[string name] => GetValueByName(name); + public void SetAttributeValue(IAttributeDefinition attribdef, object value) { EnsureAttribute(attribdef).SetValue(value); @@ -153,5 +171,49 @@ public Attribute EnsureAttribute(IAttributeDefinition attribdef) return attrib; } + + #region DynamicObject members + + public override bool TryGetMember(GetMemberBinder binder, + out object result) + { + var attribute = GetAttributeByName(binder.Name); + result = GetAttribute(attribute).Value; + return result != null; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + var attribute = GetAttributeByName(binder.Name); + if (attribute != null) + { + SetAttributeValue(attribute, value); + return true; + } + return false; + } + + #endregion + + public IAttributeDefinition GetAttributeByName(object fieldName) + { + return metaModel.GetAttributeDefinition(AssetBasePrefix + "." + fieldName); + } + + public object GetValueByName(string fieldName) + { + var attribute = GetAttributeByName(fieldName); + return GetAttribute(attribute).Value; + } + + public void SaveChanges() + { + services.Save(this); + } + + public void SaveChanges(string comment) + { + services.Save(this, comment); + } } } \ No newline at end of file diff --git a/APIClient/Query/FluentQuery.cs b/APIClient/Query/FluentQuery.cs new file mode 100644 index 0000000..7734e98 --- /dev/null +++ b/APIClient/Query/FluentQuery.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace VersionOne.SDK.APIClient +{ + public class FluentQuery + { + private IMetaModel metaModel; + private IServices services; + public Query RawQuery { get; set; } + public string AssetTypeName { get; set; } + public List> WhereCriteria { get; set; } + public List SelectFields { get; set; } + public Action> OnSuccess { get; set; } + public Action OnEmptyResults { get; set; } + public Action OnError { get; set; } + + public FluentQuery( + string assetTypeName, IMetaModel metaModel, IServices services) + { + this.metaModel = metaModel; + this.services = services; + AssetTypeName = assetTypeName; + + WhereCriteria = new List>(); + SelectFields = new List(); + } + + public FluentQuery Where(params Tuple[] criteria) + { + WhereCriteria.AddRange(criteria); + + return this; + } + + public FluentQuery Select(params object[] fields) + { + SelectFields.AddRange(fields); + + return this; + } + + public FluentQuery Success(Action> callback) + { + OnSuccess = callback; + + return this; + } + + public FluentQuery WhenEmpty(Action callback) + { + OnEmptyResults = callback; + + return this; + } + + public FluentQuery Error(Action callback) + { + OnError = callback; + + return this; + } + + public FluentQuery Execute(Action> successCallback = null) + { + if (OnSuccess == null && successCallback == null) + { + throw new NullReferenceException("Must specify the OnSuccess callback before calling Execute or pass it directly to Execute as a parameter"); + } + + try + { + QueryResult result = RetrieveQueryResult(); + + if (result.Assets.Count == 0) + { + OnSuccess?.Invoke(result.Assets); + } + + result.Assets.ForEach(a => a.Configure(AssetTypeName, metaModel, services)); + + if (result.Assets.Count == 0) + { + OnEmptyResults?.Invoke(); + } + + if (successCallback != null) + { + successCallback(result.Assets); + } + else + { + OnSuccess(result.Assets); + } + } + catch (Exception exception) + { + if (OnError != null) + { + OnError(exception); + } + else + { + throw; + } + } + + return this; + } + + private QueryResult RetrieveQueryResult() + { + RawQuery = new Query(metaModel.GetAssetType(AssetTypeName)); + + var attributes = new List(); + + if (SelectFields.Count > 0) + { + attributes.AddRange( + SelectFields.Select( + m => metaModel.GetAttributeDefinition(AssetTypeName + + "." + m.ToString()))); + } + RawQuery.Selection.AddRange(attributes); + + if (WhereCriteria.Count > 0) + { + var andTerms = new List(); + + foreach (var tuple in WhereCriteria) + { + var attribute = metaModel.GetAttributeDefinition(AssetTypeName + + "." + tuple.Item1); + var item = tuple.Item2; + var term = new FilterTerm(attribute); + term.Operate(tuple.Item3, item); + andTerms.Add(term); + } + + var andTerm = new AndFilterTerm(andTerms.ToArray()); + RawQuery.Filter = andTerm; + } + + var result = services.Retrieve(RawQuery); + return result; + } + + public QueryResult Retrieve() + { + QueryResult result = null; + try + { + result = RetrieveQueryResult(); + } + catch (Exception exception) + { + if (OnError != null) + { + OnError(exception); + } + else + { + throw; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/APIClient/Services/IServices.cs b/APIClient/Services/IServices.cs index fafdcb4..8ed87c0 100644 --- a/APIClient/Services/IServices.cs +++ b/APIClient/Services/IServices.cs @@ -18,6 +18,7 @@ public interface IServices string Localization(string key); string Localization(IAttributeDefinition attribute); Dictionary Localization(params IAttributeDefinition[] attributes); + FluentQuery Query(string assetTypeName); /// /// Executes a query using the Query API (query.v1 endpoint). diff --git a/APIClient/Services/Services.cs b/APIClient/Services/Services.cs index aa260ac..d902c21 100644 --- a/APIClient/Services/Services.cs +++ b/APIClient/Services/Services.cs @@ -720,5 +720,10 @@ private static void ParseAttributeNode(Asset asset, IAttributeDefinition attribd } } } + + public FluentQuery Query(string assetTypeName) + { + return new FluentQuery(assetTypeName, this.Meta, this); + } } } diff --git a/APIClient/VersionOne.SDK.APIClient.csproj b/APIClient/VersionOne.SDK.APIClient.csproj index 08d7e50..882778c 100644 --- a/APIClient/VersionOne.SDK.APIClient.csproj +++ b/APIClient/VersionOne.SDK.APIClient.csproj @@ -150,6 +150,7 @@ +