GithubHelp home page GithubHelp logo

axelheer / nein-linq Goto Github PK

View Code? Open in Web Editor NEW
500.0 18.0 21.0 1.81 MB

NeinLinq provides helpful extensions for using LINQ providers such as Entity Framework that support only a minor subset of .NET functions, reusing functions, rewriting queries, even making them null-safe, and building dynamic queries using translatable predicates and selectors.

License: MIT License

C# 100.00%
linq entity-framework dynamic-queries c-sharp csharp dotnet dotnet-core

nein-linq's Introduction

NeinLinq

Latest package Download tracker GitHub status Code coverage

NeinLinq provides helpful extensions for using LINQ providers such as Entity Framework that support only a minor subset of .NET functions, reusing functions, rewriting queries, even making them null-safe, and building dynamic queries using translatable predicates and selectors.

To support different LINQ implementations, the following flavours are available. Choose at least one.

Use NeinLinq for plain LINQ queries:

PM> Install-Package NeinLinq

Use NeinLinq.Async for async LINQ queries:

PM> Install-Package NeinLinq.Async

Use NeinLinq.EntityFramework for Entity Framework 6 LINQ queries:

PM> Install-Package NeinLinq.EntityFramework

Use NeinLinq.EntityFrameworkCore for Entity Framework Core LINQ queries:

PM> Install-Package NeinLinq.EntityFrameworkCore

Note: the extension methods described below have different names depending on the chosen package above, in order to avoid some conflicts! For example there are:

  • ToInjectable (NeinLinq)
  • ToAsyncInjectable (NeinLinq.Async)
  • ToDbInjectable (NeinLinq.EntityFramework)
  • ToEntityInjectable (NeinLinq.EntityFrameworkCore)

Usage of specific flavors is encouraged for EF6 / EFCore (otherwise async queries won't work).

New: with Version 5.1.0 the package NeinLinq.EntityFrameworkCore introduced an explicit DbContext extension for enabling Lambda injection globally:

    services.AddDbContext<MyContext>(options =>
         options.UseSqlOrTheLike("...").WithLambdaInjection());

Note: the call to WithLambdaInjection needs to happen after the call to UseSqlOrTheLike!

Lambda injection

Many LINQ providers can only support a very minor subset of .NET functionality, they even cannot support our own "functions". Say, we implement a simple method LimitText and use it within an ordinary LINQ query, which will get translated to SQL through Entity Framework...

LINQ to Entities does not recognize the method 'System.String LimitText(System.String, Int32)' method, and this method cannot be translated into a store expression.

This is what we get; in fact, it's really annoying. We have to scatter our logic between code, that will be translated by any LINQ query provider, and code, that won't. It gets even worse: if some logic is "translatable", which is good, we have to copy and paste! Consolidating the code within an ordinary function does not work since the provider is unable to translate this simple method call. Meh.

Let us introduce "lambda injection":

[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;
}

// -------------------------------------------------------------------

from d in data.ToInjectable()
select new
{
    Id = d.Id,
    Value = d.Name.LimitText(10)
}

If a query is marked as "injectable" (ToInjectable()) and a function used within this query is marked as "inject here" ([InjectLambda]), the rewrite engine of NeinLinq replaces the method call with the matching lambda expression, which can get translate to SQL or whatever. Thus, we are able to encapsulate unsupported .NET functionality and even create our own. Yay.

[InjectLambda]
public static bool Like(this string value, string likePattern)
{
    throw new NotImplementedException();
}

public static Expression<Func<string, string, bool>> Like()
{
    return (v, p) => SqlFunctions.PatIndex(p, v) > 0;
}

// -------------------------------------------------------------------

from d in data.ToInjectable()
where d.Name.Like("%na_f%")
select ...

This is an example of how we can abstract the SqlFunctions class of Entity Framework to use a (hopefully) nicer Like extension method within our query code -- PatIndex is likely used to simulate a SQL LIKE statement, why not make it so? We can actually implement the "ordinary" method with the help of regular expressions to run our code without touching SqlFunctions too...

Finally, let us look at this query using Entity Framework or the like:

from d in data.ToInjectable()
let e = d.RetrieveWhatever()
where d.FulfillsSomeCriteria()
select new
{
    Id = d.Id,
    Value = d.DoTheFancy(e)
}

// -------------------------------------------------------------------

[InjectLambda]
public static Whatever RetrieveWhatever(this Entity value)
{
    throw new NotImplementedException();
}

public static Expression<Func<Entity, Whatever>> RetrieveWhatever()
{
    return d => d.Whatevers.FirstOrDefault(e => ...);
}

[InjectLambda]
public static bool FulfillsSomeCriteria(this Entity value)
{
    throw new NotImplementedException();
}

public static Expression<Func<Entity, bool>> FulfillsSomeCriteria()
{
    return d => ...
}

[InjectLambda]
public static decimal DoTheFancy(this Entity value, Whatever other)
{
    throw new NotImplementedException();
}

public static Expression<Func<Entity, Whatever, decimal>> DoTheFancy()
{
    return (d, e) => ...
}

The methods RetrieveWhatever, FulfillsSomeCriteria and DoTheFancy should be marked accordingly, using the attribute [InjectLambda] or just the simple convention "same class, same name, matching signature" (which requires the class to be green listed by the way). And the call ToInjectable can happen anywhere within the LINQ query chain, so we don't have to pollute our business logic.

Note: code duplication should not be necessary. The ordinary method can just compile the expression, ideally only once. A straightforward solution can look like the following code sample (it's possible to encapsulate / organize this stuff however sophisticated it seems fit, NeinLinq has no specific requirements; feel free to use the build-in "Expression Cache" or build something fancy...):

public static CachedExpression<Func<string, int, string>> LimitTextExpr { get; }
    = new((v, l) => v != null && v.Length > l ? v.Substring(0, l) : v)

[InjectLambda]
public static string LimitText(this string value, int maxLength)
    => LimitTextExpr.Compiled(value, maxLength);

Advanced: that works with instance methods too, so the actual expression code is able to retrieve additional data. Even interfaces and / or base classes can be used to abstract all the things. Thus, we can declare an interface / base class without expressions, but provide the expression to inject using inheritance.

public class ParameterizedFunctions
{
    private readonly int narf;

    public ParameterizedFunctions(int narf)
    {
        this.narf = narf;
    }

    [InjectLambda("FooExpr")]
    public string Foo()
    {
        ...
    }

    public Expression<Func<string>> FooExpr()
    {
        ... // use the narf!
    }
}

// -------------------------------------------------------------------

public interface IFunctions
{
    [InjectLambda]
    string Foo(Entity value); // use abstraction for queries
}

public class Functions : IFunctions
{
    [InjectLambda]
    public string Foo(Entity value)
    {
        ...
    }

    public Expression<Func<Entity, string>> Foo()
    {
        ...
    }
}

Note: injecting instance methods is not as efficient as injecting static methods. Just don't use the former ones, if not really necessary. Furthermore, injecting instance methods of a sealed type reduces the overhead a bit, since there are more things that only need to be done once. Okay, nothing new to say here.

One more thing: to be more hacky and use less extension method ceremony, it's possible to inject properties directly within in models.

public class Model
{
    public double Time { get; set; }

    public double Distance { get; set; }

    [InjectLambda]
    public double Velocity => Distance / Time;

    public static Expression<Func<Model, double>> VelocityExpr => v => v.Distance / v.Time;
}

Again, instead of placing [InjectLambda] on everything it's possible to add all the models to the green-list while calling ToInjectable.

Null-safe queries

We are writing the year 2024 and still have to worry about null values.

Howsoever, we got used to it and we are fine. But writing queries in C# loaded with null checks doesn't feel right, it just looks awful, the translated SQL even gets worse. A LINQ query just for SQL dbs can spare these null checks, a LINQ query just for in-memory calculations must include them. And a LINQ query for both has a problem (unit testing?), which NeinLinq tries to solve.

The following query may trigger null references:

from a in data
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
}

While the following query should not:

from a in data
where a != null
orderby a.SomeInteger
select new
{
    Year = a.SomeDate.Year,
    Integer = a.SomeOther != null
            ? a.SomeOther.SomeInteger
            : 0,
    Others = a.SomeOthers != null
           ? from b in a.SomeOthers
             select b.SomeDate.Month
           : null,
    More = a.MoreOthers != null
         ? from c in a.MoreOthers
           select c.SomeOther != null
                ? c.SomeOther.SomeDate.Day
                : 0
         : null
}

Maybe we've forgot some check? Or we can relax thanks to NeinLinq:

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
}

