C# LinqHelper

Date: 2021-08-03
public static class LinqHelper
{
    public static IEnumerable<T> Select<T>(params T[] args) => args;
    
    public static IEnumerable<T> SelectMany<T>(params IEnumerable<T>[] args) => args.SelectMany(x => x);

    public static List<T> List<T>(params T[] args) => args.ToList();
    
    public static IEnumerable<IEnumerable<T>> Split<T>(IEnumerable<T> src, int size) => src.Where((x, i) => i % size == 0).Select((x, i) => src.Skip(i * size).Take(size));
    
    public static bool HasSharedItem<T>(IEnumerable<T> left, IEnumerable<T> right) => left.Any(l => right.Any(r => l != null && r != null && l.Equals(r)));

    public static T GetValueIfExactOne<T>(IEnumerable<T> values, T defValue = default)
    {
        var distinct = values.Distinct();
        return distinct.Count() == 1 ? distinct.First() : defValue;
    }

    public static async Task<IEnumerable<T>> FromAsync<T>(IAsyncEnumerable<T> items)
    {
        var list = new List<T>();
        await foreach (var item in items) { list.Add(item); }
        return list;
    }
    
    public static async Task<IEnumerable<U>> FromAsync<T, U>(IAsyncEnumerable<T> items, Func<T, U> converter)
    {
        var list = new List<U>();
        await foreach (var item in items) { list.Add(converter(item)); }
        return list;
    }
    
    public static T MinOrDefault<T>(this IEnumerable<T> sequence, T defValue = default(T)) => sequence.Any() ? sequence.Min() : defValue;
    public static T MaxOrDefault<T>(this IEnumerable<T> sequence, T defValue = default(T)) => sequence.Any() ? sequence.Max() : defValue;
    
    public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self.Select((item, index) => (item, index));

    // filter where conditions in (Entity Framework) queries based on condition e.g:
    // AsQueryable().WhereIf(onlyActiveItems, x => x.Active).ToListAsync();
    public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, bool condition, Func<TSource, bool> predicate) where TSource : class
    {
        return condition ? source.Where(predicate) : source;
    }

    public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, bool>> predicate) where TSource : class
    {
        return condition ? source.Where(predicate) : source;
    }

    public static IQueryable<TSource> IncludeIf<TSource, TProperty>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, TProperty>> propertySelector) where TSource : class
    {
        return condition ? source.Include(propertySelector) : source;
    }

    public static IQueryable<TSource> SkipIf<TSource>(this IQueryable<TSource> source, bool condition, int quantityToSkip) where TSource : class
    {
        return condition ? source.Skip(quantityToSkip) : source;
    }
    
    public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> recursiveSelector)
    {
        foreach (var i in source)
        {
            yield return i;

            var directChildren = recursiveSelector(i);
            var allChildren = SelectRecursive(directChildren, recursiveSelector);

            foreach (var c in allChildren)
            {
                yield return c;
            }
        }
    }

    public static IQueryable<TSource> TakeIf<TSource>(this IQueryable<TSource> source, bool condition, int quantityToTake) where TSource : class
    {
        return condition ? source.Take(quantityToTake) : source;
    }
    
    public static Dictionary<Y, T> ToDictionarySafe<T, Y>(this IEnumerable<T> items, Func<T, Y> keySelector) => items.GroupBy(x => keySelector(x)).Where(x => x.Key != null).Select(x => x.First()).ToDictionary(x => keySelector(x));
    
	// Comparable with: .GroupBy(x => x.Property).Select(x => x.First())
	// but possibly faster and with yield return
	// update: function exists in dotnet 6
	public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
	{
		var seenKeys = new HashSet<TKey>();
		foreach (TSource element in source)
			if (seenKeys.Add(keySelector(element)))
				yield return element;
	}
	
    public static bool ContainsDuplicates<T>(this IEnumerable<T> enumerable)
    {
        var knownKeys = new HashSet<T>();
        return enumerable.Any(item => !knownKeys.Add(item));
    }

    public static bool None<T>(this IEnumerable<T> list) => !list.Any();
        
	public static IEnumerable<int> GetRange(int min, int max)
	{
		for (var i = min; i <= max; i++)
			yield return i;
	}

	public static List<List<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> list)
	{
		return list
			.SelectMany(inner => inner.Select((item, index) => new { item, index }))
			.GroupBy(i => i.index, i => i.item)
			.Select(g => g.ToList())
			.ToList();
	}

}

WhereIf example

// Original: query with separate if statements
public async Task<IEnumerable<PurchaseContractExpectedAndDelivered>> GetExpectedAndDeliveredQuantitiesForContract(PurchaseQuantityFilter filter)
{
	using var unitOfWork = GetUnitOfWork();

	var query = Query<DmPurchaseContractLine>(unitOfWork);
	
	if (filter.PurchaseContractId != null)
		query = query.Where(x => x.PurchaseContractId == filter.PurchaseContractId)
		
	if (filter.PurchaseContractLineId != null)
		query = query.Where(x => x.Id == filter.PurchaseContractLineId);

	var selectQuery = query.Select(x => new PurchaseContractExpectedAndDelivered
	{
		PurchaseContractId = x.PurchaseContractId,
		PurchaseContractLineId = x.Id,
		ExpectedKg = x.WeightKg,
		DeliveredKg = x.PurchaseShipments.Sum(x => x.WeightKg),
		DeliveredBoxes = x.PurchaseShipments.Sum(x => x.Boxes),
	});

	return await selectQuery.ToListAsync();
}

// With an WhereIf: query as single statement
public async Task<IEnumerable<PurchaseContractExpectedAndDelivered>> GetExpectedAndDeliveredQuantitiesForContract(PurchaseQuantityFilter filter)
{
	using var unitOfWork = GetUnitOfWork();

	var query = Query<DmPurchaseContractLine>(unitOfWork)
		.WhereIf(filter.PurchaseContractId != null, x => x.PurchaseContractId == filter.PurchaseContractId)
		.WhereIf(filter.PurchaseContractLineId != null, x => x.Id == filter.PurchaseContractLineId);
        .Select(x => new PurchaseContractExpectedAndDelivered
    	{
    		PurchaseContractId = x.PurchaseContractId,
    		PurchaseContractLineId = x.Id,
    		ExpectedKg = x.WeightKg,
    		DeliveredKg = x.PurchaseShipments.Sum(x => x.WeightKg),
    		DeliveredBoxes = x.PurchaseShipments.Sum(x => x.Boxes),
    	});

	return await query.ToListAsync();
}

See also:

52160cookie-checkC# LinqHelper