Problem accessing association from the result of a lambda query - c#

Has anyone had problems gettting associations to load using LINQ to SQL when your child record was loaded via a lambda query? For example:
var orderLine = db.OrderLines.
Where(ol => ol.ID == orderLineID select ol).
First();
// navigate to order via the association
var order = orderLine.GetOrder();
What I get basically is a null result from GetOrder().
But if I do this instead:
var orderLine = (from ol in db.OrderLines where ol.ID == orderLineID).First();
var order = orderLine.GetOrder();
Works fine.
What can cause this? Is this a bug?
EDIT: Here's the actual code that WORKS with the Lambda expression commented out that DOESN'T WORK
var msg = db.Messages.Where(m => m.ID == msgID).First();
if (msg.SentTS.HasValue) return;
// Get the message recipients
// I don't get it.. why doesn't lambda expressions work here? returns 0 results!
// var testtos = msg.GetMessageTos.Where(mt => mt.Active);
var tos = from mt in db.MessagesTos
where mt.Active && mt.MessageID == msgID
select mt;

You can also try this, I think it's a little cleaner.
var orderLine = db.OrderLines.Single( ol => ol.ID == orderLineID );
var order = orderLine.GetOrder();
I beileive in your non-working example you want to use .First() instead of .Single().

It seems to me that the problem has more to do with the association than lambda expressions.
In your scenario, this should work:
var tos = db.MessagesTos.Where(mt=> mt.Active && mt.MessageID);
while this won't:
var tos = from mt in msg.SentTS
where mt.Active
select mt;
As to why it doesn't work, I suggest taking a look at the association in the designer and checking its matching the db model correctly (matching the correct columns). I also suggest to confirm that msg.SentTS is effectively coming empty, regardless of any further query you run on it.

See my EDIT for the code that works. I guess sometimes the "Answer" is to do what works, not necessarily what you understand.

Related

Weird LINQ ToList(), IEnumerable vs AsQueryable() results

So I have this query on MSSQL where BANKS is a Table View
SELECT t.*
FROM [DB].[dbo].[BANKS] t
where t.BCODE = 'xxxxxx '; <--- with spaces
which above query has 1 result BCODE : "xxxxxx" <--- no space
on converting it to entity framework using ToList() or as IEnumerable(); e.g.
var _BANKS = dbcontext.BANKS.IEnumerable();
//var _BANKS = dbcontext.BANKS.ToList();
Just note that I have to put this on a Memory because I constantly refer to this table as reference.
On simple execution
string bankcode = "xxxxxx ".Trim();
var test = _BANKS.Where(q => q.BCODE == bankcode ).ToList(); // <--- would return me null
var test2 = from t in _BANKS where t.BCODE == "xxxxxx" select new t; <--- still null
but when I change the _BANKS as AsQueryable(); using the same code snippet above, it would give the desired result the same on the native query (see first SQL snippet).
I'm avoiding the .AsQueryable() because it will give me a runtime error specifically
"The specified LINQ expression contains references to queries that are associated with different contexts."
because I'm using it to constantly refer in a different DBContext's.
The reason why in case of a simple SQL statement, you got one result is pretty obvious for me:
it is that in the database, you have a record which contains spaces, i.e. 'xxxxxx '.
Now why it is not working when using the below code?:
string bankcode = "xxxxx ".Trim();
var test = _BANKS.Where(q => q.BCODE == bankcode ).ToList(); // <--- would return me null
var test2 = from t in _BANKS where t.BCODE == "xxxxxx" select new t; <--- still null
Because you are trimming the bankcode which will lead to converting "xxxxx   " to "xxxxx" but in the database, you have an actual record matching this "xxxxx   ". That's why it will return null which means it will not find matching records.
To overcome this issue use LINQ to SQL Contains() method:
string bankcode = "xxxxx ".Trim();
var test = _BANKS.Where(q => q.BCODE.Contains(bankcode)).ToList();
The issue was not in the .ToList() or the .IEnumerable, but rather it is related to a memory leak when you are trying to save the data in the Memory from a Table view.
Workaround
var _BANKS = (from banks in dbcontext.BANKS
select new {
banks.BCODE,
// ... so on.
}).ToList();
re-selecting the LINQ from storing into your Memory weirdly corrects the inconsistency.

LINQ statement is not translatable

