NeinLinq

repository·main·Indexed 19 days ago

https://github.com/axelheer/nein-linq

A library of LINQ extensions designed to overcome LINQ provider limitations, such as those in Entity Framework. It provides features including lambda injection for custom functions, null-safe queries via ToNullsafe(), predicate and selector translation between different entity types, and function substitution for unit testing. It offers specific packages for plain LINQ, Entity Framework 6, and Entity Framework Core.

Tokens
2.2K
Snippets
9
Records
9
Agent score
19%

What's inside NeinLinq

  1. How Lambda injection works

    main

    Lambda injection allows you to use custom .NET methods in LINQ queries that would otherwise fail translation (e.g., in Entity Framework).

    The Pattern:

    1. Mark the extension method with the [InjectLambda] attribute.
    2. Provide a matching Expression<Func<...>> that defines the translatable logic.
    3. Mark the query as injectable using ToInjectable() (or the flavor-specific equivalent).

    When the query is executed, NeinLinq's rewrite engine replaces the method call with the provided lambda expression, which the LINQ provider can then translate to SQL.

    [InjectLambda]
    public static string LimitText(this string value, int maxLength)
    {
        if (value != null && value.Length > maxLength)
            return value.Substring(0, maxLength);
        return value;
    }
    
    public static Expression<Func<string, int, string>> LimitText()
    {
        return (v, l) => v != null && v.Length > l ? v.Substring(0, l) : v;
    }
    
    // Usage in query:
    from d in data.ToInjectable()
    select new
    {
        Id = d.Id,
        Value = d.Name.LimitText(10)
    }
  2. Enable global Lambda injection in EF Core

    main

    With NeinLinq.EntityFrameworkCore (v5.1.0+), you can enable Lambda injection globally via DbContext configuration. The call to WithLambdaInjection() must occur after the call to UseSqlOrTheLike (or your specific provider setup).

    services.AddDbContext<MyContext>(options =>
         options.UseSqlOrTheLike("...").WithLambdaInjection());
  3. Install NeinLinq and its flavors

    main

    NeinLinq provides extensions for LINQ providers (like Entity Framework) that support only a subset of .NET functions. Choose the package that matches your LINQ provider:

    • Plain LINQ: Install-Package NeinLinq
    • Entity Framework 6: Install-Package NeinLinq.EntityFramework
    • Entity Framework Core: Install-Package NeinLinq.EntityFrameworkCore

    Important: The extension method names differ by package to avoid conflicts:

    • ToInjectable() for NeinLinq
    • ToDbInjectable() for NeinLinq.EntityFramework
    • ToEntityInjectable() for NeinLinq.EntityFrameworkCore

    For EF6/EFCore, using the specific flavor is highly recommended to ensure async queries work correctly.

    # For plain LINQ
    Install-Package NeinLinq
    
    # For Entity Framework 6
    Install-Package NeinLinq.EntityFramework
    
    # For Entity Framework Core
    Install-Package NeinLinq.EntityFrameworkCore
  4. Translate predicates between different entities

    main

    NeinLinq allows you to translate a predicate designed for one entity type to work on another, such as moving a predicate from a parent to a child or vice versa. This avoids using .Invoke(), which many LINQ providers do not support.

    • Parent to Child: Use .Translate().To<ChildType>((child, predicate) => child.Relation.Any(predicate)).
    • Child to Parent: Use .Translate().To<ParentType>(parent => parent.Relation).
    // Example: Translating a Lecture predicate to work on a Course query
    Expression<Func<Lecture, bool>> p = l => ...;
    
    db.Courses.Where(p.Translate()
                      .To<Course>((c, q) => c.Lectures.Any(q)))
  5. Configure custom providers for InjectLambdaAttribute

    main

    Starting with version 7.0.0, you can define custom logic to determine which methods are injectable without using the [InjectLambda] attribute. This is useful for external libraries. You can set a custom provider using InjectLambdaAttribute.SetAttributeProvider.

    var oldProvider = InjectLambdaAttribute.Provider;
    
    InjectLambdaAttribute.SetAttributeProvider(memberInfo =>
    {
        // Your custom logic here
        if (memberInfo.Name.StartsWith("ExternalMethod"))
        {
            return new InjectLambdaAttribute(typoef(MyType), nameof(MyType.ExternalMethodExpression));
        }
        // fallback to standard provider
        return oldProvider(memberInfo);
    });
  6. Substitute functions in queries

    main

    You can replace an entire class of functions (like SqlFunctions in Entity Framework) with another implementation (like a FakeFunctions class) for unit testing or different database providers using ToSubstitution().

    var query = ...;
    
    // Replace SqlFunctions with FakeFunctions
    CallCodeUsingSqlFunctions(query
        .ToSubstitution(typeof(SqlFunctions), typeof(FakeFunctions)));
  7. Use null-safe queries with ToNullsafe()

    main

    To avoid manual null checks in LINQ queries (which can make code verbose and SQL inefficient), use the ToNullsafe() extension method. This allows you to write cleaner queries that automatically handle potential null references during translation.

    from a in data.ToNullsafe()
    orderby a.SomeInteger
    select new
    {
        Year = a.SomeDate.Year,
        Integer = a.SomeOther.SomeInteger,
        Others = from b in a.SomeOthers
                 select b.SomeDate.Month,
        More = from c in a.MoreOthers
               select c.SomeOther.SomeDate.Day
    }
  8. Reuse and combine selectors with Selector translation

    main

    You can reuse existing selectors (e.g., for DTOs or ViewModels) across different entity types using the selector translation API. This is useful for inheritance hierarchies or parent-child relationships.

    Common Workflow:

    1. Start with an existing selector: s.Translate().
    2. Define the relationship/cross-type: .Cross<TargetType>(...).
    3. Apply additional logic: .Apply(...).

    Example: Reusing a base selector for a specialized type:

    db.Academies.OfType<SpecialAcademy>()
                .Select(s.Translate()
                         .Cross<SpecialAcademy>()
                         .Apply(t));
    // Example: Reusing a parent selector for a child relation
    Expression<Func<Academy, AcademyView>> s = a => new AcademyView { Id = a.Id, Name = a.Name };
    Expression<Func<Course, CourseView>> t = c => new CourseView { Id = c.Id, Name = c.Name };
    
    // Translate from parent to child
    db.Courses.Select(s.Translate()
                       .Cross<Course>(c => c.Academy)
                       .Apply(c => c.Academy, t));
  9. Perform dynamic query filtering and sorting

    main

    For scenarios where you need to filter or sort based on non-type-safe user input (strings), use the DynamicCompare and DynamicQuery helpers. This allows you to build queries based on property names and comparison types while maintaining as much type safety as possible.

    // Simple filtering and sorting
    var query = data.Where("Name.Length", DynamicCompare.GreaterThan, "7")
                    .OrderBy("Name").ThenBy("Number", descending: true);
    
    // Combining with predicate translation
    var p = DynamicQuery.CreatePredicate<Whatever>("Name", "Contains", "p");
    var q = DynamicQuery.CreatePredicate<Whatever>("Name", "Contains", "q");
    var query = data.Where(p.Or(q));