LINQ request many-to-many - c#

I have the following tables
Users
- ID
- FirstName
- LastName
MultiplyItems
- ItemID
- Title
UserMultiplyItems
- UserID
- ItemID
I have a variable
List<int> delegateList = {1, 3, 5};
where 1, 3, 5 are ItemID
I want to select all users, where at least one ItemID linked selectable user.
I try the following:
var result = from i in _dbContext.Users
where
((delegateList == null) || i.MultiplyItems.Any(p=> delegateList.Any(a => a == p.ItemID)))
select new UserModel()
{
....
};
but it does not work. error:
Cannot compare elements of type 'System.Collections.Generic.List`1'.
Only primitive types, enumeration types and entity types are
supported.
How to do it correctly?
Thanks

I would write this one:
var filteredUsers = delegateList == null
? _dbContext.Users
: _dbContext.Users.Where(user => user.MultiplyItems
.Any(item => delegateList.Contains(item.Id)));
var result = filteredUsers.Select(user => new UserModel
{
//the UserModel initialization
});
You should not check the following line inside the query:
delegateList == null
It is translated to SQL, and SQL has no idea about what is List and how to compare it with null.

var result = from i in _dbContext.Users
from mi in i.multiplyItems
select new yourClass();
if(delegateList!=null)
{
result = result.Where(delegateList.contains(mi.ItemID));
}
result.ToList();
I dont have visual studio open to test, but it should be a lot like that.

I'm not exactly sure if this is what you wanted, but i thought its better to try and help.
This will output all users from the Users Table where the ItemID is contained in the delegateList. The magic lays in the Contains operator you can get elements from list a contained in list b
var selection = from a in db.UserMultiplyItems
from b in db.Users
where delegateList.Contains(a.ItemID) && a.UserID == b.ID
select b;

Try changing it to:
var result = from u in _dbContext.Users
where
((delegateList == null) || u.MultiplyItems.Any( mi => delegateList.Contains(mi.ItemID)))
select new UserModel()
{
....
};
Note I've also renamed your "i" and "p" things to "u" and "mi" to make it easier to read.

Alternatively you could not use LINQ at all and just stick with lambda expressions:
List<UserModel> usersWithItems =
context
.Users
.Where(u => u.MultiplyItems.Any(mi => (delegateList == null) || (delegateList.Contains(mi.ItemID))))
.Select(um => (new UserModel() { ... } ) )
.ToList();
Which personally I prefer, it means you don't need to know linq at all.

Related

selecting properties from other table with Lambda expression