As with every ToWhatever helper within NeinLinq, ToNullsafe can be called wherever within the LINQ query chain.

Predicate translator

Many data driven applications need to build some kind of dynamic queries. This can lead to dirty string manipulations, complex expression tree plumbing, or a combination of those. Simple and/or-conjunctions are already solved within other libraries, but conjunctions of "foreign" predicates are not that easy.

Let us think of three entities: Academy has Courses, Courses has Lectures.

Expression<Func<Course, bool>> p = c => ...
Expression<Func<Course, bool>> q = c => ...

db.Courses.Where(p.And(q))...

Ok, we already know that.

Expression<Func<Academy, bool>> p = a => ...

db.Courses.Where(p.Translate()
                  .To<Course>(c => c.Academy))...

We now can translate a (combined) predicate for a parent entity...

Expression<Func<Lecture, bool>> p = l => ...

db.Courses.Where(p.Translate()
                  .To<Course>((c, q) => c.Lectures.Any(q)))...

..and even for child entities.

Let us use all of this as a windup:

IEnumerable<Expression<Func<Academy, bool>>> predicatesForAcademy = ...
IEnumerable<Expression<Func<Course, bool>>> predicatesForCourse = ...
IEnumerable<Expression<Func<Lecture, bool>>> predicatesForLecture = ...

var singlePredicateForAcademy =
    predicatesForAcademy.Aggregate((p, q) => p.And(q));
var singlePredicateForCourse =
    predicatesForCourse.Aggregate((p, q) => p.And(q));
var singlePredicateForLecture =
    predicatesForLecture.Aggregate((p, q) => p.And(q));

var academyPredicateForCourse =
    singlePredicateForAcademy.Translate()
                             .To<Course>(c => c.Academy);
var coursePredicateForCourse =
    singlePredicateForCourse; // the hard one ^^
