Entity Framework Translation InvalidOperationException - c#

In my API, I am trying to compare a list of objects passed in from the client with a list of records of the same type in the database, based on multiple properties.
I.e I have a Codes table in the database, with a Code column and a TypeId column. I receive a list of Code objects from the Client application, each with a Code property
and a TypeId property.
Using LINQ/Entity Framework, I want a list of all the database Codes which are in the input/client Codes list. That is, where the Code and TypeId properties
are the same. Like an INTERSECT or JOIN based on multiple properties.
In SQL, one would typically use the following:
SELECT * FROM #InputCodes ic INNER JOIN Codes c ON ic.Code = c.Code AND ic.TypeId = ic.TypeId
How do I do this using Entity Framework? I've tried the following approaches:
List<Code> codes = new List<Code>() { }; // Assume this is being passed in from Client
codes.Add(new Code() { Code = "1234", TypeId = 1 });
codes.Add(new Code() { Code = "1234", TypeId = 2 });
codes.Add(new Code() { Code = "ABCD", TypeId = 2 });
codes.Add(new Code() { Code = "ZZZZ", TypeId = 3 });
var result = await _db.Codes.Where(a => codes.Any(b => a.Code == b.Code && a.TypeId ==
b.TypeId)).ToListAsync();
var result = from dbCode in _db.Codes
join requestCode in codes
on new { a = dbCode.Code, b = dbCode.TypeId } equals new { a = requestCode.Code,
b = requestCode.TypeId }
select dbCode;
Both queries result in the following error:
System.InvalidOperationException: 'The LINQ expression 'b => EntityShaperExpression:
MxNode.Common.DB.Models.Codes
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Code == b.Code && (int?)EntityShaperExpression:
MxNode.Common.DB.Models.Codes
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.TypeId == b.TypeId' 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'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
I need this query to be evaluated server side as there could be thousands of codes and it would not be optimal to load all of them into
memory before comparing/filtering by code and type.

Related

Cannot perform aggregation on sub query in EF core 3.0

I am getting the error after migrating to EF Core 3.0
Cannot perform aggregation on sub query...
Here is my query:
var query= (from TestAttempt in Wallet.LD_AssociateTestAttempt
join contentTest in Wallet.LD_Test on new { TestId = TestAttempt.TestId, Type = TestType.Content.Value, IsDeleted = false, IsEnable = true }
equals new { TestId = contentTest.TestId, Type = contentTest.Type, Isdeleted = contentTest.IsDeleted, IsEnable = contentTest.Enable }
let NoOfQuestions = contentTest.LD_TestDetails.Where(a => !a.IsDeleted).Select(a => a.NoOfQuestions).Sum()
let TotalMarks = NoOfQuestions * contentTest.QuestionPerMarks
where TestAttempt.IsActive && !TestAttempt.IsDeleted
group new { TotalMarks, TestAttempt.Marks, TestAttempt.TestStartDate, TestAttempt.TestEndDate, TestAttempt.TestCompletionDate } by new { TestAttempt.AssociateTestId } into G
select new
{
AssociateTestId = G.Key.AssociateTestId,
Marks = G.Sum(a => a.Marks),
TotalMarks = G.Sum(a => a.TotalMarks),
TestCompletionDate = G.Max(a => a.TestCompletionDate),
TestEndDate = G.Max(a => a.TestEndDate),
});
The exact line where I am facing the issue is
let NoOfQuestions = contentTest.LD_TestDetails.Where(a => !a.IsDeleted).Select(a => a.NoOfQuestions).Sum()
Could somebody help me on this? I am new to EF Core
Old behavior
Before 3.0, when EF Core couldn't convert an expression that was part
of a query to either SQL or a parameter, it automatically evaluated
the expression on the client. By default, client evaluation of
potentially expensive expressions only triggered a warning.
New behavior
Starting with 3.0, EF Core only allows expressions in the top-level
projection (the last Select() call in the query) to be evaluated on
the client. When expressions in any other part of the query can't be
converted to either SQL or a parameter, an exception is thrown.
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
it's mean your query is Complex for convert to SQL query and you must be simplify your EF-query or break to two or three query.

"Where" in nullable property of right join Linq C# lambda expression EF Core