I am less experienced with Lambda expression for .NET and trying to get data from SQL using lambda expression. With below query, I am able to get data back, but do not want to use include to get all properties from other tables.
public IEnumerable<ResourceGroup> GetAllServersByApplication(string application_name, string environment_name, string status)
{
var query = _context.ResourceGroup
.Include(a => a.Application)
.Include(t => t.Type)
.Include(e => e.ServersGroup).ThenInclude(e => e.Environment)
.Include(s => s.ServersGroup).ThenInclude(s => s.Server)
.Include(s => s.ServersGroup).ThenInclude(s => s.Server).ThenInclude(s => s.Status)
.Where(a => a.Application.Name == application_name && a.ServersGroup.Any(s => s.Environment.Name == environment_name && s.Server.Status.Name == status))
.ToList();
return query;
}
Lets take an example of below include statement.
.Include(s => s.ServersGroup).ThenInclude(s => s.Server)
From s.Server, I only want to select Id,ServerName,Status, and IPAddress. These are the properties from Servers class that I created as a model.
What is the easy way to exclude all the includes and only show properties that I am interested in?
Here are my tables and its properties:
Status table:
Id, Name
Application table:
Id, Name
Servers table:
Id, ServerName, Status
Environments table:
Id, Name
ResourceGroup table:
Id, Name, Application_Id, Environment_Id
ServersResourceGroup table:
Id, Server_Id, Resource_Id
UPDATE 1:
var query = _context.ResourceGroup
.SelectMany(rg => rg.ServersGroup
.Select(sg => new
{
ResourceName = rg.Name,
ApplicationName = rg.Application.Name,
ServerName = sg.Server.ServerName,
EnvironmentName = sg.Environment.Name,
Status = sg.Server.Status.Name
})).Where(a => a.ApplicationName == application_name && a.EnvironmentName == environment_name && a.Status == status).ToList();
return query;
And error from red line on query variable:
UPDATE 2:
Here is the query syntax:
var query = from rg in _context.ResourceGroup
let app = rg.Application
from sg in rg.ServersGroup
let env = sg.Environment
let srv = sg.Server
let stat = srv.Status
where app.Name == application_name
&& rg.ServersGroup.Any(s => s.Environment.Name == environment_name
&& s.Server.Status.Name == status)
select new
{
ResourceGroupName = rg.Name,
ApplicationName = app.Name,
ServerName = srv.ServerName,
Alias = srv.Alias,
IPAddress = srv.IPAddress,
Type = rg.Type.Name,
Status = stat.Name
};
return query;
Here is the red line error I get in query variable:
Your help is really appreciated. :)
Thanks,
Ray
With lambda expressions, you can use SelectMany to flatten 1-n associations into a 1 dimensional list (i.e. parent and child properties side-by-side). In your case, judging from the Where clause, I think only ResourceGroup - ServerGroup is 1 - n, so it should be something like:
var query = _context.ResourceGroup
.SelectMany
(
rg => rg.ServersGroup
.Select(sg => new
{
ResourceGroup = rg.Name,
Application = rg.Application.Name,
Server = sg.Server.ServerName,
// etc.
})
)
Of course it's good to know how to use lambda expressions, but there's really no point in using them when query syntax makes for much better comprehensible code.
The equivalent in query syntax is:
var query = from rg in _context.ResourceGroup
let app = rg.Application
from sg in rg.ServersGroup
let env = sg.Environment
let srv = sg.Server
let stat = srv.Status
where app.Name == application_name
&& sg.ServersGroup.Any(s => s.Environment.Name == environment_name
&& s.Server.Status.Name == status)
select new
{
ResourceGroup = rg.Name,
Application = app.Name,
Server = srv.ServerName,
// etc. use any property from rg, app, sg, srv, stat
};
As you see -
n - 1 associations are represented by a let statement (which really only helps here to shorten the references in the select)
1-n associations are represented by the from ... from syntax, which is query syntax for SelectMany.
I didn't change the Where clause in the query syntax. Maybe you can use ...
where app.Name == application_name
&& env.Name == environment_name
&& stat.Name == status)
... but note that this is different. The original where returns all ResourceGroup having at least one ServerGroup meeting the condition (and maybe other groups with different environments and statuses). The other where only returns data with environments and statuses equal to the search parameters.
Don't Include all the related tables, but Select all the fields you need. You might find it easier to make a new class to hold the data.
Sorry if I can't make a real query statement here, but your question doens't specify the fields you need.

How to add condition to LINQ query based on Id