var lecturePredicateForCourse =
    singlePredicateForLecture.Translate()
                             .To<Course>((c, p) => c.Lectures.Any(p));

var finalPredicate =
    academyPredicateForCourse.And(coursePredicateForCourse)
                             .And(lecturePredicateForCourse);

db.Courses.Where(finalPredicate)...

In addition to it, no Invoke is used to achieve that: many LINQ providers do not support it (Entity Framework, i'm looking at you...), so this solution should be quite compatible.

Selector translator

As with predicates selectors need some love too. If we've an existing selector for some base type and want to reuse this code for one or more concrete types, we're forced to copy and paste again. Don't do that!

Let us think of two entities (Academy and SpecialAcademy) with according Contracts / ViewModels / DTOs / Whatever (AcademyView and SpecialAcademyView).

Expression<Func<Academy, AcademyView>> s =
    a => new AcademyView { Id = a.Id, Name = a.Name };
Expression<Func<SpecialAcademy, SpecialAcademyView>> t =
    a => new SpecialAcademyView { Narf = a.Narf };

Note that we omit the Member bindings of the first selector within the second one. Don't repeat yourself, remember?

db.Academies.OfType<SpecialAcademy>()
            .Select(s.Translate()
                     .Cross<SpecialAcademy>()
                     .Apply(t));

Although there're more options, the common scenario can look that way: reuse the base selector, start it's translation (type inference), say where to start (no type inference), and finally apply the additional selector (type inference, again).

Now let us consider parent / child relations (Academy and Course).

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 };

db.Courses.Select(s.Translate()
                   .Cross<Course>(c => c.Academy)
                   .Apply(c => c.Academy, t));

db.Academies.Select(t.Translate()
                     .Cross<Academy>((a, v) => a.Courses.Select(v))
                     .Apply(a => a.Courses, s));

