Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>

<PropertyGroup>
<VersionPrefix>1.0.9.1</VersionPrefix>
<VersionPrefix>1.0.9.2</VersionPrefix>
</PropertyGroup>

<Choose>
Expand All @@ -16,4 +16,4 @@
</PropertyGroup>
</When>
</Choose>
</Project>
</Project>
2 changes: 1 addition & 1 deletion GitHubReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
https://github.com/StefH/GitHubReleaseNotes

GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.1
GitHubReleaseNotes.exe . --output CHANGELOG.md --skip-empty-releases --language en --version 1.0.9.2
20 changes: 16 additions & 4 deletions src-console/ConsoleAppEF2.0.2_InMemory/Program.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,91 @@ public static Task<dynamic> LastOrDefaultAsync([NotNull] this IQueryable source,
}
#endregion LastOrDefault

#region SingleOrDefaultAsync
private static readonly MethodInfo _singleOrDefault = GetMethod(nameof(Queryable.SingleOrDefault));

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="source">
/// An <see cref="IQueryable" /> to return the single element of.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains the single element of the input sequence that satisfies the condition in predicate.
/// </returns>
[PublicAPI]
public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
{
Check.NotNull(source, nameof(source));
Check.NotNull(cancellationToken, nameof(cancellationToken));

return ExecuteAsync<dynamic>(_singleOrDefault, source, cancellationToken);
}

private static readonly MethodInfo _singleOrDefaultPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1);

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="source">
/// An <see cref="IQueryable" /> to return the single element of.
/// </param>
/// <param name="predicate"> A function to test each element for a condition. </param>
/// <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>
/// <returns>
/// 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.
/// </returns>
[PublicAPI]
public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
{
return SingleOrDefaultAsync(source, default(CancellationToken), predicate, args);
}

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="source">
/// An <see cref="IQueryable" /> to return the single element of.
/// </param>
/// <param name="predicate"> A function to test each element for a condition. </param>
/// <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>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <returns>
/// 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.
/// </returns>
[PublicAPI]
public static Task<dynamic> 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<dynamic>(_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<dynamic> ExecuteAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
Expand Down Expand Up @@ -651,4 +736,4 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<Me
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
#endregion Private Helpers
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}