From 9ee34b33afcd97abaedd201fdf07a87669fe6dcc Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 10 Jan 2019 16:52:57 +0100 Subject: [PATCH 1/2] SingleOrDefaultAsync --- .../ConsoleAppEF2.0.2_InMemory/Program.cs | 20 ++++- .../EFDynamicQueryableExtensions.cs | 87 ++++++++++++++++++- .../EntitiesTests.SingleOrDefaultAsync.cs | 61 +++++++++++++ 3 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SingleOrDefaultAsync.cs diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs index bc4dd98a..d7b07419 100644 --- a/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs +++ b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs @@ -1,11 +1,11 @@ -using System; +using ConsoleAppEF2.Database; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core.CustomTypeProviders; -using ConsoleAppEF2.Database; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; namespace ConsoleAppEF2 { @@ -83,6 +83,18 @@ static void Main(string[] args) context.Cars.Add(new Car { Brand = "Alfa", Color = "Black", Vin = "a%bc", Year = "1979", DateLastModified = dateLastModified.AddDays(3) }); context.SaveChanges(); + var carSingleOrDefault = context.Cars.SingleOrDefault(config, "Brand = \"Ford\""); + Console.WriteLine("carSingleOrDefault {0}", JsonConvert.SerializeObject(carSingleOrDefault, Formatting.Indented)); + + try + { + context.Cars.SingleOrDefault(config, "Brand = \"Alfa\""); + } + catch (Exception e) + { + Console.WriteLine("Excepted : " + e); + } + var carDateLastModified = context.Cars.Where(config, "DateLastModified > \"2018-01-16\""); Console.WriteLine("carDateLastModified {0}", JsonConvert.SerializeObject(carDateLastModified, Formatting.Indented)); diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs index 917a5b79..38c8f8ac 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs @@ -576,6 +576,91 @@ public static Task LastOrDefaultAsync([NotNull] this IQueryable source, } #endregion LastOrDefault + #region SingleOrDefaultAsync + private static readonly MethodInfo _singleOrDefault = GetMethod(nameof(Queryable.SingleOrDefault)); + + /// + /// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. + /// This method throws an exception if more than one element satisfies the condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that any asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to return the single element of. + /// + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in predicate. + /// + [PublicAPI] + public static Task SingleOrDefaultAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + return ExecuteAsync(_singleOrDefault, source, cancellationToken); + } + + private static readonly MethodInfo _singleOrDefaultPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1); + + /// + /// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. + /// This method throws an exception if more than one element satisfies the condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that any asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to return the single element of. + /// + /// A function to test each element for a condition. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in , or default if no such element is found. + /// + [PublicAPI] + public static Task SingleOrDefaultAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) + { + return SingleOrDefaultAsync(source, default(CancellationToken), predicate, args); + } + + /// + /// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. + /// This method throws an exception if more than one element satisfies the condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that any asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to return the single element of. + /// + /// A function to test each element for a condition. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in , or default if no such element is found. + /// + [PublicAPI] + public static Task SingleOrDefaultAsync([NotNull] this IQueryable source, CancellationToken cancellationToken, [NotNull] string predicate, [CanBeNull] params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(predicate, nameof(predicate)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, predicate, args); + + return ExecuteAsync(_singleOrDefaultPredicate, source, Expression.Quote(lambda), cancellationToken); + } + #endregion SingleOrDefault + #region Private Helpers // Copied from https://github.com/aspnet/EntityFramework/blob/9186d0b78a3176587eeb0f557c331f635760fe92/src/Microsoft.EntityFrameworkCore/EntityFrameworkQueryableExtensions.cs //private static Task ExecuteAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) @@ -651,4 +736,4 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi))); #endregion Private Helpers } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SingleOrDefaultAsync.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SingleOrDefaultAsync.cs new file mode 100644 index 00000000..0ee84918 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SingleOrDefaultAsync.cs @@ -0,0 +1,61 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using EntityFramework.DynamicLinq; +#endif +using System.Threading.Tasks; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public async Task Entities_SingleOrDefaultAsync() + { + //Arrange + PopulateTestData(1, 0); + + var expectedQueryable1 = _context.Blogs.Where(b => b.BlogId > 0); + var expected1 = await expectedQueryable1.SingleOrDefaultAsync(); + + var expectedQueryable2 = _context.Blogs.Where(b => b.BlogId > 9999); + var expected2 = await expectedQueryable2.SingleOrDefaultAsync(); + + //Act + IQueryable queryable1 = _context.Blogs.Where("BlogId > 0"); + var result1 = await queryable1.SingleOrDefaultAsync(); + + IQueryable queryable2 = _context.Blogs.Where("BlogId > 9999"); + var result2 = await queryable2.SingleOrDefaultAsync(); + + //Assert + Assert.Equal(expected1, result1); + Assert.Null(expected2); + Assert.Null(result2); + } + + [Fact] + public async Task Entities_SingleOrDefaultAsync_Predicate() + { + //Arrange + PopulateTestData(1, 0); +#if EFCORE + var expected1 = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 0); + var expected2 = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 9999); +#else + var expected1 = await System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 0); + var expected2 = await System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 9999); +#endif + + //Act + var result1 = await _context.Blogs.AsQueryable().SingleOrDefaultAsync("it.BlogId > 0"); + var result2 = await _context.Blogs.AsQueryable().SingleOrDefaultAsync("it.BlogId > 9999"); + + //Assert + Assert.Equal(expected1, result1); + Assert.Null(expected2); + Assert.Null(result2); + } + } +} From 1798bff173329bcd492c4f55157d3e3857f8a4de Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 10 Jan 2019 16:57:59 +0100 Subject: [PATCH 2/2] .2 --- Directory.Build.props | 4 ++-- GitHubReleaseNotes.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index caaa013e..44c947a2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.9.1 + 1.0.9.2 @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index ccdfc7c2..7d4d7f2e 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.1 \ No newline at end of file +GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.2