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..7fe7eee 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,24 @@ public void ExecuteFutureQueries() runner.ExecuteFutureQueries(context, FutureQueries); } +#if NET45 + /// + /// 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); + } +#endif + /// /// 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..6bfadcc 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,25 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + +#if NET45 + /// + /// 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; + } +#endif } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureQueryBase.cs b/Source/EntityFramework.Extended/Future/FutureQueryBase.cs index fafc15f..19a7646 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. @@ -66,29 +70,54 @@ IQueryable IFutureQuery.Query } /// - /// Gets the result by invoking the if not already loaded. + /// Gets the result. + /// + /// + 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(); + } + +#if NET45 + /// + /// Gets the result asynchronous. /// - /// - /// An that can be used to iterate through the collection. - /// - protected virtual IEnumerable GetResult() + /// 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(); } +#endif /// /// Gets the data command for this query. @@ -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,72 @@ protected virtual void SetResult(ObjectContext dataContext, DbDataReader reader) Exception = ex; } } + +#if NET45 + /// + /// 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; + } + } +#endif } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/FutureRunner.cs b/Source/EntityFramework.Extended/Future/FutureRunner.cs index b3e841e..fcc8041 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,7 +53,54 @@ public void ExecuteFutureQueries(ObjectContext context, IList futu // once all queries processed, clear from queue futureQueries.Clear(); } + } + +#if NET45 + /// + /// 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(); + } } +#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 4e283b2..ba42f89 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 } } +#if NET45 + /// + /// 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; + } +#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 165f50a..9b08002 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,15 @@ 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 102fef4..9524cb7 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,16 @@ public interface IFutureQuery /// The data context to translate the results with. /// The to get the result from. void SetResult(ObjectContext dataContext, DbDataReader reader); + +#if NET45 + /// + /// 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)); +#endif } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Future/IFutureRunner.cs b/Source/EntityFramework.Extended/Future/IFutureRunner.cs index dc303f0..1a62019 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,16 @@ public interface IFutureRunner /// The to run the queries against. /// The future queries list. void ExecuteFutureQueries(ObjectContext context, IList futureQueries); + +#if NET45 + /// + /// 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); +#endif } } \ 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 @@ +