I have the following code containing LINQ statements:
public async Task<HashSet<long>> GetMembersRecursive(IEnumerable<long> groupIds)
{
var containsGroupId = InExpression<Group>("Id", groupIds);
var containsParentId = InExpression<RecursiveGroupModel>("ParentId", groupIds);
var groupIdsArray = groupIds as long[] ?? groupIds.ToArray();
return new HashSet<long>(await MyContext
.Groups
.Where(containsGroupId)
.Select(a => new
{
Members = MyContext
.ViewWithRecursiveGroups
.Where(containsParentId)
.SelectMany(c => c.Group.Members)
.Union(a.Members)
.Where(b => !b.User.IsActive)
})
.SelectMany(a => a.Members.Select(b => b.MemberId))
.Distinct()
.ToListAsync());
}
private static Expression<Func<T, bool>> InExpression<T>(string propertyName, IEnumerable<long> array)
{
var p = Expression.Parameter(typeof(T), "x");
var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(long));
var property = Expression.PropertyOrField(p, propertyName);
var body = Expression.Call(
contains
, Expression.Constant(array)
, property
);
return Expression.Lambda<Func<T, bool>>(body, p);
}
The error I receive is:
Microsoft.EntityFrameworkCore: Processing of the LINQ expression 'DbSet<RecursiveGroupModel>
.Where(b => __groupIdsArray_1
.Contains(b.ParentId))
.SelectMany(c => c.Group.GroupMembers)
.Union((MaterializeCollectionNavigation(
navigation: Navigation: Group.GroupMembers,
subquery: (NavigationExpansionExpression
Source: DbSet<GroupMember>
.Where(l0 => EF.Property<Nullable<long>>(l, "Id") != null && EF.Property<Nullable<long>>(l, "Id") == EF.Property<Nullable<long>>(l0, "GroupId1"))
PendingSelector: l0 => (NavigationTreeExpression
Value: (EntityReference: GroupMember)
Expression: l0)
)
.Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Group)
Expression: l), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Group)
Expression: l), "Id") == EF.Property<Nullable<long>>(i, "GroupId1"))))' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
The view:
CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
WITH RecursiveGroups (GroupId, ParentId) AS
(
SELECT Id, ParentId
FROM Group
WHERE ParentId IS NOT NULL
UNION ALL
SELECT Group.Id, t.ParentId
FROM GroupTree t
JOIN Group ON t.GroupId = Group.ParentId
)
SELECT * FROM RecursiveGroups
Apologies in advance if some variable names don't match up- I had to sanitize before posting.
I understand that it cannot convert code to SQL and so it's asking me to enumerate early or rewrite so that it's translatable. I have tired rearranging the query and breaking it up into smaller queries but the SelectMany on the recursive view seems to not be possible to convert to SQL.
Is there a way to get this working in-database? Or am I going about this completely the wrong way?
As an alternative, you can use raw sql query. In Entity Framework Code, we need to define a POCO class and a DbSet for that class. In your case you will need to define some YourClass:
public DbQuery<YourClass> YourClasses { get; set; }
and code to execute:
var result = context.YourClasses.FromSql("YOURSQL_SCRIPT").ToList();
var asyncresult = await context.YourClasses.FromSql("YOURSQL_SCRIPT").ToListAsync();
Yeah, welcome to the wonderfull world of EfCore 3.1 where all you can do is "Hello world".
Your query has various "problems" because EfCore does not really do LINQ processing except for super easy cases.
.Union(a.Members)
Can not be translated to run server side and client side processing is not enabled. Your only choises are:
Force server execution for both parts (using AsEnumerable) then Union on the client. That only works if you do not use that as part of a larger statement (i.e. intersect) otherwise it is "pull all the data to the client" time and that is not good.
At the current point in time I can only advice you to throw out EfCore and use EntityFramework which - as per framework 3.1 - is again available. Or use Entity Framework Classic which is a port that runs on netstandard 2.0 and has global query filters (which are THE ONE feature of EfCore I like). At last this is what I am currently getting to because - well - "better but without any features and not working" is not cutting it for me.
Whether or not EfCore will be extended (they seem not to see it as a fix) to handle anything except the most basic LINQ statements (and sometimes even not those) is unknown at this point - a lot of the changes in 3.1 are quite discouraging.
You MAY be able to move it into views etc. - but you may find out quite fast that EfCore has even more limitations and maintaining all the views gets quite tendious, too. I run into serious problems with the fact that I can not put any condition in front of any projection even in the most simple cases. And even simple bugs get commented on "we do not feel comfortable changing the pipeline, please wait for version 5 in november". Example? https://github.com/dotnet/efcore/issues/15279.
give that if you want to convert this view to Linq...
CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
WITH RecursiveGroups (GroupId, ParentId) AS
(
SELECT Id, ParentId
FROM Group
WHERE ParentId IS NOT NULL
UNION ALL
SELECT Group.Id, t.ParentId
FROM GroupTree t
JOIN Group ON t.GroupId = Group.ParentId
)
var data1 = db.Group.where(x=>x.ParentId != nul)
.Select(x=>new {x.Id, x.ParentId})
.Tolist()
var data2 = (from g in db.Groups
join gt in db.GroupTree on g.ParentId equals gt.GroupId
select new { d.Id, ParentId })
.ToList();
create a class reprocenting the data and have the query return as List of known type and
just union the two lists.
linqpad is a very useful tool for learn how to create the linq which give you the sql you want.

