Linq Joining IEnumerable(Of Struct) to IEnumerable(of object) - linq

With class RoleRecord (Guid RoleId, string RoleName,...) I am trying to get a new list of Name where the RoleId matches a list of Guid
IEnumerable<RoleRecord> roles;
IEnumerable<Guid> roleIds;
I was thinking about avoiding the nested for loops, and along the lines of :
var query =
from rowA in roles
join rowB in roleIds
on rowA.RoleId equals rowB.????
select { rowA.RoleName };
I tried to wrap the guid in a class too, but cant even build it because the syntax is wrong.
Any ideas?
Thanks

I would personally not use Jeremy's answer if you've got a significant number of Guids. If a join is what you really want to express - you just need:
var query = from rowA in roles
join rowB in roleIds on rowA.RoleId equals rowB
select rowA.RoleName;
Alternatively, create a set of role IDs first:
HashSet<Guid> validRoleIds = new HashSet<Guid>(roleIds);
var query = from rowA in roles
where validRoleIds.Contains(rowA.RoleId)
select rowA.RoleName;
The advantage is that then you don't need to do a linear search through every valid role ID for every role. That's not an issue if you know you don't have many roles or role IDs, but generally a hashing approach will be more effective.
Note that the join will use a hash as well internally.

Give this a try:
var query =
from rowA in roles
where roleIds.Contains(rowA.RoleId)
select rowA.RoleName;

Related

Dynamic Linq on DataTable error: no Field or Property in DataRow, c#

I have some errors using Linq on DataTable and I couldn't figure it out how to solve it. I have to admit that i am pretty new to Linq and I searched the forum and Internet and couldn't figure it out. hope you can help.
I have a DataTable called campaign with three columns: ID (int), Product (string), Channel (string). The DataTable is already filled with data. I am trying to select a subset of the campaign records which satisfied the conditions selected by the end user. For example, the user want to list only if the Product is either 'EWH' or 'HEC'. The selection criteria is dynaically determined by the end user.
I have the following C# code:
private void btnClick()
{
IEnumerable<DataRow> query =
from zz in campaign.AsEnumerable()
orderby zz.Field<string>("ID")
select zz;
string whereClause = "zz.Field<string>(\"Product\") in ('EWH','HEC')";
query = query.Where(whereClause);
DataTable sublist = query.CopyToDataTable<DataRow>();
}
But it gives me an error on line: query = query.Where(whereClause), saying
No property or field 'zz' exists in type 'DataRow'".
If I changed to:
string whereClause = "Product in ('EWH','HEC')"; it will say:
No property or field 'Product' exists in type 'DataRow'
Can anyone help me on how to solve this problem? I feel it could be a pretty simple syntax change, but I just don't know at this time.
First, this line has an error
orderby zz.Field<string>("ID")
because as you said, your ID column is of type int.
Second, you need to learn LINQ query syntax. Forget about strings, the same way you used from, orderby, select in the query, you can also use where and many other operators. Also you'll need to learn the equivalent LINQ constructs for SQL-ish things, like for instance IN (...) is mapped to Enumerable.Contains etc.
With all that being said, here is your query
var productFilter = new[] { "EWH", "HEC" };
var query =
from zz in campaign.AsEnumerable()
where productFilter.Contains(zz.Field<string>("Product"))
orderby zz.Field<int>("ID")
select zz;
Update As per your comment, if you want to make this dynamic, then you need to switch to lambda syntax. Multiple and criteria can be composed by chaining multiple Where clauses like this
List<string> productFilter = ...; // coming from outside
List<string> channelFilter = ...; // coming from outside
var query = campaign.AsEnumerable();
// Apply filters if needed
if (productFilter != null && productFilter.Count > 0)
query = query.Where(zz => productFilter.Contains(zz.Field<string>("Product")));
if (channelFilter != null && channelFilter.Count > 0)
query = query.Where(zz => channelFilter.Contains(zz.Field<string>("Channel")));
// Once finished with filtering, do the ordering
query = query.OrderBy(zz => zz.Field<int>("ID"));

Unable to create a constant value of type 'IdentityUserRole'. Only primitive types or enumeration types are supported

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;

Linq and Lambda expression for a complex sql query involving joins

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.

Need help Understanding Linq group by with joins

Hi I created this Linq query
var k = from account in _session.All<AccountDetail>()
join subscriber in _session.All<Subscriber>() on account.ID equals subscriber.AccID
join subscriberServices in _session.All<SubscriberServce>() on subscriber.ID equals subscriberServices.UserID
join paymentMethod in _session.All<PaymentMethod>() on subscriberServices.PaymentMethod_ID equals paymentMethod.ID
join paymentFrequency in _session.All<PaymentFrequency>() on subscriberServices.PaymentFrequency_ID equals paymentFrequency.ID
group account by new {AccID= account.ID,paymentFrequency= paymentFrequency.Description,paymentMethod= paymentMethod.Description} into G
select new GenerateInvoiceData() { AccID = G.Key.AccID};
I don't understand
group account by new {AccID= account.ID,paymentFrequency= paymentFrequency.Description,paymentMethod= paymentMethod.Description} into G
why do I specify account when I'm not restricted to it in the anonymous type i.e. I can type paymentFrequency.Description.
The group account part is saying what you want the elements in each group to be. The by new { ... } is what you want the key for each group to be. That's not restricted to being part of the information in an element.
As a simplest example, you might have:
from person in people
group person.FirstName by person.LastName
which would give you groups where the key of each group was the last name of all the people represented in the group, and each element of each group would be the first name of someone.
You might want to read two of my Edulinq blog posts:
How query expressions work
The GroupBy method

ef and linq extension method

I have this sql that i want to have written in linq extension method returning an entity from my edm:
SELECT p.[Id],p.[Firstname],p.[Lastname],prt.[AddressId],prt.[Street],prt.[City]
FROM [Person] p
CROSS APPLY (
SELECT TOP(1) pa.[AddressId],a.[ValidFrom],a.[Street],a.[City]
FROM [Person_Addresses] pa
LEFT OUTER JOIN [Addresses] AS a
ON a.[Id] = pa.[AddressId]
WHERE p.[Id] = pa.[PersonId]
ORDER BY a.[ValidFrom] DESC ) prt
Also could this be re-written in linq extension method using 3 joins?
Assuming you have set the Person_Addresses table up as a pure relation table (i.e., with no data besides the foreign keys) this should do the trick:
var persons = model.People
.Select(p => new { p = p, a = p.Addresses.OrderByDescending(a=>a.ValidFrom).First() })
.Select(p => new { p.p.Id, p.p.Firstname, p.p.LastName, AddressId = p.a.Id, p.a.Street, p.a.City });
The first Select() orders the addresses and picks the latest one, and the second one returns an anonymous type with the properties specified in your query.
If you have more data in your relation table you're gonna have to use joins but this way you're free from them. In my opinion, this is more easy to read.
NOTE: You might get an exception if any entry in Persons have no addresses connected to them, although I haven't tried it out.

Resources