Cosmos Db linq query for child item not working - linq

I spent so many hours today on this without success, I hope someone can help me.
I'm trying Cosmos DB and LINQ, here the items in the db:
|Customer
|String Property1
|String Property2
|ICollection Orders
|String PropertyA <--Select on this property
How can I select the Item Customer which has the PropertyA with a specific value?
I tried this and so many other:
var customer = await context.Customers.Select(_s => _s.Orders.Select(p => p.PropertyA == "123456")).FirstAsync()
Thank you for your help.
EDIT 1:
I also tried this:
var customer1 = (from _customer in context.Customers
where _customer.Orders.Any(_a => _a.MyId.Contains("2012031007470165"))
select _customer).ToList();
Here the error message i received:
The LINQ expression 'DbSet()
.Where(c => EF.Property<ICollection>(c, "Orders")
.AsQueryable()
.Any(o => o.MyId.Contains("2012031007470165")))' 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.

If I understand your question properly
var customer = await ctx.Customers.Include(c => c.Orders).FirstAsync(s => s.Orders.FirstAsync(p => p.PropertyA == "123456"));

I found the solution, the right answer (use with cosmos SQL api and no more with Entity Framework) is:
Customer _customerResult = _DBcontainer.GetItemLinqQueryable<Customer>(true)
.Where(_w => _w.Orders.Any(c => c.Purchase_Id == "1234"))
.AsEnumerable()
.FirstOrDefault();

Related

NHibernate LINQ query with GroupBy

I am struggling with converting SQL to NHibernate HQL.
SQL Query
SELECT Posts.id, Count(Comments.id) FROM Posts LEFT JOIN Comments ON Posts.id=Comments.fk_post GROUP BY Posts.id
LINQ
Session.Query<Post>().Fetch(x => x.Comments).GroupBy(x => x.id, x => x.Comments)
.Select(x => new { PostId = x.Key, CommentCount = x.Single().Count }).ToList();
This is still failing with exception:
Parameter 'inputInfo' has type 'Remotion.Linq.Clauses.StreamedData.StreamedSingleValueInfo' when type 'Remotion.Linq.Clauses.StreamedData.StreamedSequenceInfo' was expected.
What is wrong with my query?
So you have tables of Posts and Comments. There is a one-to-many relation between Posts and Comments: every Post has zero or more Comments, every Comment belongs to exactly one Post, namely the Post that the foreign key Comments.fk_post refers to.
You want to fetche the Id of every Post, together with the number of Comments for this Post.
Whenever you need to select "items with their zero or more sub-items", like Schools with their Students, Customers with their Orders, or in your case Posts with their Comments, consider to use one of the overloads of Queryable.GroupJoin.
You can also see that a GroupJoin is the most obvious solution, if you see a SQL Left Outer Join followed by a GroupBy.
Whenever you see a SQL left outer join followed by a GroupBy, it is almost certain that you need a GroupJoin.
If you want something else than juse "items with their sub-items", use the overload that has a parameter resultSelector.
I don't know nHibernate, I assume that Session, Query, Fetch are used to get the IQueryables. As this is not part of the question, I leave it up to you to get the IQueryables:
IQueryable<Post> posts = ...
IQueryable<Comment> comments = ...
// GroupJoin Posts with Comments
var postIdsWithCommentsCount = posts.GroupJoin(comments,
post => post.Id, // from every Post take the primary key
comment => comment.fk_post, // from every Comment take the foreign key to Post
// parameter resultSelector: from every Post, with all its zero or more Comments,
// make one new
(post, commentsOfThisPost) => new
{
Id = post.Id,
Count = commentsOfThisPost.Count(),
});
Try this query:
var query =
from p in Session.Query<Post>()
from c in p.Comments.DefaultIfEmpty()
group c by p.Id into g
select new
{
PostId = g.Key,
CommentCount = g.Sum(x => (int?)c.Id == null ? 0 : 1)
}
var result = query.ToList();;

Unable to translate the LINQ expression while using Group by

I have an Asp.net core web API that uses EF Core DB first approach. I have the following Tables in my SQL server database.
Application table - contains a list of Applications.
Role table - contains a list of Roles (user, admin, Super Admin, etc.)
User table - contains a list of Users
User Role table - contains a mapping between User and Role table
Feature tables - contains a list of Features (Home page, user management, etc.)
RoleFeature table - contains the mapping between Feature and Role table
I am trying to get the RoleName and list of features for the given UserId and appId.
Below is the Linq query I have so far:
RoleDto role =
from a in ctx.Application.Where(x => x.ApplicationId == appId)
from r in ctx.Role.Where(x => x.ApplicationId == a.ApplicationId)
from ur in ctx.UserRole.Where(x => x.UserId == userId && x.RoleId == r.RoleId)
from rf in ctx.RoleFeature.Where(x => x.RoleId == ur.RoleId)
from f in ctx.Feature.Where(x => x.FeatureId == rf.FeatureId).Where(x => x.IsActive)
group new { r.RoleName, f.FeatureId } by ur.RoleId into g
select new RoleDto
{
Name = g.Select(x => x.RoleName).FirstOrDefault(),
FeatureIds = g.Select(x => x.FeatureId).ToList()
}.AsNoTracking()
However, I am getting an error saying unable to translate the LINQ expression.
The problem is with the FirstOrDefault() in the select section of the "groupby" query.
You are probably using ef core version higher than 2.1 and there are some changes in handling groupby query. You should take a look at the following link :
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1#linq-groupby-translation
Before version 2.1, in EF Core the GroupBy LINQ operator would always
be evaluated in memory. We now support translating it to the SQL GROUP
BY clause in most common cases.
So the query should be translated to SQL GROUP BY, but methods such as FirstOrDefault() can not be translated. As a fast solution you can change FirstOrDefault() with Max(), Min() or other aggregate functions that are supported in DB.
You don't need to select the Application. You can start by selecting Role directly as -
from r in dbCtx.Role.Where(x => x.ApplicationId == appId)
This will simplify the final SQL generated by EF. So the query will be faster.
If a User has multiple Role then you are trying to take the first one. You should do that when you are selecting Role as -
from r in ctx.Role.Where(x => x.ApplicationId == a.ApplicationId).Take(1)
Finally, you can fetch a list of RoleName and FeatureId, and then do the grouping on client side -
var query =
from r in dbCtx.Role.Where(x => x.ApplicationId == appId).Take(1)
from ur in dbCtx.UserRole.Where(x => x.UserId == userId && x.RoleId == r.RoleId)
from rf in dbCtx.RoleFeature.Where(x => x.RoleId == ur.RoleId && x.Feature.IsActive)
select new
{
RoleName = rf.Role.RoleName,
FeatureId = rf.FeatureId
};
var roleDto = query.AsNoTracking()
.AsEnumerable()
.GroupBy(p => p.RoleName)
.Select(g => new RoleDto
{
Name = g.Key,
FeatureIds = g.Select(p => p.FeatureId).ToList()
})
.FirstOrDefault();

