Skip to content

Commit ba76480

Browse files
authored
SingleOrDefaultAsync (#239)
* SingleOrDefaultAsync * .2
1 parent 8af5769 commit ba76480

File tree

5 files changed

+166
-8
lines changed

5 files changed

+166
-8
lines changed

Directory.Build.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</PropertyGroup>
55

66
<PropertyGroup>
7-
<VersionPrefix>1.0.9.1</VersionPrefix>
7+
<VersionPrefix>1.0.9.2</VersionPrefix>
88
</PropertyGroup>
99

1010
<Choose>
@@ -16,4 +16,4 @@
1616
</PropertyGroup>
1717
</When>
1818
</Choose>
19-
</Project>
19+
</Project>

GitHubReleaseNotes.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
https://github.com/StefH/GitHubReleaseNotes
22

3-
GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.1
3+
GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.2

src-console/ConsoleAppEF2.0.2_InMemory/Program.cs

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using System;
1+
using ConsoleAppEF2.Database;
2+
using Microsoft.EntityFrameworkCore;
3+
using Newtonsoft.Json;
4+
using System;
25
using System.Collections.Generic;
36
using System.Linq;
47
using System.Linq.Dynamic.Core;
58
using System.Linq.Dynamic.Core.CustomTypeProviders;
6-
using ConsoleAppEF2.Database;
7-
using Microsoft.EntityFrameworkCore;
8-
using Newtonsoft.Json;
99

1010
namespace ConsoleAppEF2
1111
{
@@ -83,6 +83,18 @@ static void Main(string[] args)
8383
context.Cars.Add(new Car { Brand = "Alfa", Color = "Black", Vin = "a%bc", Year = "1979", DateLastModified = dateLastModified.AddDays(3) });
8484
context.SaveChanges();
8585

86+
var carSingleOrDefault = context.Cars.SingleOrDefault(config, "Brand = \"Ford\"");
87+
Console.WriteLine("carSingleOrDefault {0}", JsonConvert.SerializeObject(carSingleOrDefault, Formatting.Indented));
88+
89+
try
90+
{
91+
context.Cars.SingleOrDefault(config, "Brand = \"Alfa\"");
92+
}
93+
catch (Exception e)
94+
{
95+
Console.WriteLine("Excepted : " + e);
96+
}
97+
8698
var carDateLastModified = context.Cars.Where(config, "DateLastModified > \"2018-01-16\"");
8799
Console.WriteLine("carDateLastModified {0}", JsonConvert.SerializeObject(carDateLastModified, Formatting.Indented));
88100

src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs

+86-1
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,91 @@ public static Task<dynamic> LastOrDefaultAsync([NotNull] this IQueryable source,
576576
}
577577
#endregion LastOrDefault
578578

579+
#region SingleOrDefaultAsync
580+
private static readonly MethodInfo _singleOrDefault = GetMethod(nameof(Queryable.SingleOrDefault));
581+
582+
/// <summary>
583+
/// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists.
584+
/// This method throws an exception if more than one element satisfies the condition.
585+
/// </summary>
586+
/// <remarks>
587+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
588+
/// that any asynchronous operations have completed before calling another method on this context.
589+
/// </remarks>
590+
/// <param name="source">
591+
/// An <see cref="IQueryable" /> to return the single element of.
592+
/// </param>
593+
/// <param name="cancellationToken">
594+
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
595+
/// </param>
596+
/// <returns>
597+
/// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in predicate.
598+
/// </returns>
599+
[PublicAPI]
600+
public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
601+
{
602+
Check.NotNull(source, nameof(source));
603+
Check.NotNull(cancellationToken, nameof(cancellationToken));
604+
605+
return ExecuteAsync<dynamic>(_singleOrDefault, source, cancellationToken);
606+
}
607+
608+
private static readonly MethodInfo _singleOrDefaultPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1);
609+
610+
/// <summary>
611+
/// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists.
612+
/// This method throws an exception if more than one element satisfies the condition.
613+
/// </summary>
614+
/// <remarks>
615+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
616+
/// that any asynchronous operations have completed before calling another method on this context.
617+
/// </remarks>
618+
/// <param name="source">
619+
/// An <see cref="IQueryable" /> to return the single element of.
620+
/// </param>
621+
/// <param name="predicate"> A function to test each element for a condition. </param>
622+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
623+
/// <returns>
624+
/// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in <paramref name="predicate" />, or default if no such element is found.
625+
/// </returns>
626+
[PublicAPI]
627+
public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
628+
{
629+
return SingleOrDefaultAsync(source, default(CancellationToken), predicate, args);
630+
}
631+
632+
/// <summary>
633+
/// Asynchronously returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists.
634+
/// This method throws an exception if more than one element satisfies the condition.
635+
/// </summary>
636+
/// <remarks>
637+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
638+
/// that any asynchronous operations have completed before calling another method on this context.
639+
/// </remarks>
640+
/// <param name="source">
641+
/// An <see cref="IQueryable" /> to return the single element of.
642+
/// </param>
643+
/// <param name="predicate"> A function to test each element for a condition. </param>
644+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
645+
/// <param name="cancellationToken">
646+
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
647+
/// </param>
648+
/// <returns>
649+
/// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in <paramref name="predicate" />, or default if no such element is found.
650+
/// </returns>
651+
[PublicAPI]
652+
public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable source, CancellationToken cancellationToken, [NotNull] string predicate, [CanBeNull] params object[] args)
653+
{
654+
Check.NotNull(source, nameof(source));
655+
Check.NotNull(predicate, nameof(predicate));
656+
Check.NotNull(cancellationToken, nameof(cancellationToken));
657+
658+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, predicate, args);
659+
660+
return ExecuteAsync<dynamic>(_singleOrDefaultPredicate, source, Expression.Quote(lambda), cancellationToken);
661+
}
662+
#endregion SingleOrDefault
663+
579664
#region Private Helpers
580665
// Copied from https://github.com/aspnet/EntityFramework/blob/9186d0b78a3176587eeb0f557c331f635760fe92/src/Microsoft.EntityFrameworkCore/EntityFrameworkQueryableExtensions.cs
581666
//private static Task<dynamic> ExecuteAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
@@ -651,4 +736,4 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<Me
651736
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
652737
#endregion Private Helpers
653738
}
654-
}
739+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#if EFCORE
2+
using Microsoft.EntityFrameworkCore.DynamicLinq;
3+
#else
4+
using EntityFramework.DynamicLinq;
5+
#endif
6+
using System.Threading.Tasks;
7+
using Xunit;
8+
9+
namespace System.Linq.Dynamic.Core.Tests
10+
{
11+
public partial class EntitiesTests
12+
{
13+
[Fact]
14+
public async Task Entities_SingleOrDefaultAsync()
15+
{
16+
//Arrange
17+
PopulateTestData(1, 0);
18+
19+
var expectedQueryable1 = _context.Blogs.Where(b => b.BlogId > 0);
20+
var expected1 = await expectedQueryable1.SingleOrDefaultAsync();
21+
22+
var expectedQueryable2 = _context.Blogs.Where(b => b.BlogId > 9999);
23+
var expected2 = await expectedQueryable2.SingleOrDefaultAsync();
24+
25+
//Act
26+
IQueryable queryable1 = _context.Blogs.Where("BlogId > 0");
27+
var result1 = await queryable1.SingleOrDefaultAsync();
28+
29+
IQueryable queryable2 = _context.Blogs.Where("BlogId > 9999");
30+
var result2 = await queryable2.SingleOrDefaultAsync();
31+
32+
//Assert
33+
Assert.Equal(expected1, result1);
34+
Assert.Null(expected2);
35+
Assert.Null(result2);
36+
}
37+
38+
[Fact]
39+
public async Task Entities_SingleOrDefaultAsync_Predicate()
40+
{
41+
//Arrange
42+
PopulateTestData(1, 0);
43+
#if EFCORE
44+
var expected1 = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 0);
45+
var expected2 = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 9999);
46+
#else
47+
var expected1 = await System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 0);
48+
var expected2 = await System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(_context.Blogs, b => b.BlogId > 9999);
49+
#endif
50+
51+
//Act
52+
var result1 = await _context.Blogs.AsQueryable().SingleOrDefaultAsync("it.BlogId > 0");
53+
var result2 = await _context.Blogs.AsQueryable().SingleOrDefaultAsync("it.BlogId > 9999");
54+
55+
//Assert
56+
Assert.Equal(expected1, result1);
57+
Assert.Null(expected2);
58+
Assert.Null(result2);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)