linq query with dynamic where clause - c#

I'm need to do the equivalent of a t-sql statement like this using LINQ:
SELECT * FROM mytable WHERE idnumber IN('1', '2', '3')
so in LINQ I have:
Dim _Values as String = "1, 2, 3"
Dim _Query = (From m In mytable Where idnumber = _Values Select m).ToList()
Not sure what to do with _Values to make idnumber evaluate each value in the string.
Thanks in advance.

I will go for Inner Join in this context. If I would have used Contains, it would Iterate unnecessarily 6 times despite of the fact that there is just one match.
C# Version
var desiredNames = new[] { "Pankaj", "Garg" };
var people = new[]
{
new { FirstName="Pankaj", Surname="Garg" },
new { FirstName="Marc", Surname="Gravell" },
new { FirstName="Jeff", Surname="Atwood" }
};
var records = (from p in people
join filtered in desiredNames on p.FirstName equals filtered
select p.FirstName
).ToList();
VB.Net Version
Dim desiredNames = New () {"Pankaj", "Garg"}
Dim people = New () {New With { _
.FirstName = "Pankaj", _
.Surname = "Garg" _
}, New With { _
.FirstName = "Marc", _
.Surname = "Gravell" _
}, New With { _
.FirstName = "Jeff", _
.Surname = "Atwood" _
}}
Dim records = ( _
Join filtered In desiredNames On p.FirstName = filtered).ToList()
Disadvantages of Contains
Suppose I have two list objects.
List 1 List 2
1 12
2 7
3 8
4 98
5 9
6 10
7 6
Using Contains, it will search for each List-1 item in List-2 that means iteration will happen 49 times !!!

Dim _Values as String = "1, 2, 3"
Dim _Query = (From m In mytable Where _Values.Split(", ").Contains(m.idnumber) Select m).ToList()

Related

May someone convert following linq query syntax into method syntax using lambda?

IList<Student> studentList = new List<Student>() {
new Student() { StudentID = 1, StudentName = "John", Age = 18, StandardID = 1 } ,
new Student() { StudentID = 2, StudentName = "Steve", Age = 21, StandardID = 1 } ,
new Student() { StudentID = 3, StudentName = "Bill", Age = 18, StandardID = 2 } ,
new Student() { StudentID = 4, StudentName = "Ram" , Age = 20, StandardID = 2 } ,
new Student() { StudentID = 5, StudentName = "Ron" , Age = 21 }
};
IList<Standard> standardList = new List<Standard>() {
new Standard(){ StandardID = 1, StandardName="Standard 1"},
new Standard(){ StandardID = 2, StandardName="Standard 2"},
new Standard(){ StandardID = 3, StandardName="Standard 3"}
};
var studentsWithStandard = from stad in standardList
join s in studentList
on stad.StandardID equals s.StandardID
into sg
from std_grp in sg
orderby stad.StandardName, std_grp.StudentName
select new {
StudentName = std_grp.StudentName,
StandardName = stad.StandardName
};
foreach (var group in studentsWithStandard)
{
Console.WriteLine("{0} is in {1}", group.StudentName, group.StandardName);
}
i tried. my code is following.
var studentsWithStandard = standardList.GroupJoin(studentList, stand => stand.StandardID, s => s.StandardID,
(stand, students) => new {StandardName = stand.StandardName, studentGroup = students}).OrderBy(an => an.StandardName);
output will be like this:
John is in Standard 1
Steve is in Standard 1
Bill is in Standard 2
Ram is in Standard 2
i got it from http://www.tutorialsteacher.com/codeeditor?cid=cs-JUmITE
Thanks in advance.
In my experience join is the one place where query syntax is more readable than lambda syntax, but regardless...
I would highly recommend reading Jon Skeet's excellent book C# In Depth. The chapters on LINQ give very clear explanations of what various query syntaxes translate to. https://www.manning.com/books/c-sharp-in-depth-third-edition
A join expression that only has one from will translate to the Join method, not GroupJoin. GroupJoin is used when you have two froms before the join.
You would want this to do an inner join:
standardList
.Join(studentList,
stad => stad.StandardID,
s => s.StandardID,
(stad, s) => new { Standard = stad, Student = s })
.OrderBy(x => x.Standard.StandardName)
.ThenBy(x => x.Student.StudentName)
You can translate the query like this:
var ans = standardList.Join(studentList, stad => stad.StandardID, s => s.StandardID, (stad, s) => new { stad, s })
.OrderBy(stads => stads.stad.StandardName).ThenBy(stads => stads.s.StudentName)
.Select(stads => new { stads.s.StudentName, stads.stad.StandardName });
Note that #JamesFaix's answer provides a more efficient less literal version that combines the Join and Select.
Which is actually the query comprehension version without into which isn't needed for your query:
var studentsWithStandard = from stad in standardList
join s in studentList on stad.StandardID equals s.StandardID
orderby stad.StandardName, s.StudentName
select new {
StudentName = s.StudentName,
StandardName = stad.StandardName
};
Note a strict translation of your query would involve GroupJoin/SelectMany but it isn't necessary since you aren't try to do a left join:
var ans2 = standardList.GroupJoin(studentList, stad => stad.StandardID, s => s.StandardID, (stad, sg) => new { stad, sg })
.SelectMany(stadsg => stadsg.sg.Select(s => new { stadsg.stad, s }))
.OrderBy(stads => stads.stad.StandardName).ThenBy(stads => stads.s.StudentName)
.Select(stads => new { stads.s.StudentName, stads.stad.StandardName });

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

