Why could the F# LINQ expression not be translated - at least partially? - linq

Issue
I’m using an existing code first approach in order to acquire data from an existing database. This project is encapsulated in a .NET C# project and contains the model as well as the configuration descriptions.
My aim is to use the existing database context implementation for a F# project. I have been testing many times the database access with a XUnit test project which is also written in F#.
My problem is an unexpected behaviour, when I try to select the last name and the person’s id.
In order to explain my implementation and illustrate the issue I implemented four test functions of which only the first two are executed successfully. The last two test functions fail.
The following error appears
System.InvalidOperationException : The LINQ expression 'LastName' could not
be translated. Either rewrite the query in a form that can be translated, or
switch to client evaluation explicitly by inserting a call to 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'*
I do not understand the error because I am not actually using any client specific implementation for the query evaluation. So normally the LINQ-Provider should convert it into a database appropriate SQL definition. For the tests I am using the Entity Framework memory database instance. On top of that, please note that the error also exists on the real database.
Another issue that I do not understand is why the second test works while the third one fails. I changed only the last name select with the id select.
However, I also added a F# query expression since this is actually recommended from the documentation, but with no success.
Is the main problem the usage of the Entity Framework context? If so, how can I then reuse the implementation of the EF database context?
Test and evaluation with LINQPad 6
I tested the behaviour with LINQPad in order to make the use case more simple. Therefore, I used the DemoDB which should be available for everyone.
Apart from that I’m trying to make it reproduceable for a larger community. Unfortunately, the outcome of my test is the same. So, I created a simple database query and changed the order of the named selections. If I change the alphabetical order of the column names, the error appears. Therefore, why is the alphabetical order important in order to have a valid select statement?
I found another closed issue on stackoverflow which describes the usage of anonymous records but the different order is not treated (F# Query Expression / select operator / changing column headings in result).
// successful query
query {
for c in this.Categories do
select {| A = c.CategoryID; B = c.CategoryName; |}
}
// failed query
query {
for c in this.Categories do
select {| B = c.CategoryID; A = c.CategoryName; |}
}
The argument 'value' was the wrong type. Expected
'System.Func`2[System.Int32,<>f__AnonymousType1383943985`2[System.String,System.Int32]]'.
Actual '<>f__AnonymousType1383943985`2[System.String,System.Int32]'.
Test and evaluation with a F# unit test project
Test result summary
I tested the behaviour with .NET 3.1 and .NET 5.0 (projects as well as LINQPad 6). Furthermore, all dependencies have been adjusted accordingly (e.g. Entity Framework 5.0 or 3.1).
Test
Result
A anonymous record
successful
B anonymous record
successful
C anonymous record
failed
D anonymous record
failed
E partial person type
failed
F partial person type
successful
G partial person type
successful
H partial person type
failed
I partial person type
failed
Test outcome
System.InvalidOperationException : The LINQ expression 'LastName' could not be translated. Either rewrite the query in a form that can be translated, or switch
to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
Entity Framework Core "5.0.3" initialized
'"TestContext"' using provider '"Microsoft.EntityFrameworkCore.InMemory"'
EF core code first database .NET 5 project
public class Person
{
public int Id { get; set; }
public string LastName { get; set; }
}
...
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.ToTable("PERSON");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.HasColumnName("ID")
.ValueGeneratedNever();
builder.Property(x => x.LastName)
.HasColumnName("LASTNAME")
.HasMaxLength(512)
.IsUnicode(false);
}
...
public class TestContext : DbContext
{
public DbSet<Person> Persons { get; private set; }
public TestContext(DbContextOptions<TestContext> options) : base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PersonConfig());
}
}
F# xunit test project in order to evaluate the EF core database context access
type PartialPerson = { LastName: string; ID : int; }
type ``success database execution queries`` (output: ITestOutputHelper) =
let sLogger =
LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.TestOutput(output, Events.LogEventLevel.Verbose)
.WriteTo.Debug()
.CreateLogger()
let loggerFactory =
(new LoggerFactory())
.AddSerilog(sLogger)
let options = DbContextOptionsBuilder<TestContext>()
.EnableSensitiveDataLogging(true)
.UseLoggerFactory(loggerFactory)
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
let context = new TestContext(options)
[<Fact>]
let ``success person select lastname Test A`` () =
let rs =
context.Persons.Select(
fun person -> {| Name = person.LastName |} )
rs |> should be Empty // successful
[<Fact>]
let ``success person select id and lastname Test B`` () =
let rs =
context.Persons.Select(
fun person ->
{| ID = person.Id
LastName = person.LastName |})
rs |> should be Empty // successful
[<Fact>]
let ``success person select id and lastname Test C`` () =
let rs =
context.Persons.Select(
fun person ->
{| LastName = person.LastName
ID = person.Id |} )
rs |> should be Empty // failed
[<Fact>]
let ``success person select id and lastname Test D`` () =
let rs =
query {
for person in context.Persons do
select
{| LastName = person.LastName
ID = person.Id |}
}
rs |> should be Empty // failed
// avoid anonymous record and use the partial person type
// type PartialPerson = { LastName: string; ID : int; }
[<Fact>]
let ``success partial person select id and lastname Test E`` () =
let rs =
context.Persons.Select(
fun person ->
{ ID = person.Id
LastName = person.LastName })
rs |> should be Empty // failed
[<Fact>]
let ``success partial person select id and lastname Test F`` () =
let rs =
context.Persons.Select(
fun person ->
{ LastName = person.LastName
ID = person.Id } )
rs |> should be Empty // successful
[<Fact>]
let ``success partial person select id and lastname Test G`` () =
let rs =
query {
for person in context.Persons do
select
{ LastName = person.LastName
ID = person.Id }
}
rs |> should be Empty // successful
[<Fact>]
let ``success partial person select id and lastname Test H`` () =
let rs =
query {
for person in context.Persons do
select
{ ID = person.Id
LastName = person.LastName }
}
rs |> should be Empty // failed
[<Fact>]
let ``success partial person select id and lastname Test I`` () =
let rs =
query {
for person in context.Persons do
select
{ ID = person.Id
LastName = person.LastName }
}
rs.ToList() |> should be Empty // failed
Current findings
It seems that this issue is related to the issues 1226 and 3782. Both issues describe some problems with the order of named selections.
The dapper issue 1226 had a similar problem with the order of anonymous records for the query definition. However, thanks to Isaac Abraham (isaacabraham) who is using the CLIMutable decoration, I thought of turning off the ordering restrictions. So basically the idea was to try it for my tests since the query generation through the LINQ provider could have a positive effect. Unfortunately this was without success, maybe because of the implementation of the LINQ provider because the generation process is implemented with F# and that is the reason why the CLIMutable attribute does not affect it.
After continuing my search, I found another issue 3782 which indicates my problem. The issue has the main focus on the usage of tuples for the data selection but also the issue with records. So, I added another issue description 11131 in order to help with my current findings. Finally, I will keep track the outcome and add it to this issue.

