Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Include/ThenInclude as adapter #188

Merged
merged 6 commits into from
Dec 21, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specific
{
if (specification.OrderExpressions != null)
{
if (specification.OrderExpressions.Where(x => x.OrderType == OrderTypeEnum.OrderBy ||
x.OrderType == OrderTypeEnum.OrderByDescending).Count() > 1)
if (specification.OrderExpressions.Count(x => x.OrderType == OrderTypeEnum.OrderBy
|| x.OrderType == OrderTypeEnum.OrderByDescending) > 1)
{
throw new DuplicateOrderChainException();
}
Expand Down Expand Up @@ -55,8 +55,8 @@ public IEnumerable<T> Evaluate<T>(IEnumerable<T> query, ISpecification<T> specif
{
if (specification.OrderExpressions != null)
{
if (specification.OrderExpressions.Where(x => x.OrderType == OrderTypeEnum.OrderBy ||
x.OrderType == OrderTypeEnum.OrderByDescending).Count() > 1)
if (specification.OrderExpressions.Count(x => x.OrderType == OrderTypeEnum.OrderBy
|| x.OrderType == OrderTypeEnum.OrderByDescending) > 1)
{
throw new DuplicateOrderChainException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specific
{
foreach (var searchCriteria in specification.SearchCriterias.GroupBy(x => x.SearchGroup))
{
var criterias = searchCriteria.Select(x => (x.Selector, x.SearchTerm));
query = query.Search(criterias);
query = query.Search(searchCriteria);
}

return query;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;

namespace Ardalis.Specification.EntityFramework6
{
Expand All @@ -20,15 +20,18 @@ public static class SearchExtension
/// </list>
/// </param>
/// <returns></returns>
public static IQueryable<T> Search<T>(this IQueryable<T> source, IEnumerable<(Expression<Func<T, string>> selector, string searchTerm)> criterias)
public static IQueryable<T> Search<T>(this IQueryable<T> source, IEnumerable<SearchExpressionInfo<T>> criterias)
{
Expression expr = null;
var parameter = Expression.Parameter(typeof(T), "x");

foreach (var (selector, searchTerm) in criterias)
foreach (var criteria in criterias)
{
if (selector == null || string.IsNullOrEmpty(searchTerm))
var (selector, searchTerm) = (criteria.Selector, criteria.SearchTerm);
if (string.IsNullOrEmpty(criteria.SearchTerm))
{
continue;
}

var like = typeof(DbFunctions).GetMethod(nameof(DbFunctions.Like), new Type[] { typeof(string), typeof(string) });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,18 @@ public async Task ReturnsUntrackedCompany_GivenCompanyByIdAsUntrackedSpec()
result?.Name.Should().Be(CompanySeed.VALID_COMPANY_NAME);
dbContext.Entry(result).State.Should().Be(EntityState.Detached);
}

[Fact]
public async Task ReturnsStoreWithCompanyAndCountryAndStoresForCompany_GivenStoreByIdIncludeCompanyAndCountryAndStoresForCompanySpec()
{
var result = await storeRepository.GetBySpecAsync(new StoreByIdIncludeCompanyAndCountryAndStoresForCompanySpec(StoreSeed.VALID_STORE_ID));

result.Should().NotBeNull();
result.Name.Should().Be(StoreSeed.VALID_STORE_NAME);
result.Company.Should().NotBeNull();
result.Company?.Country.Should().NotBeNull();
result.Company?.Stores.Should().HaveCountGreaterOrEqualTo(2);
result.Company?.Stores?.Should().Match(x => x.Any(z => z.Products.Count > 0));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Ardalis.Specification.EntityFrameworkCore
{
/// <summary>
/// A thread-safe dictionary for read-heavy workloads.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
internal class CachedReadConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// The number of cache misses which are tolerated before the cache is regenerated.
/// </summary>
private const int CacheMissesBeforeCaching = 10;
private readonly ConcurrentDictionary<TKey, TValue> dictionary;
private readonly IEqualityComparer<TKey>? comparer;

/// <summary>
/// Approximate number of reads which did not hit the cache since it was last invalidated.
/// This is used as a heuristic that the dictionary is not being modified frequently with respect to the read volume.
/// </summary>
private int cacheMissReads;

/// <summary>
/// Cached version of <see cref="dictionary"/>.
/// </summary>
private Dictionary<TKey, TValue>? readCache;

/// <summary>
/// Initializes a new instance of the <see cref="CachedReadConcurrentDictionary{TKey,TValue}"/> class.
/// </summary>
public CachedReadConcurrentDictionary()
{
this.dictionary = new ConcurrentDictionary<TKey, TValue>();
}

/// <summary>
/// Initializes a new instance of the <see cref="CachedReadConcurrentDictionary{TKey,TValue}"/> class
/// that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">
/// The <see cref="T:IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to the new instance.
/// </param>
public CachedReadConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
{
this.dictionary = new ConcurrentDictionary<TKey, TValue>(collection);
}

/// <summary>
/// Initializes a new instance of the <see cref="CachedReadConcurrentDictionary{TKey,TValue}"/> class
/// that contains elements copied from the specified collection and uses the specified
/// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
/// </summary>
/// <param name="comparer">
/// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.
/// </param>
public CachedReadConcurrentDictionary(IEqualityComparer<TKey> comparer)
{
this.comparer = comparer;
this.dictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
}

/// <summary>
/// Initializes a new instance of the <see cref="CachedReadConcurrentDictionary{TKey,TValue}"/>
/// class that contains elements copied from the specified collection and uses the specified
/// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
/// </summary>
/// <param name="collection">
/// The <see cref="T:IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to the new instance.
/// </param>
/// <param name="comparer">
/// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.
/// </param>
public CachedReadConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
{
this.comparer = comparer;
this.dictionary = new ConcurrentDictionary<TKey, TValue>(collection, comparer);
}

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => this.GetReadDictionary().GetEnumerator();

/// <inheritdoc />
public void Add(KeyValuePair<TKey, TValue> item)
{
((IDictionary<TKey, TValue>)this.dictionary).Add(item);
this.InvalidateCache();
}

/// <inheritdoc />
public void Clear()
{
this.dictionary.Clear();
this.InvalidateCache();
}

/// <inheritdoc />
public bool Contains(KeyValuePair<TKey, TValue> item) => this.GetReadDictionary().Contains(item);

/// <inheritdoc />
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
this.GetReadDictionary().CopyTo(array, arrayIndex);
}

/// <inheritdoc />
public bool Remove(KeyValuePair<TKey, TValue> item)
{
var result = ((IDictionary<TKey, TValue>)this.dictionary).Remove(item);
if (result) this.InvalidateCache();
return result;
}

/// <inheritdoc />
public int Count => this.GetReadDictionary().Count;

/// <inheritdoc />
public bool IsReadOnly => false;

/// <inheritdoc />
public void Add(TKey key, TValue value)
{
((IDictionary<TKey, TValue>)this.dictionary).Add(key, value);
this.InvalidateCache();
}

/// <summary>
/// Adds a key/value pair to the <see cref="CachedReadConcurrentDictionary{TKey,TValue}"/> if the key does not exist.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="valueFactory">The function used to generate a value for the key</param>
/// <returns>The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new value if the key was not in the dictionary.</returns>
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
if (this.GetReadDictionary().TryGetValue(key, out var value))
{
return value;
}

value = this.dictionary.GetOrAdd(key, valueFactory);
InvalidateCache();

return value;
}

/// <summary>
/// Attempts to add the specified key and value.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add. The value can be a null reference (Nothing
/// in Visual Basic) for reference types.</param>
/// <returns>true if the key/value pair was added successfully; otherwise, false.</returns>
public bool TryAdd(TKey key, TValue value)
{
if (this.dictionary.TryAdd(key, value))
{
this.InvalidateCache();
return true;
}

return false;
}

/// <inheritdoc />
public bool ContainsKey(TKey key) => this.GetReadDictionary().ContainsKey(key);

/// <inheritdoc />
public bool Remove(TKey key)
{
var result = ((IDictionary<TKey, TValue>)this.dictionary).Remove(key);
if (result) this.InvalidateCache();
return result;
}

/// <inheritdoc />
public bool TryGetValue(TKey key, out TValue value) => this.GetReadDictionary().TryGetValue(key, out value);

/// <inheritdoc />
public TValue this[TKey key]
{
get => this.GetReadDictionary()[key];
set
{
this.dictionary[key] = value;
this.InvalidateCache();
}
}

/// <inheritdoc />
public ICollection<TKey> Keys => this.GetReadDictionary().Keys;

/// <inheritdoc />
public ICollection<TValue> Values => this.GetReadDictionary().Values;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IDictionary<TKey, TValue> GetReadDictionary() => this.readCache ?? this.GetWithoutCache();

private IDictionary<TKey, TValue> GetWithoutCache()
{
// If the dictionary was recently modified or the cache is being recomputed, return the dictionary directly.
if (Interlocked.Increment(ref this.cacheMissReads) < CacheMissesBeforeCaching)
{
return this.dictionary;
}

// Recompute the cache if too many cache misses have occurred.
this.cacheMissReads = 0;
return this.readCache = new Dictionary<TKey, TValue>(this.dictionary, this.comparer);
}

private void InvalidateCache()
{
this.cacheMissReads = 0;
this.readCache = null;
}
}
}
Loading