Find foreign key matching multiple row values using linq

I have a table called conversation that lists the users who are in the same conversation. Eg:
id | conversation | user
1 | 1 | Bob
2 | 1 | Jane
3 | 2 | Tim
4 | 2 | Lily
5 | 2 | Rick
And I have list has some users like so..
List<string> usernames = new List<string>{"Bob","Jane"};
I now want to check when a user wants to start a conversation with other users whether they have previously had a conversation with those other users (exclusively).
Eg. Bob wants to create a new conversation with Jane.(I have Bob and Jane in my username List values to compare if those have been in conversation before?)
As we see they two have a conversation already, I want to get the conversation id belong these two guys.
if my list contains following data like so..
List<string> usernames = new List<string>{"Bob","Jane","Tim"};
This time I am expecting there is no conversation those 3 before.
I want to find out there is no conversation i can create one new for them.
I have trying this with Linq but cannot get any correct result so far.
Thank in advance for your help;
You can group by conversation IDs and match the groups with your list of users:
var previousConversations = userConversations
.GroupBy(uc => uc.Conversation)
.Where(g => g.OrderBy(uc => uc.user).Select(uc => uc.user)
.SequenceEqual(usernames.Sort()));
You could use the SequenceEqual method to compare 2 sequences.
var users1 = new List<String> { "Bob", "Jane" };
var users2 = new List<String> { "Bob", "Jane", "Tim" };
var tableData = new List<YourTable>
{
new YourTable {foreignKey = 1, name = "Bob"},
new YourTable {foreignKey = 1, name = "Jane"},
new YourTable {foreignKey = 2, name = "Tim"},
new YourTable {foreignKey = 2, name = "Lily"},
new YourTable {foreignKey = 2, name = "Rick"},
};
var keyFound = (from t in tableData
group t by t.foreignKey into users
where users.Select(u => u.name).SequenceEqual(users1)
select users.Key).FirstOrDefault();
var keyNull = (from t in tableData
group t by t.foreignKey into users
where users.Select(u => u.name).SequenceEqual(users2)
select users.Key).FirstOrDefault();
Edit:
You're using linq as a means to fetch data from the database, not all operations are supported this way. What we can do is extract the selection in memory and then we can use all operations again.
Depending on your situation this might not be a good idea, usually you want to let sql handle all the query power since he is better at that.
But if you are selecting on a not so big table you can easily pull in memory feel free to do it like this :
var rawData = (from t in tableData
group t by t.foreignKey into users
select users).ToList();
var key = (from d in rawData
where d.Select(u => u.name).SequenceEqual(users2)
select d.Key).FirstOrDefault();
If on the other hand the data is too big and you want it executed on sql side i would consider make a stored procedure for this.
You're basically doing a "these tags" query, which is answered over here:
Select items by tag when searching multiple tags
Applied to your problem, it looks like:
int userCount = myUsers.Count;
List<int> conversationIds = conversationsUsers
.Where(cu => myUsers.Contains(cu.UserName))
.GroupBy(cu => cu.ConversationId)
.Where(g => g.Select(cu => cu.Username).Distinct().Count() == userCount)
.Select(g => g.Key)
(exclusively).
Well, ok... Move the user filtering into the group filtering.
int userCount = myUsers.Count;
List<int> conversationIds = conversationsUsers
.GroupBy(cu => cu.ConversationId)
.Where(g => g.Select(cu => cu.Username).Distinct().Count() == userCount)
.Where(g => g.Select(cu => cu.UserName).All(userName => myUsers.Contains(userName)))
.Select(g => g.Key)
Check with the following linq. It will result in conversation id if any conversation is else empty (enumeration yields no result). You can check your conditions accordingly.
var cust = new List<Table>
{
new Table {Conversation = 1, Id = 1, User = "Bob"},
new Table {Conversation = 1, Id = 2, User = "Jane"},
new Table {Conversation = 2, Id = 3, User = "Tim"},
new Table {Conversation = 2, Id = 4, User = "Lily"},
new Table {Conversation = 2, Id = 5, User = "Rick"}
};
var names = new List<string> { "Rick", "Lily", "Tim" };
var res = from x in cust
group x by x.Conversation
into y
where y.Select(z => z.User).Intersect(names).Count() == names.Count
select y.Key;
Thanks everybody for your help and answers. The following code did the trick and gives me the result what I wanted. Thank you Kristof for providing this code and your effort. I have attached order by clause in second query otherwise it wont gives the right result. Hope this helps someone who need similar stuff.
var users1 = new List<String> { "Bob", "Jane" };
var users2 = new List<String> { "Bob", "Jane", "Tim" };
var tableData = new List<YourTable>
{
new YourTable {foreignKey = 1, name = "Bob"},
new YourTable {foreignKey = 1, name = "Jane"},
new YourTable {foreignKey = 2, name = "Tim"},
new YourTable {foreignKey = 2, name = "Lily"},
new YourTable {foreignKey = 2, name = "Rick"},
};
var rawData = (from t in tableData
group t by t.foreignKey into users
select users).ToList();
var key = (from d in rawData
where d.Select(u => u.name).OrderBy(s=> s).SequenceEqual(users2.OrderBy(s=>s))
select d.Key).FirstOrDefault();