I have a query to list a group of an Entity called Transaction with children. When trying to apply a where to filter a prop in child, it throw a Linq invalid operation. (EF Core)
Here is the code:
var query = DataContext.Transactions
.Select(x => new Transaction
{
Id = x.Id,
TotalAmount = x.TotalAmount,
CreatedAt = x.CreatedAt,
TransactionState = x.TransactionState == null ? null :
new TransactionState
{
Id = x.TransactionState.Id,
Name = x.TransactionState.Name
},
Beneficiary = x.Beneficiary == null ? null :
new Beneficiary
{
Id = x.Beneficiary.Id,
DocumentNumber = x.Beneficiary.DocumentNumber
}
})
.AsNoTracking()
.AsQueryable();
if (!filter.IsNullEmtpyOrWhiteSpace()) // Own Property
{
// This is the line where apply where in prop
query = query.Where(x => x.Beneficiary != null && x.Beneficiary.DocumentNumber.Contains(filter));
}
var count = query.Count();
if (take > 0) query = query.Skip(skip).Take(take);
return new ListAndCountDto<Transaction>
{
Data = query.ToList(),
Count = count
};
This is the error exception:
"The LINQ expression 'DbSet<Transaction>\n
.LeftJoin(\n outer: DbSet<Beneficiary>, \n
inner: t => EF.Property<Nullable<int>>(t,
\"BeneficiaryId\"), \n outerKeySelector: b => EF.Property<Nullable<int>>(b, \"Id\"), \n
innerKeySelector: (o, i) => new TransparentIdentifier<Transaction, Beneficiary>(\n
Outer = o, \n Inner = i\n ))\n .Where(t => EF.Property<Nullable<int>>(t.Inner, \"Id\") == null ?
null : new Beneficiary{ \n Id = t.Inner.Id, \n DocumentNumber = t.Inner.DocumentNumber \n }\n
!= null && EF.Property<Nullable<int>>(t.Inner, \"Id\") == null ? null : new Beneficiary{ \n
Id = t.Inner.Id, \n DocumentNumber = t.Inner.DocumentNumber \n }\n
.DocumentNumber.Contains(__filter_0))' 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
either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information."
Notice the error exception ,you'll know it's caused by your linq can't be transtlated .
As you are using Queryable which will call database evaluation.
So what you need to do it's rewrite the query or use AsEnumerable() as a client evaluation:
var query = DataContext.Transactions.AsEnumerable()
.Select()...
reference:https://learn.microsoft.com/en-us/ef/core/querying/client-eval

Entity Framework 6, Oracle - Linq Join Query generates unwanted where condition

I have the following arrangement where I join 2 tables to retrieve a description column from the second table.
I am using Entity Framework 6 with Oracle 12c
public IQueryable<TEntity> GetAll()
{
return this.dbSet.AsQueryable();
}
var fooQuery = fooRepo.GetAll();
var barQuery = barRepo.GetAll();
var joinedQuery =
fooQuery.Join(
barQuery,
fooObj => new { fooObj.comp_key_1, fooObj.comp_key_2, fooObj.comp_key_3 },
barObj => new { barObj.comp_key_1, barObj.comp_key_2, barObj.comp_key_3 },
(fooItem, barItem) => new {
fooItem.comp_key_1,
fooItem.comp_key_2,
fooItem.comp_key_3,
...
...
...
barItem.BarName
}
);
When executed the code it generates the following SQL which is less that ideal as there is an unintended where clause being generated.
SELECT
1 AS "C1",
"Extent1"."COMP_KEY_1" AS "COMP_KEY_1",
"Extent1"."COMP_KEY_2" AS "COMP_KEY_2",
"Extent1"."COMP_KEY_3" AS "COMP_KEY_3",
...
...
...
"Extent2"."BAR_NAME" AS "BAR_NAME"
FROM "FOO_TABLE" "Extent1"
INNER JOIN "BAR_TABLE" "Extent2" ON ("Extent1"."COMP_KEY_1" = "Extent2"."COMP_KEY_1") AND ("Extent1"."COMP_KEY_2" = "Extent2"."COMP_KEY_2") AND ("Extent1"."COMP_KEY_3" = "Extent2"."COMP_KEY_3")
WHERE ((("Extent2"."Discriminator" = N'Foo') OR ("Extent2"."Discriminator" = N'Bar')))
What am I missing, what needs to be done to remove the unintended where Clause ?
Found it,
Had a base class for 'Bar' that contains only the p_key information and an extended class that contained all other properties.
Once I moved everything in to a single class
("Extent2"."Discriminator" = N'Foo') OR ("Extent2"."Discriminator" = N'Bar')
disappeared

Dynamically selecting fields in a linq subquery

I am trying to dynamically filter the fields in a select clause similar to what was requested by a user as was described in the post dynamically selecting fields in a linq query. (I describe my problem and fully below so you don't need to go to the link)
What I am trying to do in short is to define a query and then at runtime determine what fields are part of the select statement and which are filterd out.
QueryFilter filter = new QueryFilter {
ShowClientInfo = false,
ShowPackaging = true,
ShowName = true,
ShowWeight = false
};
//Below is a sample linq query that works without desired filtering.
// Comments are to the right of the fields I want filterd with the object above
var testQuery = db.Client
.Where(c => c.ID == clientId)
.Select(c =>
new
{
ClientInfo = c, // <== Supress this
Packaging = c.ClientPackaging // <== Show this
.Where(cp => !cp.IsDeleted)
.Select(cp =>
new
{
Name = cp.Name, // <== Show this
Weight = cp.Weight // <== Suppress this
}
).ToList()
}
).FirstOrDefault();
The first answer of the related question was not sufficient, so I am not going to go into it further. It is insufficient because the linq statement would still query the database for specific fields, and would only just not assign the value on return.
The second awesome answer by Ivan Stoev is what I am trying to have expanded upon. This answer prevents the fields from even being part of the query to the database and is preferred. But this only works for the outermost select of the linq query. The part I am having trouble with is creating a helper method that will work on subqueries in the select statement.
The reason for the trouble, is the first use of the overload is of type IQueryable, the nested queries in the select are of type IEnumerable and thus will not map to the current extension of the answer.
Extension from other post mentioned above that works for the outermost select statement
public static class MyExtensions
{
public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, object options)
{
var memberInit = (MemberInitExpression)selector.Body;
var bindings = new List<MemberBinding>();
foreach (var binding in memberInit.Bindings)
{
var option = options.GetType().GetProperty("Show" + binding.Member.Name);
if (option == null || (bool)option.GetValue(options)) bindings.Add(binding);
}
var newSelector = Expression.Lambda<Func<TSource, TResult>>(
Expression.MemberInit(memberInit.NewExpression, bindings), selector.Parameters);
return source.Select(newSelector);
}
}
Here is my sample query that works with the first Select, but the nested select statements do NOT work as they need to be an IEnumerable extension.
public class QueryFilter
{
public bool ShowClientInfo {get;set;}
public bool ShowPackaging {get;set;}
public bool ShowName {get;set;}
public bool ShowWeight {get;set;}
}
QueryFilter filter = new QueryFilter {
ShowClientInfo = false,
ShowPackaging = true,
ShowName = true,
ShowWeight = false
};
var testQuery = db.Client
.Where(c => c.ID == clientId)
.Select(c =>
new
{
ClientInfo = c,
Packaging = c.ClientPackaging
.Where(cp => !cp.IsDeleted)
.Select(cp => // <==== This select does not work
new
{
Name = cp.Name,
Weight = cp.Weight,
packages = cp.ShipmentPackage
.Select(sp => // <==== Neither does this
new
{
Dimension_x = sp.Dimension_x,
Dimension_y = sp.Dimension_y,
Dimension_z = sp.Dimension_z
}, filter //<== additional filter parameter for the select statement
).ToList()
}, filter //<== additional filter parameter for the select statement
).ToList()
}, filter //<== additional filter parameter for the select statement
).FirstOrDefault();
The error I am getting on the two nested queries in the .Select is:
Method 'Select' has 1 parameter(s) but is invoked with 2 argument(s)
I need to create an IEnumerable version of the method that would work in this case.
How would I create the method as an IEnumerable?

Make a LINQ query dynamic to bring back all rows or only rows with a link to a lookup table?

I have a query that returns a list of currencies and joins to a lookup table. The result is then put into a class object (which works fine):
var queryforobject = from x in db.CurrencyExchangeRates.AsNoTracking()
join c in db.CurrencyTypes.AsNoTracking() on x.CurrencyTypeID equals c.ID
orderby x.ID
select new CurrencyExchangeRateObject
{
ID = x.ID,
CurrencyID = c.ID,
Currency = c.Description,
ExchangeRate = x.ExchangeRate,
LastEditedDate = x.LastEditedDate,
LastEditedBy = x.LastEditedBy,
Active = x.Active
};
I want to make this more dynamic, so if no CurrencyTypeID is supplied then it will return the full list (as it does already) - otherwise if a CurrencyTypeID is supplied it will only show where X.CurrencyTypeID = ID.
Something along the lines of an inline if?
There are a few options for filtering the query based on CurrencyTypeID if a search value (named currencyTypeID in this answer) is supplied, but return all data if no currencyTypeID is supplied.
First option: You could add a where clause to your existing query expression. The WHERE clause below will return every record in the data set if null is passed in for the currencyTypeID variable, otherwise it will filter the results.
from x in db.CurrencyExchangeRates.AsNoTracking()
join c in db.CurrencyTypes.AsNoTracking() on x.CurrencyTypeID equals c.ID
where (currencyTypeID == null || x.CurrencyTypeID == currencyTypeID)
orderby x.ID
select new CurrencyExchangeRateObject {
ID = x.ID,
CurrencyID = c.ID,
Currency = c.Description,
ExchangeRate = x.ExchangeRate,
LastEditedDate = x.LastEditedDate,
LastEditedBy = x.LastEditedBy,
Active = x.Active
};
Alternatively: Since queryforobject is of type IQueryable<T>, you can use LINQ's fluent API to append a WHERE clause to the query inside an if statement. You need to be more careful about timing on this one though as it needs to be done before you force evaluation of the IQueryable with a foreach loop, .ToList(), .Select() or other LINQ methods that force evaluation.
if(currencyTypeID != null)
queryforobject = queryforobject.Where(cerObj => cerObj.CurrencyID == currencyTypeID);

Categories