From dd7f4b6fd7badc4557b553f70be6f661a4ada9f5 Mon Sep 17 00:00:00 2001 From: DzheiZee Date: Mon, 28 Nov 2016 04:55:31 +0200 Subject: [PATCH 1/2] Future Extension Async --- .../Extensions/FutureExtensions.cs | 14 +- .../Future/FutureContext.cs | 18 ++ .../Future/FutureCount.cs | 8 +- .../Future/FutureQuery.cs | 31 +- .../Future/FutureQueryBase.cs | 136 +++++++-- .../Future/FutureRunner.cs | 47 +++ .../Future/FutureValue.cs | 34 ++- .../Future/IFutureContext.cs | 9 + .../Future/IFutureQuery.cs | 13 +- .../Future/IFutureRunner.cs | 14 +- .../FutureDbContextAsync.cs | 268 ++++++++++++++++++ .../Tracker.SqlServer.Test.csproj | 1 + 12 files changed, 552 insertions(+), 41 deletions(-) create mode 100644 Source/Samples/net45/Tracker.SqlServer.Test/FutureDbContextAsync.cs diff --git a/Source/EntityFramework.Extended/Extensions/FutureExtensions.cs b/Source/EntityFramework.Extended/Extensions/FutureExtensions.cs index 8b18a97..cb62011 100644 --- a/Source/EntityFramework.Extended/Extensions/FutureExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/FutureExtensions.cs @@ -33,7 +33,7 @@ public static FutureQuery Future(this IQueryable sour throw new ArgumentException("The source query must be of type ObjectQuery or DbQuery.", "source"); var futureContext = GetFutureContext(sourceQuery); - var future = new FutureQuery(sourceQuery, futureContext.ExecuteFutureQueries); + var future = new FutureQuery(sourceQuery, futureContext); futureContext.AddQuery(future); return future; @@ -52,7 +52,7 @@ public static FutureCount FutureCount(this IQueryable source) if (source == null) return new FutureCount(0); - ObjectQuery sourceQuery = source.ToObjectQuery(); + ObjectQuery sourceQuery = source.ToObjectQuery(); if (sourceQuery == null) throw new ArgumentException("The source query must be of type ObjectQuery or DbQuery.", "source"); @@ -64,12 +64,12 @@ public static FutureCount FutureCount(this IQueryable source) source.Expression); // create query from expression using internal ObjectQueryProvider - ObjectQuery countQuery = sourceQuery.CreateQuery(expression, typeof(int)); + var countQuery = sourceQuery.CreateQuery(expression, typeof(int)) as ObjectQuery; if (countQuery == null) throw new ArgumentException("The source query must be of type ObjectQuery or DbQuery.", "source"); var futureContext = GetFutureContext(sourceQuery); - var future = new FutureCount(countQuery, futureContext.ExecuteFutureQueries); + var future = new FutureCount(countQuery, futureContext); futureContext.AddQuery(future); return future; } @@ -106,12 +106,12 @@ public static FutureValue FutureValue(this IQueryable } var expression = Expression.Call(null, methodExpr.Method, arguments); - var valueQuery = sourceQuery.CreateQuery(expression, typeof(TResult)); + var valueQuery = sourceQuery.CreateQuery(expression, typeof(TResult)) as ObjectQuery; if (valueQuery == null) throw new ArgumentException("The source query must be of type ObjectQuery or DbQuery.", "source"); var futureContext = GetFutureContext(sourceQuery); - var future = new FutureValue(valueQuery, futureContext.ExecuteFutureQueries); + var future = new FutureValue(valueQuery, futureContext); futureContext.AddQuery(future); return future; } @@ -141,7 +141,7 @@ public static FutureValue FutureFirstOrDefault(this IQueryable throw new ArgumentException("The source query must be of type ObjectQuery or DbQuery.", "source"); var futureContext = GetFutureContext(sourceQuery); - var future = new FutureValue(objectQuery, futureContext.ExecuteFutureQueries); + var future = new FutureValue(objectQuery, futureContext); futureContext.AddQuery(future); return future; } diff --git a/Source/EntityFramework.Extended/Future/FutureContext.cs b/Source/EntityFramework.Extended/Future/FutureContext.cs index 8a57bea..3e9b95c 100644 --- a/Source/EntityFramework.Extended/Future/FutureContext.cs +++ b/Source/EntityFramework.Extended/Future/FutureContext.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Data.Entity.Core.Objects; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -85,6 +87,22 @@ public void ExecuteFutureQueries() runner.ExecuteFutureQueries(context, FutureQueries); } + /// + /// Executes the future queries as a single batch. + /// + public async Task ExecuteFutureQueriesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + ObjectContext context = ObjectContext; + if (context == null) + throw new ObjectDisposedException("ObjectContext", "The ObjectContext for the future queries has been disposed."); + + var runner = Locator.Current.Resolve(); + if (runner == null) + throw new InvalidOperationException("Could not resolve the IFutureRunner. Make sure IFutureRunner is registered in the Locator.Current container."); + + await runner.ExecuteFutureQueriesAsync(context, FutureQueries, cancellationToken).ConfigureAwait(false); + } + /// /// Adds the future query to the waiting queries list on this context. /// diff --git a/Source/EntityFramework.Extended/Future/FutureCount.cs b/Source/EntityFramework.Extended/Future/FutureCount.cs index 6dad9e4..0e154a3 100644 --- a/Source/EntityFramework.Extended/Future/FutureCount.cs +++ b/Source/EntityFramework.Extended/Future/FutureCount.cs @@ -27,12 +27,12 @@ namespace EntityFramework.Future public class FutureCount : FutureValue { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The query source to use when materializing. - /// The action to execute when the query is accessed. - internal FutureCount(IQueryable query, Action loadAction) - : base(query, loadAction) + /// The future context. + internal FutureCount(IQueryable query, IFutureContext futureContext) + : base(query, futureContext) { } /// diff --git a/Source/EntityFramework.Extended/Future/FutureQuery.cs b/Source/EntityFramework.Extended/Future/FutureQuery.cs index bf7b797..91f3bbe 100644 --- a/Source/EntityFramework.Extended/Future/FutureQuery.cs +++ b/Source/EntityFramework.Extended/Future/FutureQuery.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -26,13 +28,14 @@ namespace EntityFramework.Future public class FutureQuery : FutureQueryBase, IEnumerable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The query source to use when materializing. - /// The action to execute when the query is accessed. - internal FutureQuery(IQueryable query, Action loadAction) - : base(query, loadAction) - { } + /// The future context. + internal FutureQuery(IQueryable query, IFutureContext futureContext) + : base(query, futureContext) + { + } /// /// Returns an enumerator that iterates through the collection. @@ -61,5 +64,23 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// + /// To the list asynchronous. + /// + /// The cancellation token. + /// + /// An error occurred executing the future query. + public async Task> ToListAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // triggers loading future queries + var result = + await GetResultAsync(cancellationToken).ConfigureAwait(false) ?? new List(); + + if (Exception != null) + throw new FutureException("An error occurred executing the future query.", Exception); + + return result; + } } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureQueryBase.cs b/Source/EntityFramework.Extended/Future/FutureQueryBase.cs index fafc15f..884c1af 100644 --- a/Source/EntityFramework.Extended/Future/FutureQueryBase.cs +++ b/Source/EntityFramework.Extended/Future/FutureQueryBase.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.Data.Entity; using System.Data.Entity.Core.Objects; +using System.Data.Entity.Infrastructure; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using EntityFramework.Reflection; namespace EntityFramework.Future @@ -15,31 +19,31 @@ namespace EntityFramework.Future [DebuggerDisplay("IsLoaded={IsLoaded}")] public abstract class FutureQueryBase : IFutureQuery { - private readonly Action _loadAction; - private readonly IQueryable _query; - private IEnumerable _result; + private readonly IQueryable _query; + private readonly IFutureContext _futureContext; + private IList _result; private bool _isLoaded; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The query source to use when materializing. - /// The action to execute when the query is accessed. - protected FutureQueryBase(IQueryable query, Action loadAction) + /// The future context. + protected FutureQueryBase(IQueryable query, IFutureContext futureContext) { _query = query; - _loadAction = loadAction; - _result = null; + _futureContext = futureContext; + //_result = null; } /// /// Gets the action to execute when the query is accessed. /// /// The load action. - protected Action LoadAction - { - get { return _loadAction; } - } + //protected Action LoadAction + //{ + // get { return _loadAction; } + //} /// /// Gets a value indicating whether this instance is loaded. @@ -71,23 +75,48 @@ IQueryable IFutureQuery.Query /// /// An that can be used to iterate through the collection. /// - protected virtual IEnumerable GetResult() + protected virtual IList GetResult() + { + if (IsLoaded) + return _result; + + // no load action, run query directly + if (_futureContext == null) + { + _isLoaded = true; + var enumerable = _query as IEnumerable; + _result = enumerable == null ? null : enumerable.ToList(); + return _result; + } + + // invoke the load action on the datacontext + // result will be set with a callback to SetResult + _futureContext.ExecuteFutureQueries(); + return _result ?? new List(); + } + + /// + /// Gets the result asynchronous. + /// + /// The cancellation token. + /// + protected virtual async Task> GetResultAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (IsLoaded) return _result; // no load action, run query directly - if (LoadAction == null) + if (_futureContext == null) { _isLoaded = true; - _result = _query as IEnumerable; + _result = await (_query as IQueryable).ToListAsync(cancellationToken).ConfigureAwait(false); return _result; } // invoke the load action on the datacontext // result will be set with a callback to SetResult - LoadAction.Invoke(); - return _result ?? Enumerable.Empty(); + await _futureContext.ExecuteFutureQueriesAsync(cancellationToken).ConfigureAwait(false); + return _result ?? new List(); } /// @@ -155,13 +184,16 @@ protected virtual void SetResult(ObjectContext dataContext, DbDataReader reader) dynamic queryProxy = new DynamicProxy(q); // ObjectQueryState dynamic queryState = queryProxy.QueryState; + + // ObjectQueryExecutionPlan dynamic executionPlan = queryState.GetExecutionPlan(null); - + // ShaperFactory dynamic shaperFactory = executionPlan.ResultShaperFactory; // Shaper - dynamic shaper = shaperFactory.Create(reader, dataContext, dataContext.MetadataWorkspace, MergeOption.AppendOnly, false, true, false); + dynamic shaper = shaperFactory.Create(reader, dataContext, dataContext.MetadataWorkspace, + MergeOption.AppendOnly, false, true, false); var list = new List(); IEnumerator enumerator = shaper.GetEnumerator(); @@ -179,5 +211,71 @@ protected virtual void SetResult(ObjectContext dataContext, DbDataReader reader) Exception = ex; } } + + + /// + /// Sets the underling value after the query has been executed. + /// + /// The data context to translate the results with. + /// The to get the result from. + /// + /// + Task IFutureQuery.SetResultAsync(ObjectContext dataContext, DbDataReader reader, CancellationToken cancellationToken = default(CancellationToken)) + { + return SetResultAsync(dataContext, reader, cancellationToken); + } + + /// + /// Sets the underling value after the query has been executed. + /// + /// The data context to translate the results with. + /// The to get the result from. + /// + /// + /// The future query is not of type ObjectQuery. + protected virtual async Task SetResultAsync(ObjectContext dataContext, DbDataReader reader, CancellationToken cancellationToken = default(CancellationToken)) + { + _isLoaded = true; + + try + { + IFutureQuery futureQuery = this; + var source = futureQuery.Query; + + var q = source as ObjectQuery; + if (q == null) + throw new InvalidOperationException("The future query is not of type ObjectQuery."); + + // create execution plan + dynamic queryProxy = new DynamicProxy(q); + // ObjectQueryState + dynamic queryState = queryProxy.QueryState; + + // ObjectQueryExecutionPlan + dynamic executionPlan = queryState.GetExecutionPlan(null); + + // ShaperFactory + dynamic shaperFactory = executionPlan.ResultShaperFactory; + // Shaper + dynamic shaper = shaperFactory.Create(reader, dataContext, dataContext.MetadataWorkspace, + MergeOption.AppendOnly, false, true, false); + + var list = new List(); + // Shaper GetEnumerator method return IDbEnumerator, which implements publicly accessible IDbAsyncEnumerator + IDbAsyncEnumerator enumerator = shaper.GetEnumerator(); + while (await enumerator.MoveNextAsync(cancellationToken).ConfigureAwait(false)) + list.Add(enumerator.Current); + + _result = list; + + // translate has issue with column names not matching + //var resultSet = dataContext.Translate(reader); + //_result = resultSet.ToList(); + } + catch (Exception ex) + { + Exception = ex; + } + } } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureRunner.cs b/Source/EntityFramework.Extended/Future/FutureRunner.cs index b3e841e..e9f39eb 100644 --- a/Source/EntityFramework.Extended/Future/FutureRunner.cs +++ b/Source/EntityFramework.Extended/Future/FutureRunner.cs @@ -5,6 +5,8 @@ using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure.Interception; using System.Text; +using System.Threading; +using System.Threading.Tasks; using EntityFramework.Reflection; namespace EntityFramework.Future @@ -51,6 +53,51 @@ public void ExecuteFutureQueries(ObjectContext context, IList futu // once all queries processed, clear from queue futureQueries.Clear(); } + } + + /// + /// Executes the future queries. + /// + /// The to run the queries against. + /// The future queries list. + /// The cancellation token. + /// + public async Task ExecuteFutureQueriesAsync(ObjectContext context, IList futureQueries, CancellationToken cancellationToken = default(CancellationToken)) + { + if (context == null) + throw new ArgumentNullException("context"); + if (futureQueries == null) + throw new ArgumentNullException("futureQueries"); + + // used to call internal methods + dynamic contextProxy = new DynamicProxy(context); + contextProxy.EnsureConnection(false); + + //the (internal) InterceptionContext contains the registered loggers + DbInterceptionContext interceptionContext = contextProxy.InterceptionContext; + + try + { + using (var command = CreateFutureCommand(context, futureQueries)) + using (var reader = await DbInterception.Dispatch.Command.ReaderAsync( + command, new DbCommandInterceptionContext(interceptionContext), cancellationToken + ) + .ConfigureAwait(false) + ) + { + foreach (var futureQuery in futureQueries) + { + await futureQuery.SetResultAsync(context, reader, cancellationToken).ConfigureAwait(false); + await reader.NextResultAsync(cancellationToken).ConfigureAwait(false); + } + } + } + finally + { + contextProxy.ReleaseConnection(); + // once all queries processed, clear from queue + futureQueries.Clear(); + } } private static DbCommand CreateFutureCommand(ObjectContext context, IEnumerable futureQueries) diff --git a/Source/EntityFramework.Extended/Future/FutureValue.cs b/Source/EntityFramework.Extended/Future/FutureValue.cs index 4e283b2..a64bbfc 100644 --- a/Source/EntityFramework.Extended/Future/FutureValue.cs +++ b/Source/EntityFramework.Extended/Future/FutureValue.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -26,12 +29,12 @@ public class FutureValue : FutureQueryBase private bool _hasValue; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The query source to use when materializing. - /// The action to execute when the query is accessed. - internal FutureValue(IQueryable query, Action loadAction) - : base(query, loadAction) + /// The future context. + internal FutureValue(IQueryable query, IFutureContext futureContext) + : base(query, futureContext) { } /// @@ -76,6 +79,29 @@ public T Value } } + /// + /// Gets the value asynchronous. + /// + /// The cancellation token. + /// + /// An error occurred executing the future query. + public async Task GetValueAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (!_hasValue) + { + _hasValue = true; + + var resultingList = await GetResultAsync(cancellationToken).ConfigureAwait(false); + + UnderlyingValue = resultingList != null ? resultingList.FirstOrDefault() : default(T); + } + + if (Exception != null) + throw new FutureException("An error occurred executing the future query.", Exception); + + return UnderlyingValue; + } + /// /// Performs an implicit conversion from to T. /// diff --git a/Source/EntityFramework.Extended/Future/IFutureContext.cs b/Source/EntityFramework.Extended/Future/IFutureContext.cs index 165f50a..5f963c5 100644 --- a/Source/EntityFramework.Extended/Future/IFutureContext.cs +++ b/Source/EntityFramework.Extended/Future/IFutureContext.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -19,6 +21,13 @@ public interface IFutureContext /// void ExecuteFutureQueries(); + /// + /// Executes the future queries. + /// + /// The cancellation token. + /// + Task ExecuteFutureQueriesAsync(CancellationToken cancellationToken); + /// /// Adds the future query to the waiting queries list on this context. /// diff --git a/Source/EntityFramework.Extended/Future/IFutureQuery.cs b/Source/EntityFramework.Extended/Future/IFutureQuery.cs index 102fef4..368db73 100644 --- a/Source/EntityFramework.Extended/Future/IFutureQuery.cs +++ b/Source/EntityFramework.Extended/Future/IFutureQuery.cs @@ -2,6 +2,8 @@ using System.Data.Common; using System.Data.Entity.Core.Objects; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -35,5 +37,14 @@ public interface IFutureQuery /// The data context to translate the results with. /// The to get the result from. void SetResult(ObjectContext dataContext, DbDataReader reader); - } + + /// + /// Sets the underling value after the query has been executed. + /// + /// The data context to translate the results with. + /// The to get the result from. + /// The cancellation token. + /// + Task SetResultAsync(ObjectContext dataContext, DbDataReader reader, CancellationToken cancellationToken = default(CancellationToken)); + } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/IFutureRunner.cs b/Source/EntityFramework.Extended/Future/IFutureRunner.cs index dc303f0..e0049fe 100644 --- a/Source/EntityFramework.Extended/Future/IFutureRunner.cs +++ b/Source/EntityFramework.Extended/Future/IFutureRunner.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Data.Entity.Core.Objects; +using System.Threading; +using System.Threading.Tasks; namespace EntityFramework.Future { @@ -15,5 +17,15 @@ public interface IFutureRunner /// The to run the queries against. /// The future queries list. void ExecuteFutureQueries(ObjectContext context, IList futureQueries); - } + + + /// + /// Executes the future queries. + /// + /// The to run the queries against. + /// The future queries list. + /// The cancellation token. + /// + Task ExecuteFutureQueriesAsync(ObjectContext context, IList futureQueries, CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/Source/Samples/net45/Tracker.SqlServer.Test/FutureDbContextAsync.cs b/Source/Samples/net45/Tracker.SqlServer.Test/FutureDbContextAsync.cs new file mode 100644 index 0000000..77cdb51 --- /dev/null +++ b/Source/Samples/net45/Tracker.SqlServer.Test/FutureDbContextAsync.cs @@ -0,0 +1,268 @@ +using System.Linq; +using System.Text; +using EntityFramework.Extensions; +using EntityFramework.Future; +using Xunit; +using Tracker.SqlServer.CodeFirst; +using Tracker.SqlServer.CodeFirst.Entities; + +namespace Tracker.SqlServer.Test + { + public class FutureDbContextAsync + { + [Fact] + public void PageTest() + { + var db = new TrackerContext(); + + // base query + var q = db.Tasks + .Where(p => p.PriorityId == 2) + .OrderByDescending(t => t.CreatedDate); + + // get total count + var q1 = q.FutureCount(); + // get first page + var q2 = q.Skip(0).Take(10).Future(); + // triggers sql execute as a batch + var tasks = q2.ToListAsync().Result; + int total = q1.Value; + + Assert.NotNull(tasks); + } + + [Fact] + public void SimpleTest() + { + var db = new TrackerContext(); + + // build up queries + + string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .Future(); + + string search = "Earth"; + var q2 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .Future(); + + // should be 2 queries + //Assert.Equal(2, db.FutureQueries.Count); + + // this triggers the loading of all the future queries + var users = q1.ToListAsync().Result; + Assert.NotNull(users); + + // should be cleared at this point + //Assert.Equal(0, db.FutureQueries.Count); + + // this should already be loaded + Assert.True(((IFutureQuery)q2).IsLoaded); + + var tasks = q2.ToList(); + Assert.NotNull(tasks); + + } + + [Fact] + public void FutureCountTest() + { + var db = new TrackerContext(); + + // build up queries + + string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .Future(); + + string search = "Earth"; + var q2 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .FutureCount(); + + // should be 2 queries + //Assert.Equal(2, db.FutureQueries.Count); + + // this triggers the loading of all the future queries + var users = q1.ToListAsync().Result; + Assert.NotNull(users); + + // should be cleared at this point + //Assert.Equal(0, db.FutureQueries.Count); + + // this should already be loaded + Assert.True(((IFutureQuery)q2).IsLoaded); + + int count = q2; + Assert.NotEqual(count, 0); + } + + [Fact] + public void FutureCountReverseTest() + { + var db = new TrackerContext(); + + // build up queries + + string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .Future(); + + string search = "Earth"; + var q2 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .FutureCount(); + + // should be 2 queries + //Assert.Equal(2, db.FutureQueries.Count); + + // access q2 first to trigger loading, testing loading from FutureCount + // this triggers the loading of all the future queries + var count = q2.GetValueAsync().Result; + Assert.NotEqual(count, 0); + + // should be cleared at this point + //Assert.Equal(0, db.FutureQueries.Count); + + // this should already be loaded + Assert.True(((IFutureQuery)q1).IsLoaded); + + var users = q1.ToList(); + Assert.NotNull(users); + } + + [Fact] + public void FutureValueTest() + { + var db = new TrackerContext(); + + // build up queries + string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .FutureFirstOrDefault(); + + string search = "Earth"; + var q2 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .FutureCount(); + + // duplicate query except count + var q3 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .Future(); + + // should be 3 queries + //Assert.Equal(3, db.FutureQueries.Count); + + // this triggers the loading of all the future queries + User user = q1.GetValueAsync().Result; + Assert.NotNull(user); + + // should be cleared at this point + //Assert.Equal(0, db.FutureQueries.Count); + + // this should already be loaded + Assert.True(((IFutureQuery)q2).IsLoaded); + + var count = q2.Value; + Assert.NotEqual(count, 0); + + var tasks = q3.ToList(); + Assert.NotNull(tasks); + } + + [Fact] + public void FutureValueReverseTest() + { + var db = new TrackerContext(); + // build up queries + + string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .FutureFirstOrDefault(); + + string search = "Earth"; + var q2 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .FutureCount(); + + // duplicate query except count + var q3 = db.Tasks + .Where(t => t.Summary.Contains(search)) + .Future(); + + // should be 3 queries + //Assert.Equal(3, db.FutureQueries.Count); + + // access q2 first to trigger loading, testing loading from FutureCount + // this triggers the loading of all the future queries + var count = q2.GetValueAsync().Result; + Assert.NotEqual(count, 0); + + // should be cleared at this point + //Assert.Equal(0, db.FutureQueries.Count); + + // this should already be loaded + Assert.True(((IFutureQuery)q1).IsLoaded); + + var users = q1.GetValueAsync().Result; + Assert.NotNull(users); + + var tasks = q3.ToListAsync().Result; + Assert.NotNull(tasks); + + } + + [Fact] + public void FutureValueWithAggregateFunctions() + { + var db = new TrackerContext(); + + var q1 = db.Users.Where(x => x.EmailAddress.EndsWith("@battlestar.com")).FutureValue(x => x.Count()); + var q2 = db.Users.Where(x => x.EmailAddress.EndsWith("@battlestar.com")).FutureValue(x => x.Min(t => t.LastName)); + var q3 = db.Tasks.FutureValue(x => x.Sum(t => t.Priority.Order)); + + Assert.False(((IFutureQuery)q1).IsLoaded); + Assert.False(((IFutureQuery)q2).IsLoaded); + Assert.False(((IFutureQuery)q3).IsLoaded); + + var r1 = q1.GetValueAsync().Result; + var r2 = q2.GetValueAsync().Result; + var r3 = q3.GetValueAsync().Result; + + Assert.True(((IFutureQuery)q1).IsLoaded); + Assert.True(((IFutureQuery)q2).IsLoaded); + Assert.True(((IFutureQuery)q3).IsLoaded); + } + + [Fact] + public void LoggingInterception() + { + //log to a string builder + var sb = new StringBuilder(); + + var db = new TrackerContext(); + db.Database.Log = s => sb.AppendLine(s); + + const string emailDomain = "@battlestar.com"; + var q1 = db.Users + .Where(p => p.EmailAddress.EndsWith(emailDomain)) + .FutureFirstOrDefault(); + + //materialize it + var user = q1.GetValueAsync().Result; + + //did we log anything? + var logged = sb.ToString(); + + Assert.Contains("[EmailAddress] LIKE N'%@battlestar.com'", logged); + } + + } + } diff --git a/Source/Samples/net45/Tracker.SqlServer.Test/Tracker.SqlServer.Test.csproj b/Source/Samples/net45/Tracker.SqlServer.Test/Tracker.SqlServer.Test.csproj index a0bfffc..4cd1cc1 100644 --- a/Source/Samples/net45/Tracker.SqlServer.Test/Tracker.SqlServer.Test.csproj +++ b/Source/Samples/net45/Tracker.SqlServer.Test/Tracker.SqlServer.Test.csproj @@ -88,6 +88,7 @@ + From 0815cc1f3d2711222e7200d55edcd9ed0f303a29 Mon Sep 17 00:00:00 2001 From: DzheiZee Date: Mon, 28 Nov 2016 06:36:28 +0200 Subject: [PATCH 2/2] added pragmas so .net40 compiler would skip not supported code --- .../EntityFramework.Extended/Future/FutureContext.cs | 2 ++ Source/EntityFramework.Extended/Future/FutureQuery.cs | 2 ++ .../Future/FutureQueryBase.cs | 11 ++++++----- .../EntityFramework.Extended/Future/FutureRunner.cs | 2 ++ Source/EntityFramework.Extended/Future/FutureValue.cs | 4 ++-- .../EntityFramework.Extended/Future/IFutureContext.cs | 2 ++ .../EntityFramework.Extended/Future/IFutureQuery.cs | 4 +++- .../EntityFramework.Extended/Future/IFutureRunner.cs | 5 +++-- 8 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Source/EntityFramework.Extended/Future/FutureContext.cs b/Source/EntityFramework.Extended/Future/FutureContext.cs index 3e9b95c..7fe7eee 100644 --- a/Source/EntityFramework.Extended/Future/FutureContext.cs +++ b/Source/EntityFramework.Extended/Future/FutureContext.cs @@ -87,6 +87,7 @@ public void ExecuteFutureQueries() runner.ExecuteFutureQueries(context, FutureQueries); } +#if NET45 /// /// Executes the future queries as a single batch. /// @@ -102,6 +103,7 @@ public void ExecuteFutureQueries() await runner.ExecuteFutureQueriesAsync(context, FutureQueries, cancellationToken).ConfigureAwait(false); } +#endif /// /// Adds the future query to the waiting queries list on this context. diff --git a/Source/EntityFramework.Extended/Future/FutureQuery.cs b/Source/EntityFramework.Extended/Future/FutureQuery.cs index 91f3bbe..6bfadcc 100644 --- a/Source/EntityFramework.Extended/Future/FutureQuery.cs +++ b/Source/EntityFramework.Extended/Future/FutureQuery.cs @@ -65,6 +65,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } +#if NET45 /// /// To the list asynchronous. /// @@ -82,5 +83,6 @@ IEnumerator IEnumerable.GetEnumerator() return result; } +#endif } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureQueryBase.cs b/Source/EntityFramework.Extended/Future/FutureQueryBase.cs index 884c1af..19a7646 100644 --- a/Source/EntityFramework.Extended/Future/FutureQueryBase.cs +++ b/Source/EntityFramework.Extended/Future/FutureQueryBase.cs @@ -70,11 +70,9 @@ IQueryable IFutureQuery.Query } /// - /// Gets the result by invoking the if not already loaded. + /// Gets the result. /// - /// - /// An that can be used to iterate through the collection. - /// + /// protected virtual IList GetResult() { if (IsLoaded) @@ -95,6 +93,7 @@ protected virtual IList GetResult() return _result ?? new List(); } +#if NET45 /// /// Gets the result asynchronous. /// @@ -118,6 +117,7 @@ protected virtual IList GetResult() await _futureContext.ExecuteFutureQueriesAsync(cancellationToken).ConfigureAwait(false); return _result ?? new List(); } +#endif /// /// Gets the data command for this query. @@ -212,7 +212,7 @@ protected virtual void SetResult(ObjectContext dataContext, DbDataReader reader) } } - +#if NET45 /// /// Sets the underling value after the query has been executed. /// @@ -277,5 +277,6 @@ protected virtual void SetResult(ObjectContext dataContext, DbDataReader reader) Exception = ex; } } +#endif } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureRunner.cs b/Source/EntityFramework.Extended/Future/FutureRunner.cs index e9f39eb..fcc8041 100644 --- a/Source/EntityFramework.Extended/Future/FutureRunner.cs +++ b/Source/EntityFramework.Extended/Future/FutureRunner.cs @@ -55,6 +55,7 @@ public void ExecuteFutureQueries(ObjectContext context, IList futu } } +#if NET45 /// /// Executes the future queries. /// @@ -99,6 +100,7 @@ public void ExecuteFutureQueries(ObjectContext context, IList futu futureQueries.Clear(); } } +#endif private static DbCommand CreateFutureCommand(ObjectContext context, IEnumerable futureQueries) { diff --git a/Source/EntityFramework.Extended/Future/FutureValue.cs b/Source/EntityFramework.Extended/Future/FutureValue.cs index a64bbfc..ba42f89 100644 --- a/Source/EntityFramework.Extended/Future/FutureValue.cs +++ b/Source/EntityFramework.Extended/Future/FutureValue.cs @@ -79,6 +79,7 @@ public T Value } } +#if NET45 /// /// Gets the value asynchronous. /// @@ -90,9 +91,7 @@ public T Value if (!_hasValue) { _hasValue = true; - var resultingList = await GetResultAsync(cancellationToken).ConfigureAwait(false); - UnderlyingValue = resultingList != null ? resultingList.FirstOrDefault() : default(T); } @@ -101,6 +100,7 @@ public T Value return UnderlyingValue; } +#endif /// /// Performs an implicit conversion from to T. diff --git a/Source/EntityFramework.Extended/Future/IFutureContext.cs b/Source/EntityFramework.Extended/Future/IFutureContext.cs index 5f963c5..9b08002 100644 --- a/Source/EntityFramework.Extended/Future/IFutureContext.cs +++ b/Source/EntityFramework.Extended/Future/IFutureContext.cs @@ -21,12 +21,14 @@ public interface IFutureContext /// void ExecuteFutureQueries(); +#if NET45 /// /// Executes the future queries. /// /// The cancellation token. /// Task ExecuteFutureQueriesAsync(CancellationToken cancellationToken); +#endif /// /// Adds the future query to the waiting queries list on this context. diff --git a/Source/EntityFramework.Extended/Future/IFutureQuery.cs b/Source/EntityFramework.Extended/Future/IFutureQuery.cs index 368db73..9524cb7 100644 --- a/Source/EntityFramework.Extended/Future/IFutureQuery.cs +++ b/Source/EntityFramework.Extended/Future/IFutureQuery.cs @@ -38,6 +38,7 @@ public interface IFutureQuery /// The to get the result from. void SetResult(ObjectContext dataContext, DbDataReader reader); +#if NET45 /// /// Sets the underling value after the query has been executed. /// @@ -46,5 +47,6 @@ public interface IFutureQuery /// The cancellation token. /// Task SetResultAsync(ObjectContext dataContext, DbDataReader reader, CancellationToken cancellationToken = default(CancellationToken)); - } +#endif + } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/IFutureRunner.cs b/Source/EntityFramework.Extended/Future/IFutureRunner.cs index e0049fe..1a62019 100644 --- a/Source/EntityFramework.Extended/Future/IFutureRunner.cs +++ b/Source/EntityFramework.Extended/Future/IFutureRunner.cs @@ -18,7 +18,7 @@ public interface IFutureRunner /// The future queries list. void ExecuteFutureQueries(ObjectContext context, IList futureQueries); - +#if NET45 /// /// Executes the future queries. /// @@ -27,5 +27,6 @@ public interface IFutureRunner /// The cancellation token. /// Task ExecuteFutureQueriesAsync(ObjectContext context, IList futureQueries, CancellationToken cancellationToken); - } +#endif + } } \ No newline at end of file