LINQ - Can't add column to result without changing multi-table grouping

Suppose I have a "database" defined as:
// Baked goods vendors
var vendor = new[] {
new { ID = 1, Baker = "Pies R Us", StMnemon = "NY", Items = 8, Rating = 9 },
new { ID = 2, Baker = "Mikes Muffins", StMnemon = "CA", Items = 5, Rating = 9 },
new { ID = 3, Baker = "Best Bakers", StMnemon = "FL", Items = 2, Rating = 5 },
new { ID = 4, Baker = "Marys Baked Treats", StMnemon = "NY", Items = 8, Rating = 7 },
new { ID = 5, Baker = "Cool Cakes", StMnemon = "NY", Items = 4, Rating = 9 },
new { ID = 6, Baker = "Pie Heaven", StMnemon = "CA", Items = 12, Rating = 9 },
new { ID = 7, Baker = "Cakes N More", StMnemon = "GA", Items = 6, Rating = 8 },
new { ID = 8, Baker = "Dream Desserts", StMnemon = "FL", Items = 2, Rating = 7 }
};
// Locations
var location = new[] {
new {ID= 1, State = "New York", Mnemonic = "NY"},
new {ID= 2, State = "Massachusetts", Mnemonic = "MA"},
new {ID= 3, State = "Ohio", Mnemonic = "OH"},
new {ID= 4, State = "California", Mnemonic = "CA"},
new {ID= 5, State = "Florida", Mnemonic = "FL"},
new {ID= 6, State = "Texas", Mnemonic = "TX"},
new {ID= 7, State = "Georgia", Mnemonic = "GA" }
};
I want to build a query that would be the equivalent of the SQL query:
SELECT State, Rating, SUM(Items) AS 'Kinds'
FROM vendor, location
WHERE vendor.StMnemon = location.Mnemonic
GROUP BY State, Rating
Two things of interest in this query are:
The GROUP BY involves multiple tables, and
The result contains a summation of a column not appearing in the grouping criteria.
I've seen the solutions in the posts on grouping by multiple tables and summing columns not in the group-by. The problem is that combining both doesn't really duplicate the relational query.
I try to duplicate it in LINQ with the following code:
var query = from v in vendor
join l in location
on v.StMnemon equals l.Mnemonic
orderby v.Rating ascending, l.State
select new { v, l };
var result = from q in query
group q by new {
s = q.l.State,
r = q.v.Rating
/* ==> */ , i = q.v.Items
} into grp
select new
{
State = grp.Key.s,
Rating = grp.Key.r
/* ==> */ , Kinds = grp.Sum(k => grp.Key.i)
};
This results in:
=================================
State Rating Kinds
Florida 5 2
Florida 7 2
New York 7 8
Georgia 8 6
California 9 5
California 9 12
New York 9 8
New York 9 4
=================================
Whereas, the SQL query given above gives this result:
=========================
State Rating Kinds
Florida 5 2
Florida 7 2
New York 7 8
Georgia 8 6
California 9 17
New York 9 12
=========================
The discrepancy is because there seems to be no place to put additional columns, other than in the grouping criteria, which of course changes the grouped result. Commenting out the two lines indicated by the /* ==> */ comment in the code above will give the same grouping as the SQL result, but of course that removes the summation field that I want to include.
How do we group multiple tables in LINQ and include additional criteria without changing the grouped result?
something like this seems to return the same as the SQL query:
var result = from v in vendor
from l in location
where l.Mnemonic == v.StMnemon
group v by new { l.State, v.Rating } into grp
orderby grp.Key.Rating ascending, grp.Key.State
select new {State = grp.Key.State, Rating = grp.Key.Rating, Kinds = grp.Sum(p=>p.Items)};
foreach (var item in result)
Console.WriteLine("{0}\t{1}\t{2}", item.State, item.Rating, item.Kinds);
You can do an aggregation outside of the group:
var query = from v in vendor
join l in location
on v.StMnemon equals l.Mnemonic
orderby v.Rating ascending, l.State
select new { v, l };
var result = from q in query
group q by new {
s = q.l.State,
r = q.v.Rating
} into grp
select new
{
State = grp.Key.s,
Rating = grp.Key.r,
Kinds = grp.Sum(g => g.Items)
};
Grouping is a little tricky to grasp - it returns an IGrouping that has one property - Key. The actual items in that grouping are returned by the GetEnumerator() function that lets you treat the group as a collection of those items, meaning you can do aggregation on the items within that group.

