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.
Related
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();
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();
I am trying to get a list of a database table called oracleTimeCards whose employee id equals to the employeeID in employees list. Here is what I wrote:
LandornetSQLEntities db = new LandornetSQLEntities();
List<OracleEmployee> employees = db.OracleEmployees.Where(e => e.Office.Contains(officeName) && e.IsActive == true).Distinct().ToList();
var oracleTimeCards = db.OracleTimecards.Where(c => employees.Any(e => c.PersonID == e.PersonID)).ToList();
Anyone has any idea?
I'm going to assume you're using Entity Framework here. You can't embed calls to arbitrary LINQ extension methods inside your predicate, since EF might not know how to translate these to SQL.
Assuming you want to find all the timecards for the employees you found in your first query, you have two options. The simplest is to have a navigation property on your Employee class, named let's say TimeCards, that points to a collection of time card records for the given employee. Here's how that would work:
var oracleTimeCards = employees
.SelectMany(e => e.TimeCards)
.ToList();
If you don't want to do this for whatever reason, you can create an array of employee IDs by evaluating your first query, and use this to filter the second:
var empIDs = employees
.Select(e => e.PersonID)
.ToArray();
var oracleTimeCards = db.OracleTimecards
.Where(tc => empIDs.Contains(tc.PersonID))
.ToList();
In my ASP.NET MVC 5 application I want to list the roles of a user. I downloaded some samples that seem to be broken. Basically I want both the role ID and role name of the roles of a selected user (not the current user!).
ApplicationUser.Roles gives me an IdentityUserRole object with only RoleId and UserId.
ApplicationDbContext.Roles gives me an IdentityRole with RoleId, RoleName etc. of ALL application roles.
So what I want is a result set with the intersection of both sets while retaining full role information so that I can use both its role ID and role name.
I tried Intersect() but that didn't work because both objects are of different type. I tried the dumb style of iterating but got an exception saying the DAta Reader was already active so I am stumped :(
I tried the following on LinQPad (with the appropriate conenctions and namespaces):
string UserName = "user#email.com";
ApplicationDbContext ctx = new ApplicationDbContext();
var allroles = ctx.Roles.OrderBy(r => r.Id);
allroles.Dump(); // dumps well, 6 roles
ApplicationUser user = ctx.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var myroles = user.Roles;
myroles.Dump(); // dumps well, 3 roles
IEnumerable<IdentityRole> list = from roles in allroles
join uroles in myroles
on roles.Id equals uroles.RoleId
select roles;
list.Dump(); // exception
And while the query seems to produce no error during execution, its dumping does regardless of whether I use Dump() or an explicit foreach (IdentityRole item in list). The error I get in this case is
"Unable to reate a constant value of type 'Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole'. Only primitive types or enumeration types are supported in this context".
The only problem here is that you are not calling ToList() method which execute the query immediately (everything will be held in the memory).
For better understanding - ToList() method converts an IEnumerable<T> to a List<T>.
So, your code will look like this:
var allroles = ctx.Roles.OrderBy(r => r.Id).ToList();
var myroles = user.Roles.ToList();
You could use a combination of the two approaches you tried, where you get roles from the context that are present in the ApplicationUser's Roles property...
var roles = ApplicationDbContext.Roles
.Where(ar =>
ApplicationUser.Roles
.Select(ur =>
ur.RoleId)
.Contains(ar.RoleId));
You can do this way :
var rolesList = allroles.ToList().Join(myroles.ToList(),
left => left.Id,
right => right.RoleId,
(left,right) => left);
This way it is working for me for different scenario.
You're trying to join an in-memory list, myroles, with an IQueryable, allroles, which produces a new IQueryable: list. However, this new IQueryable is translated into SQL, so myroles must be translated into SQL as well. This is not supported for lists of non-primitive types.
The solution is to join two IQueryables:
var myroles = ctx.Users.Where(u => u.UserName == UserName).SelectMany(u => u.Roles);
var list = from role in allroles
join urole in myroles
on role.Id equals urole.RoleId
select role;
Using Linq to Entity (Entity Framework) in MVC 3 project.
My model:
Table - Users
UserID (PK)
...
Table - Clients
ClientID (PK)
Table - PropertyItems
PropertyItemID (PK)
Table - MemberContactPreference (Contains PropertyItems selected by Users- many to many)
UserID(FK)
PropertyItemID(FK)
Table ClientProperties (Contains PropertyItems that belong to Clients - many to many)
ClientID (FK)
PropertyItemID (FK)
I want to list all the distinct users that have selected all the properties selected by clients.
My Approach :
I got a list of all properties for a particular client in
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> UsersMatchingClientProperties = GetAllUsers();
foreach (ClientProperty property in clientproperties)
{
UsersMatchingClientProperties = (from uem in UsersMatchingClientProperties
join ucp in GetAllMemberContactPreferences on
ucp.UserID == uem.UserID
where uem.MemberContactPreferences.SelectMany(
mcp => mcp.PropertyItemID == property.PropertyItemID)
select uem).Distinct;
}
It gives the right result only first time. As it doesn't reduce the number of items in UsersMatchingClientProperties with each iteration. actually it replaces the collection with new resultset. I want to filter out this collection with each iteration.
Also, any suggestions to do this in Lambda expression without using Linq.
Thanks
That generation of an iqueryable in a for loop seems like a dangerous thing, which could end up in a monster sql join being executed at once.
Anyway, I don't think you need that. How about something like this?
// for a given client, find all users
// that selected ALL properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = allUsers
.Where(user => allMemberContactProperties
.Where(membP => membP.UserID==user.UserID)
.All(membP => clientProperties
.Select(clientP => clientP.PropertyID)
.Contains(membP.PropertyID)
)
);
Here is an alternative query in case you want the users that selected ANY property for a given client
// for a given client, find all users
// that selected ANY properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = clientproperties
.Join(allMembersContactProperties, // join clientproperties with memberproperties
clientP => clientP.PropertyItemID,
membP => membP.PropertyItemID,
(clientP, membP) => membP)) // after the join, ignore the clientproperties, keeping only memberproperties
.Distinct() // distinct is optional here. but perhaps faster with it?
.Join(allUsers, //join memberproperties with users
membP => membP.UserID,
user => user.UserID,
(membP, user) => user)) // after the join, ignore the member properties, keeping only users
.Distinct();
I trust Hugo did a good job suggesting ways to improve your query (+1). But that does not yet explain the cause of your problem, which is the modified closure pitfall.
I think that after your loop there is some code that actually executes the query in UsersMatchingClientProperties. At that moment the query is executed with the last value of the loop variable property! (The loop variable is the closure in each query delegate that is created in an iteration, and it is modified by each iteration).
Change the loop like this:
foreach (ClientProperty property in clientproperties)
{
var property1 = property;
...
and use property1 in the query. That should solve the cause of the problem. But as said, it looks like the whole process can be improved.