From b4d9eede77c61eec17ea6a40f5d403b5c84c3543 Mon Sep 17 00:00:00 2001 From: Robb Schiefer Date: Wed, 23 Oct 2019 14:26:31 -0500 Subject: [PATCH] added working example of adding interface to DataServiceQuery with example tests --- .../DataServiceQueryOfT.cs | 21 +- .../DataServiceQueryTests.cs | 375 ++++++++++++++++++ 2 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 test/FunctionalTests/Microsoft.OData.Client.Tests/DataServiceQueryTests.cs diff --git a/src/Microsoft.OData.Client/DataServiceQueryOfT.cs b/src/Microsoft.OData.Client/DataServiceQueryOfT.cs index 6b8aa88cad..fce0029056 100644 --- a/src/Microsoft.OData.Client/DataServiceQueryOfT.cs +++ b/src/Microsoft.OData.Client/DataServiceQueryOfT.cs @@ -15,12 +15,20 @@ namespace Microsoft.OData.Client using System.Reflection; using System.Threading.Tasks; + public interface IDataServiceQuery : IQueryable + { + IDataServiceQuery Expand(string path); + IDataServiceQuery Expand(Expression> navigationPropertyAccessor); + Task> ExecuteAsync(); + //DataServiceContext Context { get; } + } + /// /// query object /// /// type of object to materialize [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "required for this feature")] - public class DataServiceQuery : DataServiceQuery, IQueryable + public class DataServiceQuery : DataServiceQuery, IQueryable, IDataServiceQuery { #region Private fields @@ -277,6 +285,13 @@ public IEnumerable GetAllPages() } #endif + /// Expands a query to include entities from a related entity set in the query response. + /// A new query that includes the requested $expand query option appended to the URI of the supplied query. + /// The expand path in the format Orders/Order_Details. + IDataServiceQuery IDataServiceQuery.Expand(string path) + { + return Expand(path) as IDataServiceQuery; + } /// Expands a query to include entities from a related entity set in the query response. /// A new query that includes the requested $expand query option appended to the URI of the supplied query. /// The expand path in the format Orders/Order_Details. @@ -297,6 +312,10 @@ public DataServiceQuery Expand(string path) /// A lambda expression that indicates the navigation property that returns the entity set to include in the expanded query. /// Target type of the last property on the expand path. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design")] + IDataServiceQuery IDataServiceQuery.Expand(Expression> navigationPropertyAccessor) + { + return Expand(navigationPropertyAccessor) as IDataServiceQuery; + } public DataServiceQuery Expand(Expression> navigationPropertyAccessor) { Util.CheckArgumentNull(navigationPropertyAccessor, "navigationPropertyAccessor"); diff --git a/test/FunctionalTests/Microsoft.OData.Client.Tests/DataServiceQueryTests.cs b/test/FunctionalTests/Microsoft.OData.Client.Tests/DataServiceQueryTests.cs new file mode 100644 index 0000000000..1672146ee4 --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Client.Tests/DataServiceQueryTests.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.OData.Client.Tests +{ + public class DataServiceQueryTests + { + [Fact] + public async Task CanCallDataServiceQuery() + { + var client = new TestClient(); + var dataServiceQuery = client.People; + var peopleDataServiceQuery = dataServiceQuery + //.Expand(x => x.Friends) + .Expand("Friends") + .Take(1); + var people = await (peopleDataServiceQuery as DataServiceQuery).ExecuteAsync(); + var person = people.First(); + } + [Fact] + public async Task CanFakeDataServiceQuery() + { + var fakeData = new[] + { + new Person{ + UserName = "jdoe", + FirstName = "John", + Emails = new System.Collections.ObjectModel.Collection(new [] { "jdoe@acme.com"}.ToList()), + Friends = new System.Collections.ObjectModel.Collection() + } + }; + var dataServiceQuery = new FakeDataServiceQuery(fakeData.AsQueryable()); + var peopleDataServiceQuery = dataServiceQuery + .Expand("Friends") + .Select(x => x); + peopleDataServiceQuery = peopleDataServiceQuery + .Take(1); + var people = await (peopleDataServiceQuery as IDataServiceQuery).ExecuteAsync(); + var person = people.First(); + } + } + + public class FakeDataServiceQueryProvider : IQueryProvider + { + private IQueryable _fakeSource; + + public IQueryable FakeSource { get { return this._fakeSource; } } + + public FakeDataServiceQueryProvider(IQueryable fakeSource) + { + this._fakeSource = fakeSource; + } + + public IQueryable CreateQuery(Expression expression) + { + throw new NotImplementedException(); + } + + public IQueryable CreateQuery(Expression expression) + { + return new FakeDataServiceQuery(expression, this as FakeDataServiceQueryProvider); + } + + public object Execute(Expression expression) + { + throw new NotImplementedException(); + } + + public TResult Execute(Expression expression) + { + throw new NotImplementedException(); + } + } + public class FakeDataServiceQuery : IDataServiceQuery + { + private FakeDataServiceQueryProvider _fakeDataServiceQueryProvider; + private Expression _fakeExpression; + + public FakeDataServiceQuery(IQueryable fakeSource) + { + this._fakeExpression = fakeSource.Expression; + this._fakeDataServiceQueryProvider = new FakeDataServiceQueryProvider(fakeSource); + } + + public FakeDataServiceQuery(Expression expression, FakeDataServiceQueryProvider fakeDataServiceQueryProvider) + { + this._fakeExpression = expression; + this._fakeDataServiceQueryProvider = fakeDataServiceQueryProvider; + } + + public Type ElementType => this._fakeDataServiceQueryProvider.FakeSource.ElementType; + + public Expression Expression => this._fakeExpression; + + public IQueryProvider Provider => this._fakeDataServiceQueryProvider; + + public DataServiceContext Context => throw new NotImplementedException(); + + public Task> ExecuteAsync() + { + return Task.Run(() => this._fakeDataServiceQueryProvider.FakeSource.ToArray().AsEnumerable()); + } + + public IDataServiceQuery Expand(string path) + { + return this; + } + + public IDataServiceQuery Expand(Expression> navigationPropertyAccessor) + { + return this; + } + + public IEnumerator GetEnumerator() + { + return this._fakeDataServiceQueryProvider.FakeSource.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this._fakeDataServiceQueryProvider.FakeSource.GetEnumerator(); + } + } + + public class TestClient : DataServiceContext + { + public TestClient() : base(new Uri("https://services.odata.org/TripPinRESTierService"), global::Microsoft.OData.Client.ODataProtocolVersion.V4) + { + this.Format.LoadServiceModel = GeneratedEdmModel.GetInstance; + } + + DataServiceQuery _people; + [global::Microsoft.OData.Client.OriginalNameAttribute("People")] + public DataServiceQuery People { + get + { + + if ((this._people == null)) + { + this._people = base.CreateQuery("People"); + } + return this._people; + } + set + { + this._people = value; + } + } + + private abstract class GeneratedEdmModel + { + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.6.0")] + private static global::Microsoft.OData.Edm.IEdmModel ParsedModel = LoadModelFromString(); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.6.0")] + private const string Edmx = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + +"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.6.0")] + public static global::Microsoft.OData.Edm.IEdmModel GetInstance() + { + return ParsedModel; + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.6.0")] + private static global::Microsoft.OData.Edm.IEdmModel LoadModelFromString() + { + global::System.Xml.XmlReader reader = CreateXmlReader(Edmx); + try + { + global::System.Collections.Generic.IEnumerable errors; + global::Microsoft.OData.Edm.IEdmModel edmModel; + + if (!global::Microsoft.OData.Edm.Csdl.CsdlReader.TryParse(reader, false, out edmModel, out errors)) + { + global::System.Text.StringBuilder errorMessages = new System.Text.StringBuilder(); + foreach (var error in errors) + { + errorMessages.Append(error.ErrorMessage); + errorMessages.Append("; "); + } + throw new global::System.InvalidOperationException(errorMessages.ToString()); + } + + return edmModel; + } + finally + { + ((global::System.IDisposable)(reader)).Dispose(); + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.6.0")] + private static global::System.Xml.XmlReader CreateXmlReader(string edmxToParse) + { + return global::System.Xml.XmlReader.Create(new global::System.IO.StringReader(edmxToParse)); + } + } + } + public class Person + { + public string UserName { get; set; } + public string FirstName { get; set; } + public long? Age { get; set; } + public System.Collections.ObjectModel.Collection Emails { get; set; } + public System.Collections.ObjectModel.Collection Friends { get; set; } + } + +} \ No newline at end of file