linq-to-sql getting sequence contains more than one element - c#

I have a query that looks like this: it takes a list of IDs (ThelistOfIDs) as parameter and I'm grouping for a count.
var TheCounter = (from l in MyDC.SomeTable
where ThelistOfIDs.Contains(l.ID)
group l by l.Status into groups
select new Counter()
{
CountOnes = (from g in groups
where g.Status == 1
select g).Count(),
CountTwos = (from g in groups
where g.Status == 2
select g).Count(),
}).Single();
And basically, I don't understand why I'm getting the error. I don't want to brring back the entore collection from the DB and do the count in linq-to-object; I want to do the count in the DB and bring back the result.

I have not put your query into my IDE or compiled with C#, but I guess the problem is that
groups in your query is IGrouping<Tkey, Telm> and not IQueryable<Tkey>
(where Tkey is type of l.Status and Telm is type of l).
I think you got confused with the use of grouping operator.
What you want to get is I guess:
var queryByStatus = from l in MyDC.SomeTable
where ThelistOfIDs.Contains(l.ID)
group l by l.Status;
var counter = new Counter()
{
CountOnes = queryByStatus.Where(l => l.Key == 1).Count(),
CountTwos = queryByStatus.Where(l => l.Key == 2).Count(),
};
EDIT:
Alternative query, to obtain the same, moving all operation on DB into the original query so that DB is queried only once.
var queryCountByStatus = from l in MyDC.SomeTable
where ThelistOfIDs.Contains(l.ID)
group l by l.Status into r
select new { status = r.Key, count = r.Count() };
var countByStatus = queryCountByStatus.ToList();
var counter = new Counter()
{
CountOnes = countByStatus.FirstOrDefault(l => l.status == 1).count,
CountTwos = countByStatus.FirstOrDefault(l => l.status == 2).count,
};
Note:
The query in my edit section queries the DB once only and mapping Status -> Count is returned.
Note that in my original query there were two calls to DB needed only - both of which returned single number - one for CountOnes, one for CountTwos.
In the edit query, one query is done which return table { { 1, CountOnes}, {2, CountTwos } }. The other lines are just to convert the result - which is set of items - into single object having certain objects as properties and is done physically on these two values.

You are grouping by Status, and then projecting from that group - but you will still have one row per unique Status (===group).
So: I propose that you don't have exactly one unique Status.