c # using linq to group by multiple columns in a datatable

I have three columns in a datatable: string, DateTime, and decimal. I want to group by the string and decimal column, and for the rows grouped I want to sum the decimal values. I know how to do the sum part, but how do you group two different columns in a datatable?
This is my code so far which doesn't work properly:
var newSort = from row in objectTable.AsEnumerable()
group row by new {ID = row.Field<string>("resource_name"), time1 = row.Field<DateTime>("day_date")} into grp
orderby grp.Key
select new
{
resource_name1 = grp.Key.ID,
day_date1 = grp.Key.time1,
Sum = grp.Sum(r => r.Field<Decimal>("actual_hrs"))
};
I don't think you're giving us the full story. Other than orderby not working with anonymous types (the code you gave wouldn't have compiled), your query should work the way you want. I just put this in LINQPad:
var objectTable = new DataTable();
objectTable.Columns.Add("resource_name",typeof(string));
objectTable.Columns.Add("day_date",typeof(DateTime));
objectTable.Columns.Add("actual_hrs",typeof(decimal));
objectTable.Rows.Add(1, DateTime.Today, 1);
objectTable.Rows.Add(2, DateTime.Today, 2);
var newSort = from row in objectTable.AsEnumerable()
group row by new {ID = row.Field<string>("resource_name"), time1 = row.Field<DateTime>("day_date")} into grp
select new
{
resource_name1 = grp.Key.ID,
day_date1 = grp.Key.time1,
Sum = grp.Sum(r => r.Field<Decimal>("actual_hrs"))
};
newSort.Dump();
... and I got these results:
resource_name1 | day_date1 | Sum
1 | 7/1/2011 12:00:00 AM | 1
2 | 7/1/2011 12:00:00 AM | 2
use this code
var newSort = from row in objectTable.AsEnumerable()
group row by new {ID = row.Field<string>("resource_name"), time1 = row.Field<DateTime>("day_date")} into grp
orderby grp.Key
select new
{
resource_name1 = grp.Key.ID,
day_date1 = grp.Key.time1,
Sum = grp.Sum(r => Convert.ToDecimal(r.ItemArray[2]))
};
For those that want a solution in Vb.net, here is an example:
Dim workTable As DataTable = New DataTable("Customers")
Dim workCol As DataColumn = workTable.Columns.Add("ID", Type.GetType("System.Int32"))
workTable.Columns.Add("Total", Type.GetType("System.Decimal"))
workTable.Columns.Add("Compra", Type.GetType("System.Decimal"))
Dim row As DataRow = workTable.NewRow()
row("id") = 2
row("total") = 1.5
row("compra") = 3
workTable.Rows.Add(row)
row = workTable.NewRow()
row("id") = 1
row("total") = 1.5
row("compra") = 3.3999999999999999
workTable.Rows.Add(row)
row = workTable.NewRow()
row("id") = 1
row("total") = 1.5
row("compra") = 5
workTable.Rows.Add(row)
Dim detalles As IEnumerable(Of DataRow) = workTable.AsEnumerable()
Dim query = From detalle In detalles.AsEnumerable() _
Group detalle By grupoClave = New With _
{ _
Key .C2 = detalle("id"), _
Key .C4 = detalle("total")} Into g = Group _
Select New With _
{ _
.Col2 = g(0).Field(Of Integer)("id"), _
.Col3 = g(0).Field(Of Decimal)("total"), _
.Col4 = g.Sum(Function(fact) fact.Field(Of Decimal)("compra")) _
}
For Each p In query
Console.WriteLine((p.Col2 & p.Col3 & p.Col4))
Next

Categories