Select two lists as one list in ASP.NET Core linq

I am trying to create a query with ASP.NET Core EF Core and Linq that would give me a List of users based on two different lists, something like this:
return await _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.Where(u => u.Id == userID)
.Select(u => new List<User>()
{
u.PropertyTenantLeases.Select(ptl => ptl.Tenant).ToList()
u.PropertyOwners.Select(po => po.Owner).ToList()
}).FirstOrDefaultAsync();
The tables that are used in this query are connected in the following way:
Everything is fine with this query except for the Select, with the Select I am trying to achieve that it returns a list of all the tenants in the PropertyTenantLeases table which is a junction table togheter with all the Owners form the PropertyOwners junction table (both Tenant and Owner are IdentityUser classes. When I right this query like this I get the following error:
The best overloeaded Add method 'List<User>.Add(User)' for the collection initializer has some invalid arguments
and also
Argument 1: cannot convert from 'System.Collections.Generic.List<RosyMasterDBManagement.Models.User>' to 'RosyMasterDBManagement.Models.User'
Joining two list is called a union in Linq -- I believe that is what you want:
note: I still can't test this since you gave a picture of the data model instead of the code that would allow me to be certain of how to implement. expect the fields to be named incorrectly etc.
var ownerlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.ToList();
var tenantlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.ThenInclude(po => po.Tenant)
.ToList();
return ownerlist.Union(tenantlist);
I don't believe you need await() since ToList() forces it to not be lazy. But I could be wrong about that.

Entity Framework Linq Update Table

I am using Entity Framework 6 Code First, all of my navigation properties looks correct. What I want to do in linq is to update multiple values in the one (entity) table with calculated values from the other (entity) table. The required outcome is the sql below,
UPDATE SalesDealItemChange
SET Price = SD.Rate * #FactorRate
FROM SalesDealItemChange SC
INNER JOIN [SalesDealItems] SD
ON SC.SalesDealItemID = sd.ID
WHERE SD.SalesDealID = #SalesDeal
I have tried the following linq, but i am not sure how to isolate the update to the SalesDealItemChange
context.SalesDealItems
.Include(x => x.SalesDealItemChanges)
.Where(x => x.SalesDealID == #SalesDeal)
.ForEach(x =>
{
x.SalesDealItemChanges.Price = x.ListPrice * #FactorRate;
});
context.SaveChanges();
Regards,

Dynamics CRM 2011 Linq Group Join Method Syntax

How can I use group join with method syntax?
I can do a group join with query syntax like this:
var contAndAcc = from contact in linq.ContactSet
join account in linq.AccountSet
on contact.ParentCustomerId.Id equals account.AccountId
into accountGroup
from account in accountGroup.DefaultIfEmpty()
select new{contact = contact, account = account};
The compiler converts the query syntax to method syntax, so it must be possible to write the above in method syntax.
These do not work:
var contAndAcc = linq.ContactSet.GroupJoin(
linq.AccountSet, // collection to join to
contact => contact.ParentCustomerId.Id,
account => account.AccountId,
(contact, account) => contact);
var contAndAcc = linq.ContactSet.GroupJoin(
linq.AccountSet,
contact => contact.ParentCustomerId.Id,
account => account.AccountId,
(contact, account) => contact)
.DefaultIfEmpty()
.Select(contact=>contact);
var contAndAcc = linq.ContactSet.GroupJoin(
linq.AccountSet,
contact => contact.ParentCustomerId.Id,
account => account.AccountId,
(contact, account) => contact)
.DefaultIfEmpty()
.SelectMany((contact) => contact);
No matter what I try I get this message:
The 'GroupJoin' operation must be followed by a 'SelectMany'
operation where the collection selector is invoking the 'DefaultIfEmpty' method.
I would also really like to use an IEqualityComparer; will that be possible?
Total stab in the dark since I don't do much Linq to CRM, but the QueryExpressions that it gets converted to don't support returning an entire entity. Try listing every column that you want returned, rather than the entities themselves.

Resources