Does this still need answering?
As you already found out, F# anonymous types order fields by name (not by source code order of declaration, as C# anonymous types would do).
When writing {| B = c.CategoryID; A = c.CategoryName; |} in a LINQ query, this will not actually pass an anonymous type, rather the compiler creates an System.Linq.Expressions.Expression that describes how to construct the anonymous type, and later on the underlying framework implementing LINQ (e.g., the Entity Framework) will try to parse that expression (and create e.g. SQL code from it).
Problem here is, c.CategoryID and c.CategoryName may have side effects, hence the compiler will evaluate them in the order specified in the source code (first ID, then Name), but assign them in the order of the anonymous type.
Long story short, the generated System.Linq.Expressions.Expression first will evaluate c.CategoryID, assign the value to a temporary variable, evaluate c.CategoryName, assign that to the anonymous type's first field, and finally assign the temporary variable to the anonymous type's second field. And the EF translator later on does not know how to handle the temporary variable (e.g., how to translate that to SQL.)
(In C#, no field reordering happens, so no temporary variables are required to mask side effects, so the expression parser will not face that problem.) (The F# anonymous type at present is not fully fit for LINQ.)

Related

Selecting last record by linq fails with "method not recognised"

i have following query to select the last record in the database
using (Entities ent = new Entities())
{
Loaction last = (from x in ent.Locations select x).Last();
Location add = new Location();
add.id = last.id+1;
//some more stuff
}
the following error is returned when calling the method containing these lines via "Direct event" on ext.net:
LINQ to Entities does not recognize the method 'Prototype.DataAccess.Location Last[Location]
(System.Linq.IQueryable`1[Prototype.DataAccess.Location])' method,
and this method cannot be translated into a store expression.
table structure is following:
int ident IDENTITY NOT NULL,
int id NOT NULL,
varchar(50) name NULL,
//some other varchar fields
Last is not supported by Linq to Entities. Do OrderByDescending (usually by ID or some date column) and select first. Something like:
Location last = (from l in ent.Locations
orderby l.ID descending
select l).First();
Or
Location last = ent.Locations.OrderByDescending(l => l.ID).First();
See MSDN article Supported and Unsupported LINQ Methods (LINQ to Entities) for reference.

LINQ: When to use 'new' in Select clause?

When is it required to use new in the select clause as in the following example? Thanks.
var results = from person in people
select new { FN = person.FirstName, LN = person.LastName };
I've searched and partially understand this: "In the select new, we're creating a new anonymous type with only the properties you need." But does it mean I can omit new and not get an anonymous type?
To restate my question in another way: is 'new' optional? Is 'new' here similar to C# new where it is not required with value type?
Can I do this?
var results = from person in people
select { FN = person.FirstName, LN = person.LastName };
No, new is not optional if you're selecting "new" objects. (which is what you are doing. You are creating new objects of an anonymous type with 2 properties FN and LN)
Simply omitting new is a syntax error.
If you have an existing type you would like to use, (say you've defined a class Person with 2 properties FN and LN) then you can use that type. E.g.
var results = from person in people
select new Person { FN = person.FirstName, LN = person.LastName };
(assuming Person has a parameterless constructor and FN and LN are properties with setters)
The only time you can omit new is if you are not creating a new object, for instance:
var results = from person in people
select LN = person.LastName;
There you are selecting just the LastName and the result is an IEnumerable<string>
If you are projecting to a new type (anonymous class or known class/struct) it is not optional but required.
If you are projecting to an existing instance though of course you don't need the new, i.e. in your example if you just wanted to project to the first name of each person (to get a string enumeration you could just do:
var results = from person in people select person.FirstName
So rule of thumb if you are constructing a new type you need new in the projection (Select), if you are just selecting something that already exists you don't.
Instead of creating an anonymous type you can instantiate an instance of a named type that takes a Person as an argument or assign values using the object initializer syntax.
Example:
var results = from person in people
select new Report(person);
var results = from person in people
select new Report() { Customer = person };
Edit: Or of course just select a single property from the person object as BrokenGlass pointed out!
Edit: I just re-read your question and wanted to point out that anonymous types in C# are just normal types with the type name generated by the compiler. So yes, you still need to use the new operator because you're creating a new instance of a type. Anonymous Types (C# Programming Guide)

Unable to create a constant value of type (type) Only primitive types ('such as Int32, String, and Guid') are supported in this context

I've read ALL of:
Unable to create a constant value of type 'System.Object' in Entity Framework
Entity Framework - "Unable to create a constant value of type 'Closure type'..." error
Entity Framework - Union causes "Unable to create a constant value of type.."
Only primitive types ('such as Int32, String, and Guid') are supported in this context
and searched a bit more, but still no solution. I've seen that this happens on EF 3.5 and in 4.0 the Contains method should be supported, but I'm in EF 4 but I'm getting this error. I have a photo gallery, where albums can have any number of different photos, and each photo can belong to any number of albums. So this is a many-to-many relationship.
I've got a VisibleObjects property which is used by about 100 other methods that work well, but I'm pasting it anyway: (I'm definitely sure the problem is not caused by something here)
static IQueryable<GlobalObject> VisibleObjects
{
get
{
return from obj in db.GlobalObjectSet where obj.IsVisible && !obj.SiteUser.IsDeactivated orderby obj.ID descending select obj;
}
}
I've tried several different queries:
I have a VisiblePhotos property:
This wasn't working:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>() where a.Photos.Contains(p) select p;
}
Changed to this:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>() where a.Photos.Any(other => p.ID == other.ID) select p;
}
Still didn't work.
Here is the calling method:
public static List<Photo> GetLatestPhotosByAlbum(Album alb, int count = 3)
{
lock (sync)
{
return alb.VisiblePhotos().OrderByDescending(p => p.ID).Take(count).ToList();
}
}
Wasn't working, changed to this:
public static List<Photo> GetLatestPhotosByAlbum(Album alb, int count = 3)
{
lock (sync)
{
return (from p in VisibleObjects.OfType<Photo>()
where alb.Photos.Any(ph => ph.ID == ph.ID)
select p).ToList();
}
}
Still isn't working. Complaining about not being able to create a constant of my Photo object type, which is an Entity object with an ID property, if it helps. I am not sure of the real cause of the error and I don't have any other ideas of queries in my mind. I think the method name is pretty self explanatory: I'm trying to get the photos in a given album. Loading album entries into memory is not a solution, the query should run on database, not memory. I need an explanation of this exception, why it is occuring here, and how can I get my query to work.
It will not work because you want to use local Album in linq-to-entities query. You must either use navigation property on p to get its album:
var query = from p in VisibleObjects.OfType<Photo>()
where p.Album.Id == alb.Id
select p;
or you must build complex query with some join between photos and albums. You cannot pass local object and any its relation to the query. Only simple properties can be passed.
I think that EF is trying to convert where a.Photos.Contains(p) into SQL like WHERE p IN (a.Photos), but it doesn't know how to express a.Photos in SQL. The SQL you want probably looks like WHERE p.Id IN (1, 2, 3), so you could try doing that in C#:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
var photoIds = a.Photos.Select(p => p.Id).ToArray();
return from p in VisibleObjects.OfType<Photo>() where photoIds.Contains(p.Id) select p;
}
I ran into a similar problem, and instead of IQueryable, I tried using List, and it worked. May be of some help.
I tried another way around and it worked:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>()
where p.Albums.Any(alb => a.ID == alb.ID)
select p;
}
Quite weird to see this works but the other one not. But I'm still wondering why Contains is not working.

Linq/EF, Eager loading and GROUP BY issues

I'm having issues with GROUP BY and eager loading. I try to explain what im doing.
I'm querying a datacontext ctx for events
The event class has the following properties
string Description
DateTime Date
bool IsDeleted
Guid SubjectId
string Title
DateTime Created
Guid ForProjectId
Person TriggeredBy
List<Guid> Targets
There are muttiple events with the same SubjectId and i would like to end up having events with unique SubjectIds and that are the newest in the group. I end up with the following query.
var events = from x in
(from e in ctx.Events
.Include("TriggeredBy")
.Include("Targets")
group e by e.SubjectId
into g
select new
{
GroupId = g.Key,
EventsWithSameSubjectId = g,
}
)
select x.EventsWithSameSubjectId
.OrderByDescending(y => y.Created).FirstOrDefault();
The query compile fine and returns the right resulting set. But the included properties are always null.
When i strip the query to see if eagor loading is working properly....
var events = (from e in ctx.Events.OfType<DataNotificationEvent>()
.Include("TriggeredBy")
.Include("Targets")
select e).ToList();
This return the events with all the included properties.
Is this a known issue / bug with Linq / EF or is there any way i can get rid of this error.
Regards
Vincent Ottens
You're projecting onto an anonymous type, so Include() isn't going to work like that. Because what you've done with the group and projecting into the anonymous type is to change the shape of the query. That tosses out the eager loading. Reading this article might help.
Thnx for the quick answer. You pointed me in the right direction. Here is the solution i came up with:
using MyFunc = Func<ExtendedCoreContext, Guid, IQueryable<DataNotificationEvent>>;
private static readonly MyFunc GetMentionsNewCompiledQuery =
CompiledQuery.Compile<ExtendedCoreContext, Guid, IQueryable<DataNotificationEvent>>(
(ctx, personId) => ((ObjectQuery<DataNotificationEvent>)(
from x in (
from e in ctx.Events.OfType<DataNotificationEvent>()
group e by e.SubjectId
into g
select g.OrderByDescending(p => p.Created).FirstOrDefault()
)
orderby x.Created descending
where x.Targets.Any(t => t.Id == personId)
select x
))
.Include(EntityProperties.Event.TriggeredBy)
.Include(EntityProperties.DataNotificationEvent.Targets)
);

Can't combine "LINQ Join" with other tables

The main problem is that I recieve the following message:
"base {System.SystemException} = {"Unable to create a constant value of type 'BokButik1.Models.Book-Author'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."}"
based on this LinQ code:
IBookRepository myIBookRepository = new BookRepository();
var allBooks = myIBookRepository.HamtaAllaBocker();
IBok_ForfattareRepository myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var Book-Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer();
var q =
from booknn in allBooks
join Book-Authornn in Book-Authors on booknn.BookID equals
Book-Authornn.BookID
select new { booknn.title, Book-AuthorID };
How shall I solve this problem to get a class instance that contain with property title and Book-AuthorID?
// Fullmetalboy
I also have tried making some dummy by using "allbooks" relation with Code Samples from the address http://www.hookedonlinq.com/JoinOperator.ashx. Unfortunately, still same problem.
I also have taken account to Int32 due to entity framework http://msdn.microsoft.com/en-us/library/bb896317.aspx. Unfortunatley, still same problem.
Using database with 3 tables and one of them is a many to many relationship. This database is used in relation with entity framework
Book-Author
Book-Author (int)
BookID (int)
Forfattare (int)
Book
BookID (int)
title (string)
etc etc etc
It appears that you are using two separate linq-to-sql repositories to join against. This won't work. Joins can only work between tables defined in a single repository.
However, if you are happy to bring all of the data into memory then it is very easy to make your code work. Try this:
var myIBookRepository = new BookRepository();
var myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var allBooks =
myIBookRepository.HamtaAllaBocker().ToArray();
var Book_Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer().ToArray();
var q =
from booknn in allBooks
join Book_Authornn in Book_Authors
on booknn.BookID equals Book_Authornn.BookID
select new { booknn.title, Book_AuthorID = Book_Authornn.Book_Author };
Note the inclusion of the two .ToArray() calls.
I had to fix some of you variable names and I made a bit of a guess on getting the Author ID.
Does this work for you?
I would suggest only having a single repository and allowing normal joining to occur - loading all objects into memory can be expensive.
If you are making custom repositories you make also consider making a custom method that returns the title and author IDs as a defined class rather than as anonymous classes. Makes for better testability.

Resources