This is my action method which fetches all the users with their Id.
public JsonResult GetUsers()
{
var ret = (from user in db.Users.ToList()
select new
{
UserName = user.UserName,
// i am stuck here, i want to get all those ids whom current logged user is following
Idfollowing = user.FollowTables.Contains()
Idnotfollowing =
});
return Json(ret, JsonRequestBehavior.AllowGet);
}
the structure of FollowTable is like this:
ID UserId FollowId
1 4 11
2 4 12
2 4 13
here, current loggedin user's id is 4 and he is following 11, 12, 13 so i want to return only 11, 12 and 13 to Idfollowing and rest remaining id in the Idnotfollowing. how to get it done.
Well, i think with list or array, i will not get desired result. so, i want to add something here.
Well, with every UserName an id is also passed to view page. So, i have break them into two.Now, how to assign values to these ids.
Comapre User.Id with Current loggedin user's follow table's followId column.If match is found .i.e if id matches or found then assign that user.Id to Idfollowing and null to Idnotfollowing and vice versa in opposite case.
I have to generate follow unfollow button based on these ids returned.
public JsonResult GetUsers()
{
int currentUserId = this.User.Identity.GetUserId<int>();
var ret = (from user in db.Users.ToList()
let Id = user.FollowTables.Where(x => x.UserId == currentUserId).Select(f => f.FollowId).ToList()
let Idnot = (from user2 in db.Users
where !Id.Contains(user2.Id)
select user2.Id).ToList()
select new
{
UserName = user.UserName,
Id = Id,
//Id = user.FollowTables.Where(x => x.UserId == currentUserId)
// .Select(x => x.FollowId).Single(),
Idnot = Idnot,
It looks like you have a standard one-to-many relationship from User to FollowTable. This data model enforces that user.FollowTables only contains followers. You won't be able to fill in Idnotfollowing from the FollowTables property directly.
Something like this may work:
var query = (
from user in db.Users // note: removed ToList() here
// to avoid premature query materialization
where //TODO ADD WHERE CLAUSE HERE ?
let followIds = user.FollowTables.Select(f => f.FollowId)
let notFollowIds = (from user2 in db.Users
where !followIds.Contains(user2.Id)
select user2.Id)
select new
{
UserName = user.UserName,
Idfollowing = followIds.ToArray(),
Idnotfollowing = notFollowIds.ToArray()
})
// TODO add paging? .Skip(offset).Take(pageSize)
.ToList();
Do verify the SQL generated by this query and make sure it performs ok though...
Also, note that I removed the .ToList() from db.Users.ToList() to avoid premature query materialization. It is generally a bad idea anyway to extract all data from a table unconstrained, you will typically want to a
var ret = (from user in db.Users.ToList()
select new
{
UserName = user.UserName,
Idfollowing = user.FollowTables.Select(x=> x.Id)
Idnotfollowing = db.FollowTables.Where(x=> !user.FollowTables.Select(x=> x.Id).Contains(x.Id)).Select(x=> x.Id)
});
it's ugly but will work, there must be another better way to do.
You can simply use a Where method to filter the table and use Select to project FollowiId:-
var ret = (from user in db.Users.ToList()
select new
{
UserName = user.UserName,
Idfollowing = user.FollowTables.Where(x => x.UserId == user.Id)
.Select(x => x.FollowId).ToArray(),
Idnotfollowing = user.FollowTables.Where(x => x.UserId != user.Id)
.Select(x => x.FollowId).ToArray()
});
Assuming, Idfollowing & Idnotfollowing are array if integers (if FollowId is integer) otherwise you can replace it with ToList if its a list instead.

Can I combine two Linq calls when they use different syntax?

I have the following code. The function has a lot of Linq calls and I had help on putting this into place.
public IList<Content.Grid> Details(string pk)
{
IEnumerable<Content.Grid> details = null;
IList<Content.Grid> detailsList = null;
var data = _contentRepository.GetPk(pk);
var refType = this.GetRefType(pk);
var refStat = this.GetRefStat(pk);
var type = _referenceRepository.GetPk(refType);
var stat = _referenceRepository.GetPk(refStat);
details =
from d in data
join s in stat on d.Status equals s.RowKey into statuses
from s in statuses.DefaultIfEmpty()
join t in type on d.Type equals t.RowKey into types
from t in types.DefaultIfEmpty()
select new Content.Grid
{
PartitionKey = d.PartitionKey,
RowKey = d.RowKey,
Order = d.Order,
Title = d.Title,
Status = s == null ? null : s.Value,
StatusKey = d.Status,
Type = t == null ? null : t.Value,
TypeKey = d.Type,
Link = d.Link,
Notes = d.Notes,
TextLength = d.TextLength
};
detailsList = details
.OrderBy(item => item.Order)
.ThenBy(item => item.Title)
.Select((t, index) => new Content.Grid()
{
PartitionKey = t.PartitionKey,
RowKey = t.RowKey,
Row = index + 1,
Order = t.Order,
Title = t.Title,
Status = t.Status,
StatusKey = t.StatusKey,
Type = t.Type,
TypeKey = t.TypeKey,
Link = t.Link,
Notes = t.Notes,
TextLength = t.TextLength,
})
.ToList();
return detailsList;
}
The first uses one format for Linq and the second another. Is there some way that I could simplify and/or combine these? I would really like to make this code simpler but I am not sure how to do this. Any suggestions would be much appreciated.
Of course you can combine them. The Linq keywords such as from, where and select get translated into calls like the Extension methods that you call below, so effectively there's no difference.
If you really want to combine them, the quickest way is to put () around the first query, then append the method calls you use on details in the second query. Like this:
detailsList =
(from d in data // <-- The first query
// ...
select new Content.Grid
{
// ...
})
.OrderBy(item => item.Order) // <-- The calls from the second query
.ThenBy(item => item.Title)
.Select((t, index) => new Content.Grid()
{
//...
}).ToList();
But i think that would be ugly. Two queries are just fine IMO.

using linq to order tickets by lowest date where ticket is not closed?

I have an object called Ticket with that contains a list of objects called TicketActions. The Ticket object has a field called Date_Closed and the Actions object has a field called Action_Date:
Ticket
Date_Closed
TicketActions
-Action_Date
What I'm trying to do is order a List of tickets (List) based on the latest date of each Action in ascending order where the Ticket does not have a value for Date_Closed. The goal is to load this list into a listview and show tickets in a way that displays tickets in order on the page, placing the ones that have gone the longest without an action at the top. Does that make sense?
Here is what I ended up with so far that isn't working:
protected List<FullTicket> BuildTickets(int ticketsToShow)
{
using (var db = new SupportLogDBDataContext())
{
var result =
(from ticket in db.Support_Tickets
join status in db.Ticket_Statuses on ticket.Status_ID equals status.ID
select new FullTicket
{
TicketID = ticket.ID,
DateOpened = (DateTime)ticket.Date_Opened,
DateClosed = (DateTime)ticket.Date_Closed,
Subject = ticket.Subject,
Status = new KeyPair { Key = status.Status, Value = status.ID },
CreatedBy = new GuidPair { Key = ticket.Reported_By, Value = (Guid)ticket.AD_GUID },
TicketActions =
(from a in db.Ticket_Actions
where a.Ticket_ID == ticket.ID
select a).ToList()
}).Take(ticketsToShow).ToList();
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max()); //error on this line (invalid arguments)
return result;
}
}
People reply quick here!
Try this:
var result = (from ticket in tickets
where !ticket.DateClosed.HasValue
select ticket).OrderByDescending(t => (from a in t.TicketActions
select a.ActionDate).Max());
From here you can take as many as you need.
David B's analysis is slightly off. The line...
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
... will not compile because the argument to the Where method is not a lambda expression or delegate.
I would suggest this solution (assuming that the relevant property of the TicketAction type is ActionDate):
return result.Where(i => i.DateClosed == null)
.OrderBy(i => i.TicketActions.Max(a => a.ActionDate));
Or, in query comprehension syntax:
return from i in result
where i.DateClosed == null
orderby i.TicketActions.Max(a => a.ActionDate)
select i;
Here is some simple code.
var sorted = tickets.Where(t => t.DateClosed == null)
.OrderBy(t => t.TicketActions.Max(ta => ta.Action_Date.Ticks));
Sorry, I prefer LINQ function syntax, but if you want it in query syntax, it shouldn't be too hard to convert.
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
This line generates an error because TicketActions.Max() is not defined.
You need to project TicketAction into something that can be Max'd. For example:
result.OrderBy(i =>
i.TicketActions
.Where(ta => i.DateClosed == null)
.Select(ta => ta.Id)
.Max()
);
Also note:
OrderBy does not modify its source. OrderBy returns an ordered IEnumerable, which you didn't assign anywhere.
OrderBy's enumerable is deferred, and you want a List result instead, so you should call ToList.
You are accessing Ticket.TicketActions outside of the query. This will cause one database round trip per ticket to load that property.
Here is a modification to your query that avoids the problems mentioned above by ordering and using navigational properties within the query.
from ticket in db.Support_Tickets
where ticket.DateClosed == null
let lastDate = ticket.TicketActions
.Select(ta => ta.ActionDate)
.OrderByDescending(date => date)
.FirstOrDefault()
let ticketStatus = ticket.TicketStatus
order by lastDate
select new FullTicket
{
...
}

IN Operator in where clause

List<HelprClass.Organizer> org =
( from EventOrg in cntx.EventOrganizer
from MstrOrg in cntx.Organizer
where EventOrg.OrganizerID == MstrOrg.OrganizerID
Select new HelprClass.Organizer
{
OrganizerName = MstrOrg.OrganizerName
}).ToList()
This work fine now i want to use IN Opeartor in the above Query.
in the EventOrganizer I have EventID now i want to select only Event ID exsist in EventOrganizer collection
I have EventID another var varibale
Var EventID= From EvntID in Evetn Select new {ID= EvntID.EventID};
Something like this
where
EventOrg.OrganizerID == MstrOrg.OrganizerID
&& EventOrg.EventID in EventID.ID
How I can achive this ?
I will appreciate your help
Try this:
var EventIDs = from EvntID in Event select EvntID.EventID;
var org = (from EventOrg in cntx.EventOrganizer
from MstrOrg in cntx.Organizer
where EventOrg.OrganizerID == MstrOrg.OrganizerID
select new {E=EventOrg, M=MstrOrg}
).ToList();
org = org
.Where(o => EventIDs.Contains(o.E.EventID) )
.Select(o => new HelprClass.Organizer
{
OrganizerName = o.M.OrganizerName
}
);
If you are using Entity Framework you can not use a Contains statement, which would be an easy solution in Linq. SO if it's just Linq 2 Entities, use a where clause like "where EventId.Contains(EventOrg.Id)"
If you are indeed using Entity Framework you will have to build an or expression based on the Id's in the EventID Collection.

Categories

Resources