Using LinqKit to Apply a Query to a Single Complex Property

EDIT: I managed to reduce my code to the least-common-denominator with the assistance of LinqPad.
I have a method that returns a complex LinqKit predicate. The desired result is to be able to reuse this method whenever I need to perform this query. It works fine in a Where clause against an IQueryable collection of these entities, but I can't for the life of me figure out how to use this predicate when I have this entity as a single property of an object, and want to use predicates to query the other properties of that entity.
I'll give you a simplified example of what I mean.
// Note: .Includes removed for brevity's sake.
var task = Tasks.First(t => t.QaConfigTaskId == 2);
var predicate = PredicateBuilder.True<Task>();
// Ensure that the task's own properties match.
predicate = predicate.And(t => t.TaskType == task.TaskType &&
t.Description == task.Description &&
t.PreTreatment == task.PreTreatment &&
t.Treatment == task.Treatment);
var structureAnalysis = task.StructureAnalysis;
var query = PredicateBuilder.True<StructureAnalysis>();
query = query.And(analysis =>
// The names match
analysis.Name == structureAnalysis.Name &&
// We have the same # of goals so they must all match.
analysis.Goals.Count == structureAnalysis.Goals.Count
);
predicate = predicate.And(t => query.Invoke(t.StructureAnalysis));
This will work fine:
StructureAnalyses.AsExpandable().Where(query).Dump();
...assuming StructureAnalyses is an IQueryable of my StructureAnalysis objects from Entity Framework.
But let's say I want to get the Tasks, filtering with the related StructureAnalyses. How can I do such a thing? Keep in mind that in reality, query is built out by a reusable function, and predicate is built out in a separate function that calls it, so I can't simply merge the two queries.
It compiles but fails when I try and execute the query:
Tasks.AsExpandable().Where(predicate).Dump();
...with "The parameter 't' was not bound in the specified LINQ to Entities query expression.", or the like. In this example, Tasks is an IQueryable that contains all of the Task type entities in my DB.
I've also tried altering this line:
predicate = predicate.And(t => query.Invoke(t.StructureAnalysis));
...to:
compiled = query.Compile();
predicate = predicate.And(t => compiled(t.StructureAnalysis));
That also compiles but fails with, "Unable to cast object of type 'System.Linq.Expressions.FieldExpression' to type 'System.Linq.Expressions.LambdaExpression'." and understandably so.
I've also tried calling Expand on both query and compiled, which had no effect, as well as the following "solutions":
This one seems incomplete (Utility is undefined):
https://stackoverflow.com/questions/26105570/linqkit-the-parameter-was-not-bound-in-the-specified-linq-to-entities-query-e
This one simply says, "it can't be done" which I refuse to believe:
Entity Framework Code First 4.3 / LINQKit predicate for related table
I ended up finding the solution on my own. The trick is to wrap the invocation of query in another predicate that accesses the property, then expand that predicate when calling it. So for my example where I'm attempting to invoke a query on a single StructureAnalysis, my code now looks like this:
// Note: .Includes removed for brevity's sake.
var task = Tasks.First(t => t.QaConfigTaskId == 2);
var predicate = PredicateBuilder.True<Task>();
// Ensure that the task's own properties match.
predicate = predicate.And(t => t.TaskType == task.TaskType &&
t.Description == task.Description &&
t.PreTreatment == task.PreTreatment &&
t.Treatment == task.Treatment);
var structureAnalysis = task.StructureAnalysis;
var query = PredicateBuilder.True<StructureAnalysis>();
query = query.And(analysis =>
// The names match
analysis.Name == structureAnalysis.Name &&
// We have the same # of goals so they must all match.
analysis.Goals.Count == structureAnalysis.Goals.Count
);
//// HERE'S WHAT'S NEW ////
Expression<Func<Task, bool>> subPredicate = t => query.Invoke(t.StructureAnalysis);
predicate = predicate.And(subPredicate.Expand());
Tasks.AsExpandable().Where(predicate).Dump();