Again, apart from other options, we can translate from parent to child: reuse the parent selector, start it's translation, say where to start (given the path to it's parent entity), and finally apply the additional selector (given the path to it's parent "view"). And we can translate the other way too: reuse the child selector, start it's translation, say where to start (given an expression to select the children), and finally apply the additional selector...

To be more flexible the "Source translation" / "Result translation" can be used individually:

Expression<Func<Academy, AcademyView>> selectAcademy =
    a => new AcademyView { Id = a.Id, Name = a.Name };

var selectCourseWithAcademy =
    selectAcademy.Translate()
                 .Source<Course>(c => c.Academy)
                 .Translate()
                 .Result<CourseView>(c => c.Academy)
                 .Apply(a => new CourseView
                 {
                     Id = a.Id,
                     Name = a.Name
                 });

Note: to be less verbose "Source translation" / "Result translation" can be used within a single bloated statement, if appropriate:

Expression<Func<Course, CourseView>> selectCourse =
    c => new CourseView { Id = c.Id, Name = c.Name };

var selectAcademyWithCourses =
    selectCourse.Translate()
                .To<Academy, AcademyView>((a, v) => new AcademyView
                {
                    Id = a.Id,
                    Name = a.Name,
                    Courses = a.Courses.Select(v)
                })

Note: for parent / child relations the less dynamic but (maybe) more readable Lambda injection is also an option: just encapsulate the selector as a nice extension method.

Function substitution

This is a really dead simple one. Maybe we should've started here.

Just think of helper functions like the SqlFunctions class provided by Entity Framework. And we need to replace the whole class for unit testing or whatsoever.

var query = ...

CallCodeUsingSqlFunctions(query
    .ToSubstitution(typeof(SqlFunctions), typeof(SqlCeFunctions)));
CallCodeUsingSqlFunctions(query
    .ToSubstitution(typeof(SqlFunctions), typeof(FakeFunctions)));
...

That's it.

Custom query manipulation

Okay, you can use the generic rewrite mechanism of this library to intercept LINQ queries with your own Expression visitor. The code behind the substitution above should provide a good example.

Dynamic query filtering / sorting

At some point it may be necessary to filter / sort an almost ready query based on user input, which is by its nature not type safe but text based. To handle these scenarios as well a (very) simple helper is included.

var query = data.Where("Name.Length", DynamicCompare.GreaterThan, "7")
                .OrderBy("Name").ThenBy("Number", descending: true);

It's possible to combine this stuff with the predicate translations above.

var p = DynamicQuery.CreatePredicate<Whatever>("Name", "Contains", "p");
var q = DynamicQuery.CreatePredicate<Whatever>("Name", "Contains", "q");

var query = data.Where(p.Or(q));

Note: if you're seeking a possibility to create complex queries based on string manipulation, this won't help. The goal of this library is to stay type safe as long as possible.

nein-linq's People

Contributors

axelheer avatar peterwurzinger avatar rostunic avatar shaddix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nein-linq's Issues

Extending select functionality of Entity Framework

Hi,

I was hoping to use your software to apply it to the following problem. Sadly it seems I get the same error: "The specified type member 'Test' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.".

Is there anything I'm doing wrong?

    public class Status
    {
        [Key]
        public int Id { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public int SortKey { get; set; }

        [InjectLambda]
        public string Test
        {
            get { return $"{Description} {SortKey}"; }
            set { }
        }
    }
    dbSet
        .ToInjectable()
        .Select(s => new { s.Test })
        .ToList();

Issue with Moq

Hi, I have a NRE at System.Data.Entity.Internal.Linq.DbQueryVisitor.ExtractObjectQuery(Object dbQuery).
The problem appeared after the fix in 6.2.0.

Framework: net4.8
EntityFramework: 6.4.4
Moq: 4.18.4
NeinLinq: 6.2.0
NeinLinq.EntityFramework: 6.2.0

Code to reproduce

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using Moq;
using Xunit;

namespace NeinLinq.Tests;

public class SomeTest
{
    [Fact]
    public void Test_Moq_WithLambdaInjection()
    {
        var items = new List<TodoItem>()
        {
            new() { Id = 1, ListId = 1 },
            new() { Id = 2, ListId = 1 }
        };

        var todoLists = new List<TodoList>()
        {
            new() { Id = 1, Items = items }
        };

        Mock<DbContext> dbContextMock = new();
        dbContextMock.Setup(dc => dc.Set<TodoList>())
            .Returns(MockDbSetCreator.GetMockDbSet(todoLists));
        dbContextMock.Setup(dc => dc.Set<TodoItem>())
            .Returns(MockDbSetCreator.GetMockDbSet(items));
        DbContext dbContext = dbContextMock.Object;

        var queryProvider = new GetTodoItemsQueryProvider();

        var query = (from list in dbContext.Set<TodoList>().ToDbInjectable()
            let item = queryProvider.GetQuery(dbContext, list.Id).FirstOrDefault()
            select item);

        var result = query.ToList();

        Assert.NotEmpty(result);

        int count = query.Count();

        Assert.NotEqual(0, count);
    }
}

public class TodoList
{
    public int Id { get; set; }

    public virtual ICollection<TodoItem> Items { get; set; } = new List<TodoItem>();
}

public class TodoItem
{
    public int Id { get; set; }

    public int ListId { get; set; }

    public virtual TodoList? List { get; set; }
}

public interface IQueryProvider<DbContext, TParam, TResult>
{
    [InjectLambda]
    IQueryable<TResult> GetQuery(DbContext context, TParam query);
}

public class GetTodoItemsQueryProvider : IQueryProvider<DbContext, int, TodoItem>
{
    [InjectLambda(nameof(GetQueryExpression))]
    public IQueryable<TodoItem> GetQuery(DbContext context, int query)
    {
        throw new NotImplementedException();
    }

    public Expression<Func<DbContext, int, IQueryable<TodoItem>>> GetQueryExpression()
    {
        return (context, query) => from todoItem in context.Set<TodoItem>()
            where todoItem.ListId == query
            select todoItem;
    }
}

public static class MockDbSetCreator
{
    public static DbSet<T> GetMockDbSet<T>(IEnumerable<T> collection) where T : class
    {
        var dbSet = new Mock<DbSet<T>>();

        dbSet.As<IQueryable<T>>().Setup(m => m.Provider)
            .Returns(collection.AsQueryable().Provider);

        dbSet.As<IQueryable<T>>().Setup(m => m.Expression)
            .Returns(collection.AsQueryable().Expression);

        dbSet.As<IQueryable<T>>().Setup(m => m.ElementType)
            .Returns(collection.AsQueryable().ElementType);

        dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
            .Returns(collection.AsQueryable().GetEnumerator());

        return dbSet.Object;
    }
}

Stacktrace

System.NullReferenceException: Object reference not set to an instance of an object.

System.NullReferenceException
Object reference not set to an instance of an object.
   at System.Data.Entity.Internal.Linq.DbQueryVisitor.ExtractObjectQuery(Object dbQuery)
   at System.Data.Entity.Internal.Linq.DbQueryVisitor.CreateObjectQueryConstant(Object dbQuery)
   at System.Data.Entity.Internal.Linq.DbQueryVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitNew(NewExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at NeinLinq.RewriteQueryProvider.RewriteQuery[TElement](Expression expression)
   at NeinLinq.RewriteQueryable`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at NeinLinq.Tests.SomeTest.Test_Moq_WithLambdaInjection() in \TestProject1\Tests.cs:line 40

I believe the problem is in the MockDbSetCreator, mb I need to rewrite it some way?

NeinLinq injects hiding member in injectable member of declaring type

new is evil
- A wise man

When a base class instance-member declares itself to be injectable, NeinLinq would also inject a member of a derived class, that overrides the base member.
While this behavior conforms to the behavior of the CLR for virtual members, it does not for members that were hidden by using new.

FunctionsBase.cs:

[InjectLambda(nameof(VelocityCalculationExpression))]
public double VelocityCalculation(Dummy value)
{
    throw new NotSupportedException();
}

public Expression<Func<Dummy, double>> VelocityCalculationExpression()
{
    return v => v.Distance / v.Time;
}

ConcreteFunctions.cs

public new Expression<Func<Dummy, double>> VelocityCalculationExpression()
{
    throw new InvalidOperationException();
}

ConcreteQueryTests.cs:

[Fact]
public void ShouldSucceedWithHiddenSibling()
{
    var query = from d in data.ToInjectable()
	            select ((FunctionsBase)functions).VelocityCalculation(d);

    var result = query.ToList();
	
    Assert.Equal(new[] { 200, .0, .1 }, result);
}

^ Fails by throwing the Exception.

  • For virtual (and therefore also for abstract) members the current behavior conforms to the behavior of the CLR
  • For nonvirtual-members that are hidden with new, the current behavior is wrong, when the target type of the injectable method is not the hiding type.
    • Note: I have no clue if that also applies for types, that derive from the hiding type - most likely it does. (Imho ones HD should get formatted by the compiler when trying to do that, but it's valid .NET-Code and NeinLinq should define how to deal with it.)

Completely conforming to the behavior of the CLR could end up in a lookup hell with a major influence on performance. Since using new in domain design, the main use of NeinLinq, is considered to be an anti-pattern anyway, I think it would be sufficient to generally prohibit the injection of hiding members by throwing an exception if that is attempted.

Documentation: Can you explain the difference between classic and interactive queries

Thank you for this library! It fixes a functionality gap that I have tried to overcome, and have never managed in so eloquent a way.

In the readme, you refer to "classic" and "interative" LINQ queries, but don't explain what the difference is.

Would it be possible to add a note so that people like me (who clearly don't understand expression trees as fully as you) can understand the difference between the two?

Thanks

.CountAsync() and .FirstAsync() throws exception when using ToInjectable

The following code

var users = await _dbContext.Users
                .ToInjectable()
                .Select(x => new UserDto())
                .CountAsync();

throws

System.InvalidOperationException: The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchr
onous operations.
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancella
tionToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

The same exception is for .FirstAsync() and .FirstOrDefaultAsync().
On the contrary, .Count(), .ToList() or even .ToListAsync() work ok.

I'm using NeinLinq.EntityFrameworkCore version 4.1.0.

Property vs method issue

This works:

[InjectLambda]
public decimal Total => TotalExpr.Compiled(this); // **Property**

private static CachedExpression<Func<Order, decimal>> TotalExpr { get; }
= new(order => order.Rows.Sum(row => row.Total));

But changing the property to a method throws Unable to retrieve lambda expression from FooBarWhatever: returns no lambda expression:

[InjectLambda]
public decimal Total() => TotalExpr.Compiled(this); // **Method**

private static CachedExpression<Func<Order, decimal>> TotalExpr { get; }
= new(order => order.Rows.Sum(row => row.Total));

In my case, the usage is nested in a basic projection. Giving Nein-Linq some clues in the form of: [InjectLambda(nameof(TotalExpr))] gives us the following: Unable to retrieve lambda expression from FooBarWhatever.TotalExpr: non-static implementation expected.

Any thoughts?

Issue with NeinLinq.EntityFramework, Linq.Count() method

Hi, I have a problem with the Count method in NeinLinq.EntityFramework

Framework: net4.8
EntityFramework: 6.4.4
NeinLinq: 6.1.1
NeinLinq.EntityFramework: 6.1.1

The code looks like this:
I have a query provider:

public interface IQueryProvider<DbContext, TParam, TResult>
{
    [InjectLambda]
    IQueryable<TResult> GetQuery(DbContext context, TParam query);
}

public class GetTodoItemsQueryProvider : IQueryProvider<ApplicationDbContext, int, TodoItem>
{
    [InjectLambda(nameof(GetQueryExpression))]
    public IQueryable<TodoItem> GetQuery(ApplicationDbContext context, int query)
    {
        throw new NotImplementedException();
    }

    public Expression<Func<ApplicationDbContext, int, IQueryable<TodoItem>>> GetQueryExpression()
    {
        return (context, query) => from todoItem in context.Set<TodoItem>()
            where todoItem.ListId == query
            select todoItem;
    }
}

and code which query database and calls this query provider

[HttpGet("test")]
public ActionResult Test()
{
    List<TodoItem> result = (from list in _dbContext.Set<TodoList>().ToDbInjectable()
        let item = _queryProvider.GetQuery(_dbContext, list.Id).FirstOrDefault()
        select item).ToList();

    int count = (from list in _dbContext.Set<TodoList>().ToDbInjectable()
        let item = _queryProvider.GetQuery(_dbContext, list.Id).FirstOrDefault()
        select item).Count();

  return true;
}

the first request(with ToList) works without any problems
on second request I got an exception

LINQ to Entities does not recognize the method 'System.Data.Entity.DbSet`1[TodoList] Set[TodoList]()' method, and this method cannot be translated into a store expression.

Same code on NeinLinq.EntityFrameworkCore works fine, pls help

Implement IDbAsyncQueryProvider

Hi,
using neinlinq with async operation I am getting the following error:

The provider for the source IQueryable doesn't implement IDbAsyncQueryProvider. Only providers that implement IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations

Are you missing to Implement IDbAsyncQueryProvider interface?

PS: I am using EF 6.4.4

Thanks in advance !

[Help] Translating from LinqKit Invoke + Expand to NeinLinq

Hi, please feel free to close this if you feel I should ask this somewhere else.

Today I'm using LinqKit to be able to reuse some Expression for selecting from collections, but I would like if I could only use NeinLinq since I want the Apply feature of it. I have tried for a long time now, but I can't figure out how to translate this to NeinLinq:

Base expression that should be reused:

    internal static class MapperForDTO
    {
        internal static Expression<Func<Subject, EducationEvent, PeriodData, MyStudentsListDTO>> ToDTO =>
            (subject, educationEvent, _) => new MyStudentsListDTO
            {
                Id = subject.Id,
                School Name = educationEvent.Name,
                //more stuff here
            };
    }

Code to use it for the specified entity:

Expression<Func<EducationEvent, MyStudentsListDTO>> mapper =
    educationEvent => MapperForDTO.ToDTO.Invoke(educationEvent.Subject, educationEvent, null);
var selectExpression = mapper.Expand();

Code for the second entity using the same base expression:

Expression<Func<PeriodData, MyStudentsListDTO>> mapper =
    periodData => MapperForDTO.ToDTO.Invoke(periodData.Subject, periodData.EducationEvent, periodData);
var selectExpression = mapper.Expand();

It might be that I'm just thinking about this the wrong way but any help would be appreciated.

Adding null check to a projection expression

Let's say I have an expression like this:

Expression<Func<Dummy, DummyView>> s = d => 
    new DummyView { Id = d.Id, Name = d.Name };

I'm looking for a tool, to convert s to a resulting expression like this:

Expression<Func<Dummy, DummyView>> r = d => 
    d == null ? null : new DummyView { Id = d.Id, Name = d.Name };

How can I do that? Do you have a method for this operation? Thank you!

I get this error when using member methods "variable 'r' of type 'MyModel' referenced from scope '', but it is not defined"

If I try to inject a member method like this, I get the error in the title.

        [InjectLambda]
        public static string GetParentDescription()
        {
            return GetParentDescriptionExpr
                .Compile()
                .Invoke(r);
        }
        public static Expression<Func<Resource, string>> GetParentDescriptionExpr =>
            (r) => $"hello world";

But if I move it to an extension method, then suddenly it works. The docs suggest that using member methods in this way should be fine.

I'm using EFCore with Nein-linq 6.2.0

Would it be possible to use params to construct expression, where params are not to be included in the expression?

How might I alter the behaviour of InjectLambdaAttribute to achieve the following effect?

Where the arguments of the extension method are not actually used as parameters to the expression, but used to determine what expression is returned.

[InjectLambda]
public static string MethodA(this Whatever w, int number) {
    // ...
}

public static Expression<Func<Whatever, string>> MethodA(int number) {

    // Say, we want to alter the number that will actually be used in the DB query...
    // Ideally using some kind of function that actually cannot be duplicated in SQL.
    number = number * 2;

    return w => w.RelatedThings.Count > number ? "Has More" : "Has Less";

}

Or for another example:

var foo = data.ToInjectable()
    .Select(d => d.FlavourMethod(Flavour.Chocolate, true))
    .ToList();

// -------------------------------------------------------------------

[InjectLambda]
public static string FlavourMethod(this Whatever w, Flavour f, bool sprinkles) {
    // ...
}

public enum Flavour {
    Vanilla,
    Chocolate,
    Strawberry  
}

public static Expression<Func<Whatever, string>> FlavourMethod(Flavour f, bool sprinkles) {

    switch(f) {
        case Flavour.Vanilla:
            return VanillaMethod(); // Does not take arg.

        case Flavour.Chocolate:
            return ChocolateMethod(sprinkles);

        case Flavour.Strawberry:
            return StrawberryMethod(sprinkles);

        default:
            throw new InvalidOperationException();
    }

}

private static Expression<Func<Whatever, string>> VanillaMethod() {
    // SQL-friendly implementation ...
}

private static Expression<Func<Whatever, string>> ChocolateMethod(bool sprinkles) {
    // SQL-friendly implementation ...
}

private static Expression<Func<Whatever, string>> StrawberryMethod(bool sprinkles) {
    // SQL-friendly implementation ...
}

Could this be achieved with some kind of attribute on the injectable method?

[ConstructExpressionWith("f", "sprinkles")]
public static Expression<Func<Whatever, string>> FlavourMethod(Flavour f, bool sprinkles) {
    // ...
}

Or:

public static Expression<Func<Whatever, string>> FlavourMethod([Needed] Flavour f, [Needed] bool sprinkles) {
    // ...
}

Support For Entity Framework Core 6

Issue

I installed NeinLinq.EntityFramework.Core 5.1.0 to a net6.0 project using Microsoft.EntityFrameworkCore 6.0.0-rc.1.21452.10. As the major versions differs I was already prepared it might not work - and so it was.

An exception was thrown, obviously due to added members in EF Core 6:

Unhandled exception. System.TypeLoadException: Method 'GetServiceProviderHashCode' in type 'ExtensionInfo' from assembly 'NeinLinq.EntityFrameworkCore, Version=5.1.0.0, Culture=neutral, PublicKeyToken=be449d2bc6d51b0b' does not have an implementation.
   at NeinLinq.RewriteDbContextOptionsExtension.get_Info()
   at Microsoft.EntityFrameworkCore.DbContextOptions.GetHashCode()
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd(IDbContextOptions options, Boolean providerRequired)
   at Microsoft.EntityFrameworkCore.DbContext..ctor(DbContextOptions options)
   at ConsoleApp.TestDbContext..ctor(DbContextOptions options) in some\path\ConsoleApp\TestDbContext.cs:line 9
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at ConsoleApp.Program.Main() in some\path\Program.cs:line 60
   at ConsoleApp.Program.<Main>()

Question

Can you say if and when EF Core 6 will be supported?

Environment

.NET: .NET 6 RC1
Project: net6.0
EF Core: Microsoft.EntityFrameworkCore 6.0.0-rc.1.21452.10

ToInjectable vs ToEntityInjectable

Having EntityFrameworkCore version of package just upgraded the app from .Net Core 5 to .Net Core 6.

Out of sudden queries with ToEntityInjectable() stopped working with translation error. Switching to ToInjectable() seems to fix the issue.

Is it me misinterpreting the docs or something's wrong?

[Question/Feature Request] Overwrite member/property mapping

Hi, I'm having some problems with reusing expressions and stumbled upon this really nice library!

Now I wonder if the following is already possible with NeinLinq and if not if it would be possible to implement (I guess it might be tricky).

I have a DTO class that is display in a list, some of the properties in the DTO should be featched from diffrent places depending on selections of the users so I have done this now:

public class DTO
{
    public Guid Id {get;set;}
    public string Name {get;set;}
    public string ExtendedName {get;set;}
}

Expression<Func<Education, StaticEducation, DTO>> base = (e, _) => new DTO { Id = e.Id, Name = e.Name};
Expression<Func<Education, StaticEducation, DTO>> extended = (e, es) => new DTO { ExtendedName = es.Name};

var combined = base.Apply(extended);

This works but I would like to do this: Override the first Name mapping with the second one like this:

public class DTO
{
    public Guid Id {get;set;}
    public string Name {get;set;} 
}

Expression<Func<Education, StaticEducation, DTO>> base = (e, _) => new DTO { Id = e.Id, Name = e.Name};
Expression<Func<Education, StaticEducation, DTO>> extended = (e, es) => new DTO { Name = es.Name};

//Here Id is from Education and Name is from StaticEducation
var combined = base.Apply(extended);

So my two questions:

  1. Is this possible with NeinLinq today somehow? (I have looked long at the documentation and code but could not figure it out)
  2. If not would it be hard to implement as a feature?

How to add an extension on a IQueryable with Nein-Linq ?

Hi,

Imagine this query:

var query = context.Users.Where(u=>u.IsActive()); //Using Nein-Linq

i would like to do:

var query = context.Users.AreActives();

with

public static class UserExtensions { public static IQueryable<User> AreActives(this IQueryable<User> query){ return null; //to be completed } }

can you help me to complete my method working with nein-linq please ?

Regards,
AB

Readonly CachedExpressions

Is there a reason for static readonly CachedExpressions not to work and instead allow status CachedExpression { get; } only? This doesn't make much sense to me because I typically never change the value of those and I would like to express that in my code.

NotSupportedException when using DbInjectable within a sub query

Hi,

A NotSupportedException is thrown by EF6 when trying to use .Any() on a DbInjectable query.

I have a query like this:

var foo = dbContext.Set<Foo>().ToDbInjectable();
var query = from bar in dbContext.Set<Bar>()
            where foo.Any(f => f.SomeInjectLambdaMethod())
            select bar;

Now it doesn't even have to contain method that should be replaced with a lambda, only the .ToDbInjectable() is enough to get the exception.

This can be simply reproduced in the following test:
NeinLinq.Tests.DbAsyncQuery.RealTest

[Fact]
public async Task AnyShouldSucceed()
{
    var rewriter = new Rewriter();

    var dummies = db.Dummies.ToDbInjectable();
    var query = from dummy in db.Dummies
                where dummies.Any(d => d.Id < dummy.Id)
                select dummy;
    
    var result = await query.ToListAsync();

    Assert.True(rewriter.VisitCalled);
    Assert.Equal(2, result.Count);
}

Full stack:

System.NotSupportedException
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
   at System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered()
   at System.Data.Entity.Core.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
   at NeinLinq.RewriteQueryable`1.GetEnumerator() in C:\Users\Johannes\Source\nein-linq\src\NeinLinq.Queryable\RewriteQueryable.cs:line 37
   at NeinLinq.RewriteQueryable`1.System.Collections.IEnumerable.GetEnumerator() in C:\Users\Johannes\Source\nein-linq\src\NeinLinq.Queryable\RewriteQueryable.cs:line 41
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.ConstantTranslator.TypedTranslate(ExpressionConverter parent, ConstantExpression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
   at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert()
   at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassc.<GetResultsAsync>b__a()
   at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteInTransactionAsync>d__3d`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<ExecuteAsyncImplementation>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult()
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<GetResultsAsync>d__e.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult()
   at System.Data.Entity.Internal.LazyAsyncEnumerator`1.<FirstMoveNextAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.<ForEachAsync>d__5`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at NeinLinq.Tests.DbAsyncQuery.RealTest.<AnyShouldSucceed>d__8.MoveNext() in C:\Users\Johannes\Source\nein-linq\test\NeinLinq.Tests\DbAsyncQuery\RealTest.cs:line 125
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Xunit.Sdk.ExecutionTimer.<AggregateAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Xunit.Sdk.ExceptionAggregator.<RunAsync>d__9.MoveNext()

Query tags are stripped away

A simple query like db.MyTable.ToInjectable().TagWith("MyTag").ToList(); does not include -- MyTag in the generated SQL, as expected.

I am using NeinLinq.EntityFrameworkCore 4.1.0 .

Is it possible to store the result of an expression in a property?

We're using efcore and are attempting to find a solution that would satisfy storing the results of a calculated property directly in an ef entity. Because our calculated values will rely on navigation properties, we'd like to store those back in the entity. Is this possible with nein-linq?

Here's a simple example:

public class Foo
{
	public int Id { get; set; }
	public string Name { get; set; }
	public ICollection<FooBar> FooBars { get; set; }

	[NotMapped]
	[InjectLambda]
	public int FooBarCount { get; protected set; }
	public static Expression<Func<Foo, int>> FooBarCountExpr => foo => foo.FooBars.Count();
}

public class FooBar
{
	public int Id { get; set; }
	public Foo Foo { get; set; }
	public string Name { get; set; }
}

Doc bug

This example given in the docs does not compile due to the duplicate Foo() signature:

public class ParameterizedFunctions
{
    private readonly int narf;

    public ParameterizedFunctions(int narf)
    {
        this.narf = narf;
    }

    [InjectLambda]
    public string Foo()
    {
        ...
    }

    public Expression<Func<string>> Foo()
    {
        ... // use the narf!
    }
}

Documentation: What does calling WithLambdaInjection() on DbContext options do?

The following info from the readme is unclear to me:

New: with Version 5.1.0 the package NeinLinq.EntityFrameworkCore introduced an explicit DbContext extension for enabling Lambda injection globally:

services.AddDbContext<MyContext>(options =>
     options.UseSqlOrTheLike("...").WithLambdaInjection());

Does this mean it is required to call WithLambdaInjection() on the DbContext?
Or does calling this mean I can use LamdaInjection on a IQueryable without calling q.ToInjectable() on it?

DynamicQuery.CreatePredicate doesn't work with Enums

Throws exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.ArgumentException: Argument types do not match
   at System.Linq.Expressions.Expression.Constant(Object value, Type type)
   at NeinLinq.DynamicExpression.CreateConstant(ParameterExpression target, Expression selector, String value, IFormatProvider provider)
   at NeinLinq.DynamicExpression.CreateComparison(ParameterExpression target, String selector, DynamicCompare comparer, String value, IFormatProvider provider)
   at NeinLinq.DynamicQuery.CreatePredicate[T](String selector, DynamicCompare comparer, String value, IFormatProvider provider)

It seems to me that DynamicExpression.CreateConverter doesn't support enums (since enums do not have .Parse method).

If you don't mind, I could provide a PR with this fix

[Nullsafe Queries] Default values for Nullable<T> causing troubles

Given the following example:

class Temporal {
    public DateTime? Date { get; }
}

class TemporalView {
    public DateTime? DateView { get; set; }
}

IQueryable<Temporal> query ...ToNullable();
query.Select(t => new TemporalView { DateView = t.Date.Value.AddDays(-1) });

It looks good, but at runtime things behave strange at first sight:
DateView = Date.Value.AddDays(-1) is an expression tree of Kind

  • Bind(DateView, MethodCall("AddDays", MemberAcces(MemberAccess(we, Date), Value))).

NeinLinq prevents NR-exceptions to be thrown by inserting nullchecks and returning default values.
When Date is null that leads to

  • Bind(DateView, Call("AddDays", MA(MA(we, Date), Value)))
  • reducing to Bind(DateView, Call("AddDays", MA(null, Value)))
  • to Bind(DateView, Call("AddDays", default(DateTime))) where default(DateTime) is 01.01.0001
    And subtracting a day from the minimum date will trigger an out-of-range-exception.

From a general point of view the main problem is, that the default value is determined by the most outer expression, while the expression gets evaluated from inside out.
One approach could be to pass the information whether an outer expression is nullable to every inner expression, and, if so, ommit their execution at appearance or return null while changing it's type.

  1. Bind(DateView, MethodCall("AddDays", MemberAcces(MemberAccess(we, "Date"), "Value"))) -> Nullable<DateTime>
  2. MethodCall("AddDays", MemberAcces(MemberAccess(we, "Date"), "Value")) -> DateTime
  3. MemberAcces(MemberAccess(we, "Date"), "Value") -> DateTime
  4. MemberAccess(we, "Date") -> Nullable<DateTime>

Ad 1.: Expression is nullable, no action required
Ad 2.: An outer expression is nullable, omit execution or return null (change type?)
Ad 3.: An outer expression is nullable, omit execution or return null (change type?)
Ad 4.: Expression is nullable, no action required

There can't be every inner expression be affected by such a mechanism - e.g. method calls of extension methods but not arbitrary method calls.

I can't tell whether that solution is good, valid, or if it is even an issue. The point is, that above code would cause runtime exceptions nevertheless, but the question is which behavior is desirable instead. Just leaving this here for now.

Cannot get the SQL query generated

Hi,
I am using nein-linq with EF 6.4.4 (net standard).
Maybe I am not using library in the right way but when I try to debug the generated SQL I get
image
The simple query is defined as follows:

dbContext.Set<ADDRESS>().ToInjectable().Select(a => a.ALPHACODE.LimitText(10));

The LimitText method is defined in a static class as follows:

public static class AMOSFunctionsGeneric
{
    [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;
    }
}

Is there a way to preview the generated SQL ?
PS: if I use Database.Log event I can get the generated sql but is important to have it in VS debug.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.