This might be what you're looking for to get...
(it's for users table I had but should be the same)
var statuscounts = (from u in db.Users
where u.UserStatus > 0
group u by u.UserStatus into groups
select new { Status = groups.Key, Count = groups.Count() });
// do this to iterate and pump into a Counter at will
foreach (var g in statuscounts)
Console.WriteLine("{0}, {1}", g.Status, g.Count);
...or even something like this...
var counter = statuscounts.AsEnumerable()
.Aggregate(new Counter(), (c, a) => {
switch (a.Status)
{
case 1: c.CountOfOnes = a.Count; return c;
case 2: c.CountOfTwos = a.Count; return c;
case 3: c.CountOfThrees = a.Count; return c;
default: c.CountOfOthers = a.Count; return c;
}});
...point is that if you're grouping already you should use the grouping result, it's of type IGrouping<out TKey, out TElement> where the key is your status and it's IEnumerable<> or your records.
hope this helps

Related

Returning List<string> from Linq query returns query syntax not values

I have the below code to return a list of strings.
public List<string> Top5CodesForToday()
{
var date = DateTime.Now;
var resultList = new List<string>();
using (var db = new PillowContext())
{
var qry = (from d in db.DownTimes
where DbFunctions.TruncateTime(d.DateTime) == DbFunctions.TruncateTime(date)
group d by new {d.Code}
into g
let total = g.Sum(x => x.Amount)
orderby total descending
let top5 = g.Take(5).ToList()
select new {g.Key.Code, Total = total});
foreach (var item in qry)
{
int x = item.Code;
var results = from r in db.DownTimeCodes
where r.Code == x
select r.Description;
resultList.Add(results.ToString());
}
}
return resultList;
}
When I look at the contents of returnList I am seeing the correct number of items however each item is made up of the actual query syntax, not the data itself. I have seen this before and usually solve it by doing .ToList() however I am unsure how I could change my code to solve this
The problem here is that when you are calling ToString the query is not executed yet, so essentially you are calling ToString on a IQueryable object, receiving the query instead of results. You need to call something to execute the query.
You can call ToList() still:
var results = (from r in db.DownTimeCodes
where r.Code == x
select r.Description).ToList();
resultList.AddRange(results);
Or, if you expect just one result, call FirstOrDefault()/SingleOrDefault():
var results = (from r in db.DownTimeCodes
where r.Code == x
select r.Description).FirstOrDefault();
resultList.Add(results);
You are calling ToString() on List<>. As default for most complex types, it just writes out type name not the data.
This line
resultList.Add(results.ToString());
should be changed to
resultList.AddRange(results);

Controlling LINQ query order based on other list of integers

I'm doing a LINQ query where I select the video info from table Videos. The query selects only those videos whose IDs are present in the following list:
List<int> results; //Has some values
var query = from l in dataContext.Videos
where results.Contains(l.ID)
select l;
Now how do I order the items(Video infos) in the query such the their IDs follow the same order as the List results?
I am able to do this as:
List<int> results; //Has some values
var query = from k in results
from l in dataContext.Videos
where k==l.ID
select l;
But this is slow, I need something faster.
Use a join, it's much faster
var orderedByIDList = from k in results
join l in dataContext.Videos
on k equals l.Id
select l;
Addon/Edit due to #MarcinJuraszek and #Phil comments, thanks guys.
Basically grab your data first, then sort so here's what I got:
var myList = (from l in dataContext.Videos
where results.Contains(l.ID)
select l).ToList(); //grab data and resolve to list or array
var orderedByIDList = from k in results
join l in myList
on k equals l.Id
select l; //result type IEnumerable<Video>
Here's my alternative attempt (probably not as fast as a join), which retrieves the minimum set of rows and then orders the data locally.
var results = new List<int>{ 9, 2, 3, 6, 8 };
// record the original order
var results2 = results.Select ((r, index) => new {r, index});
// get results and convert to list
var videos = dataContext.Videos.Where(v => results.Contains(v.Id)).ToList();
// order according to results order
var ordered = videos.Select (v =>
new {v, results2.Single (r => r.r == v.Id).index})
.OrderBy (v => v.index).Select (v => v.v)

Use Any() and Count() in Dynamic Linq

I am trying to write dynamic Linq Library query to fetch record on condition,
Customers who has order count is greater than 3 and ShipVia field equal 2.
Below is my syntax what i have tried.
object[] objArr = new object[10];
objArr[0] = 1;
IQueryable<Customer> test = db.Customers.Where("Orders.Count(ShipVia=2)", objArr);
and
IQueryable<Customer> test = db.Customers.Where("Orders.Any(ShipVia=2).Count()", objArr);
But both are not working. In second query Any returns true so it won't work with Count.
Suggest me a way to implement this.
If you HAVE to use Dynamic Linq, your query should look like that:
db.Customers.Where("Orders.Count(ShipVia == 2) > 3");
How about something like this.
IQueryable<Customer> test = db.Customers.Where(c => c.Orders.Where(o => o.ShipVia ==2).Count() >2);
var grp = db.Customers.Where("ShipVia=2").GroupBy("ShipVia");
var test = from a in grp
where a.Count() > 3
select a.Key;
IQueryable<Customer> test =
from c in db.Customers
from o in c.Orders
where o.ShipVia == 2 // NOTE you need == not = for compare
group c by c into grp
select new {customer = grp.key, ordercount = grp.Count() };
Untested but I believe this should do it all in one statement, assuming Orders are a collection within Customer.
Note that your single = in your where clause is very dangerous as it'll assign 2 to all shipvias instead of test (==)

Data duplication in DataGrid. Problem with LINQ query

I have such a query but it gives me wrong output.
I have two data collections abcdata && xyzdata. each collection consists of an anonymous objects that have Group, Name properties. What I need to do is to get resulting collection with merged groups from abcdata and xyzdata respectively.
if(this.AbcDataGrid.ItemsSource != null && this.XyzDataGrid.ItemsSource != null)
{
var abcdata = ((IEnumerable<dynamic>)this.AbcDataGrid.ItemsSource).ToList().OrderByDescending(x => x.Id);
var xyzdata = ((IEnumerable<dynamic>)this.XyzDataGrid.ItemsSource).ToList().OrderByDescending(x => x.Id);
var result = from i1 in abcdata
from i2 in xyzdata
select new
{
Name = i1.Name,
Group = i1.Group.ToString() + i2.Group.ToString()
};
this.ResultGrid.ItemsSource = result.ToList();
}
While I was expecting to get DataGrid populated with list of new {Name, Group} objects I have very strange result:
I believe that what you are trying to do is join both data collections.
The linq query that you are doing is returning the correct results as what you are asking with it is: for every element of abcdata, and for every element of xyzdata, return the object you are constructing. So, if abcdata has 3 elements and xyzdata has 5 elements, the result will have 15 elements.
If you want: for each element of abcdata, select the elements of xyzdata that have the same name and concatenate the Groups, what you need is a Join.
Something like
var result = from i1 in abcdata
join i2 in xyzdata on i1.Name equals i2.Name
select new
{
Name = i1.Name,
Group = i1.Group.ToString() + i2.Group.ToString()
};

Count occurrences of values across multiple columns

I am having a terrible time finding a solution to what I am sure is a simple problem.
I started an app with data in Lists of objects. It's pertinent objects used to look like this (very simplified):
class A {
int[] Nums;
}
and
List<A> myListOfA;
I wanted to count occurrences of values in the member array over all the List.
I found this solution somehow:
var results
from a in myListOfA
from n in a.Nums
group n by n into g
orderby g.Key
select new{ number = g.Key, Occurences = g.Count}
int NumberOfValues = results.Count();
That worked well and I was able to generate the histogram I wanted from the query.
Now I have converted to using an SQL database. The table I am using now looks like this:
MyTable {
int Value1;
int Value2;
int Value3;
int Value4;
int Value5;
int Value6;
}
I have a DataContext that maps to the DB.
I cannot figure out how to translate the previous LINQ statement to work with this. I have tried this:
MyDataContext myContext;
var results =
from d in myContext.MyTable
from n in new{ d.Value1, d.Value2, d.Value3, d.Value4, d.Value5, d.Value6 }
group n by n into g
orderby g.Key
select new { number = g.Key, Occurences = g.Count() };
I have tried some variations on the constructed array like adding .AsQueryable() at the end - something I saw somewhere else. I have tried using group to create the array of values but nothing works. I am a relative newbie when it come to database languages. I just cannot find any clue anywhere on the web. Maybe I am not asking the right question. Any help is appreciated.
I received help on a microsoft site. The problem is mixing LINQ to SQL with LINQ to Objects.
This is how the query should be stated:
var results =
from d in MyContext.MyTable.AsEnumerable()
from n in new[]{d.Value1, d.Value2, d.Value3, d.Value4, d.Value5, d.Value6}
group n by n into g
orderby g.Key
select new {number = g.Key, Occureneces = g.Count()};
Works like a charm.
If you wish to use LINQ to SQL, you could try this "hack" that I recently discovered. It isn't the prettiest most cleanest code, but at least you won't have to revert to using LINQ to Objects.
var query =
from d in MyContext.MyTable
let v1 = MyContext.MyTable.Where(dd => dd.ID == d.ID).Select(dd => dd.Value1)
let v2 = MyContext.MyTable.Where(dd => dd.ID == d.ID).Select(dd => dd.Value2)
// ...
let v6 = MyContext.MyTable.Where(dd => dd.ID == d.ID).Select(dd => dd.Value6)
from n in v1.Concat(v2).Concat(v3).Concat(v4).Concat(v5).Concat(v6)
group 1 by n into g
orderby g.Key
select new
{
number = g.Key,
Occureneces = g.Count(),
};
How about creating your int array on the fly?
var results =
from d in myContext.MyTable
from n in new int[] { d.Value1, d.Value2, d.Value3, d.Value4, d.Value5, d.Value6 }
group n by n into g
orderby g.Key
select new { number = g.Key, Occurences = g.Count() };
In a relational database, such as SQL Server, collections are represented as tables. So you should actually have two tables - Samples and Values. The Keys table would represent a single "A" object, while the Values table would represent each element in A.Nums, with a foreign key pointing to the one of the records in the Samples table. LINQ to SQL
's O/R mapper will then create a "Values" property for each Sample object, which contains a queryable collection of the attached Values. You would then use the following query:
var results =
from sample in myContext.Samples
from value in sample.Values
group value by value into values
orderby values.Key
select new { Value = values.Key, Frequency = values.Count() };

Categories