EF: how to tackle many-to-many relation (some filtering involved)

I am a beginner in EF and LINQ and I would like to retrieve a list of categories (with filter) by product id.
So, I have many-to-many relation between Product * <---> * Category and I use the following code:
var categList = dbContext.Products
.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories)
.Distinct();
categList = SearchCategories(filter, categList);
categList = SortCategories(filter, categList);
categList = PageCategories(filter, categList);
where SearchCategories is used to reuse some code and looks like below
IQueryable<Category> SearchCategories(MyFilter filter, IQueryable<Category> source = null)
{
source = source ?? this.dbContext.Categories;
return source.Where(cat => [...filtering...] );
}
Although this looks ok, I would like to have it slightly optimized, with filtering inside the SelectMany (make use of SearchCategories inside SelectMany)... but I cannot make it work. I tried this, but gives me error
var categList = dbContext.Products
.Where(prod => prod.PROD_UID == 1234)
.SelectMany(cat => SearchCategories(filter, prod.Categories.AsQueryable()).AsEnumerable());
// throws LINQ to Entities does not recognize the method 'SearchCategories'
How could I filter the categories inside SelectMany?
Thank you!
Your problem is you are confusing the server query with the client query there is no magic here.
your first query until Distinct is serialized and sent to the server, then the server sends a response and you then run a filter in your client.
when you put the SearchCategories in the server query it cant be resolver so you get the error.
you have two options here:
1:Just write all your query from SearchCategories in the first query making it run in the server
.SelectMany(prod => prod.Categories.Where(c => [...filtering...]))
remember the filtering cant call client code.
2:You put a ToList or ToArray and then use the SearchCategories but this option wont optimize anything.

Chain together multiple complex WHERE clauses in LINQ to SQL

This is the pseudo-SQL I want to generate:
SELECT * FROM Table WHERE Column1 = #Value1 OR Column2 = #Value2
The problem is, sometimes the second one should not be included. I was hoping to chain together .Where() clauses like so:
var query = context.TableName;
query = query.Where(t => t.Column1 == value1);
if (NeedsToBeIncluded(value2))
query = query.Where(t => t.Column2 == value2);
Unfortunately, this doesn't work. .Where() will emit an AND if you chain them together by default. Is there a way to get it to emit an OR?
I'm looking for something along the lines of:
var query = context.TableName;
query = query.Where(t => t.Column1 == value1);
if (NeedsToBeIncluded(value2))
query = query.OrWhere(t => t.Column2 == value2);
UPDATE
Ok, so my example listed above is too simple. It was merely supposed to be an example that outlines the problem space. Out "in the wild" so to speak, Column1 and Column2 could actually be "CarType" and "OwnerName", maybe there's more, maybe there's less. I just used a simple example to outline the problem because I'm looking to solve a range of domain problems with this chaining-.Where()s together.
One way is to use LINQKit's PredicateBuilder.
Another way is to use a list:
var values = new List<string> { value1 };
if (NeedsToBeIncluded(value2)) values.Add(value2);
query = context.TableName.Where(t => values.Contains(t));
PB is more flexible, but the list will solve the problem in your question. Note that you need EF 4 for Contains.
I gave an example how to dynamically build a condition yesterday - see here. For your case it would be something like that.
var parameter = Expression.Parameter(typeof(TableName), "t");
Expression condition = Expression.Equal(
Expression.Property(parameter, "Column1"),
Expression.Constant(value1)));
if (NeedsToBeIncluded(value2))
{
condition = Expression.OrElse(
condition,
Expression.Equal(
Expression.Property(parameter, "Column2"),
Expression.Constant(value2)));
}
var expression = Expression.Lambda<Func<TableName, Boolean>>(condition, parameter);
var query = context.TableName.Where(expression);

Categories

Resources