C# Linq "GroupBy" - c#

I have a problem with a code that i wrote today.
I want to select some rows from the database and group them by one of the columns.
This is my code:
public class Classes
{
public IQueryable<ClassData> HomeWorkList { get; set; }
}
public class ClassData
{
public string Name { get; set; }
public string English { get; set; }
}
var classesCustomViews = new Classes {
HomeWorkList = _repositoryClasses.GetRecords()
.GroupBy(s => new { s.Name, s.English })
.Select(s => new ClassData
{
English = s.Key.English,
Name = s.Key.Name
})
};
return PartialView(classesCustomViews);
This is the error that I get:
System.Data.SqlClient.SqlException: Invalid column name 'English'.
Invalid column name 'English'. Invalid column name 'English'.

Since you're not pulling back any aggregate info other than your keys, you should be able to do it without the grouping by just using Distinct assuming that you are working with IQueryable up the stack to the database:
var classesCustomViews = new Classes {
HomeWorkList = _repositoryClasses.GetRecords()
.Select(s => new ClassData
{
English = s.English,
Name = s.Name
})
.Distinct()
};
If you still get an error, I would check your mapping and table to make sure that the table column names didn't change.

Related

EFCore SqlException for SelectListItem projection using same property for Value and Text

I created a SelectListHelper to keep all of my dropdown data population in a central location. It projects rows in my database into collections of IEnumerable<SelectListItem> that can be consumed by my views. Here is a portion of the class:
public class SelectListHelper
{
private AppDbContext _db;
public SelectListHelper(AppDbContext db)
{
_db = db;
}
public IEnumerable<SelectListItem> GetCountries(bool showAll = false)
{
return _db.Set<Country>().Where(x => showAll || x.Enabled == true).OrderBy(x => x.Name).Select(x => new SelectListItem { Value = x.Abbreviation, Text = x.Name }).ToList();
}
public IEnumerable<SelectListItem> GetGoogleCategories(bool showAll = false)
{
return _db.Set<GoogleCategory>().Where(x => showAll || x.Enabled == true).OrderBy(x => x.Name).Select(x => new SelectListItem { Value = x.Name, Text = x.Name }).ToList();
}
}
GetCountries, and all other functions omitted for brevity, work just fine. They use distinct columns projected onto the Value and Text properties of the SelectListItem.
GetGoogleCategories projects the Name column onto both the Value and Text properties of the SelectListItem. This produces the following SqlException:
An exception of type 'System.Data.SqlClient.SqlException' occurred in
Microsoft.EntityFrameworkCore.dll but was not handled in user code
Invalid column name 'Value'.
When I looked at the SQL being generated by the GetCountries function, it looked like I expected:
SELECT [x].[Abbreviation] AS [Value], [x].[Name] AS [Text]
FROM [Countries] AS [x]
WHERE [x].[Enabled] = 1
ORDER BY [Text]
However, the SQL generated by the GetGoogleCategories function did not look like I expected:
SELECT [x].[Name] AS [Text]
FROM [GoogleCategories] AS [x]
WHERE [x].Enabled] = 1
ORDER BY [Value]
I'm using EfCore 2.1.0 in Visual Studio 2017 (15.7.3). Any ideas what might be going on here? I can work around this by returning an array of Names and manually building a list of SelectListItems, but this has me worried about what other things in my Data Access Layer might be working improperly.
public class GoogleCategory
{
[Key]
public int Id { get; set; }
[Required, StringLength(250)]
public string Name { get; set; }
public bool Enabled { get; set; } = true;
}
public class Country
{
[Key]
public int Id { get; set; }
[Required, StringLength(250)]
public string Name { get; set; }
[Required, StringLength(2)]
public string Abbreviation { get; set; }
public bool Enabled { get; set; } = true;
}
It's a 2.1 regression bug tracked by #12180: Invalid column name: orderby uses a column alias that does not exist.
The workaround for your particular case is to use the SelectListItem constructor with parameters (a feature introduced in 2.1):
return _db.Set<GoogleCategory>()
.Where(x => showAll || x.Enabled == true)
.OrderBy(x => x.Name)
.Select(x => new SelectListItem(x.Name, x.Name) })
.ToList();
but of course the concerns remain.

Linq select many into new column

I am trying to make a jquery auto complete where I use a label and value like in this post which means that I need my json in the form of
{ label: 'label text', value: 'value text' }
However I am filtering a list of Employees which is a class with the following structure:
public sealed class Employee
{
public string Name { get; set; }
public string PersonnelNumber { get; set; }
public int RecID { get; set; }
public string Email { get; set; }
}
So I tried the following Linq to get the format of label, value I needed:
var jsonResult = employees
.SelectMany(emp => new { label = emp.Name, value = emp.RecID })
.ToList();
Where employees is a list of Employee objects but it is throwing up an build error of
Error 1 The type arguments for method 'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable, System.Func>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
How do I fix this to get the Name and RecID in a new list of objects with label and value as their output?
I think you just want to use Select here:
var jsonResult = employees
.Select(emp => new { label = emp.Name, value = emp.RecID })
.ToList();
SelectMany is for "flattening" a group of collections. Since you just have a single collection just use Select:
var jsonResult = employees.Select(emp => new { label = emp.Name, value = emp.RecID })
.ToList();

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

Getting List of Model1 with List of Model2 in it LINQ - MVC 4 EntityFramework 5

I have a requirement where I need to get a List of Model1 (List) using Linq, the Model1 have List of Model2 (List) in it and I need to fetch that also. For this I have created a Linq but m getting following error:
LINQ to Entities does not recognize the method
'System.Collections.Generic.List1 [OurCourse]
ToList[OurCourse](System.Collections.Generic.IEnumerable1
[OurCourse])' method, and this method cannot be translated into a
store expression.
Please refer below for detail:
I have two tables Colleges and Courses, with following columns:
College: ID, Name, Contact, City, Address
Cource: ID, CollegeID, Name, Years
My project have two view models for them, as follows:
public class OurCollege
{
public string Name { get; set; }
public string Contact { get; set; }
public List<OurCourse> MyCourses { get; set; }
}
public class OurCourse
{
public string Name { get; set; }
public int NumberOfYears { get; set; }
}
Here the the query query which I have prepared but I am getting the error:
var colleges = db.Colleges
.Select(cld => new OurCollege()
{
Name = cld.Name,
Contact = cld.Contact,
MyCourses = cld.Course
.Select(crs => new OurCourse()
{
Name = crs.Name,
NumberOfYears = crs.Years
}).ToList()
}).ToList();
How about doing like this:
MyCourses = from crs in cld.Course
select new OurCourse
{
Name = crs.Name,
NumberOfYears = crs.Years
}
Your complete query will look now:
var colleges = db.Colleges
.Select(cld => new OurCollege()
{
Name = cld.Name,
Contact = cld.Contact,
MyCourses = (from crs in cld.Course
select new OurCourse
{
Name = crs.Name,
NumberOfYears = crs.Years
}).ToList()
}).ToList();
Actually LINQ to Entities converts your query into SQL.It doesn't know how to translate ToList() in SQL
An alternate is to change you List<T> to IEnumerable<T> and remove ToList() frim your original code:
public IEnumerable<OurCourse> MyCourses { get; set; }
and in query:
var colleges = db.Colleges
.Select(cld => new OurCollege()
{
Name = cld.Name,
Contact = cld.Contact,
MyCourses = cld.Course
.Select(crs => new OurCourse()
{
Name = crs.Name,
NumberOfYears = crs.Years
})
}).ToList();
For more details visit this Entity Framework ToList() in nested type (LINQ to Entities does not recognize the method)
A foreach loop will work, you can write the query something like this
var colleges = db.Colleges
.Select(x => new OurCollege()
{
CollegeId = x.CollegeId,
Name = x.Name,
Contact = x.Contact
}).ToList();
foreach (var college in colleges)
{
college.MyCourse = db.Course.Where(x => x.CollegeId == college.CollegeId)
.Select(x => new OurCourse()
{
Name = x.Name,
NumberOfYears = x.Years
}).ToList()
}

Simplest way to flatten document to a view in RavenDB

Given the following classes:
public class Lookup
{
public string Code { get; set; }
public string Name { get; set; }
}
public class DocA
{
public string Id { get; set; }
public string Name { get; set; }
public Lookup Currency { get; set; }
}
public class ViewA // Simply a flattened version of the doc
{
public string Id { get; set; }
public string Name { get; set; }
public string CurrencyName { get; set; } // View just gets the name of the currency
}
I can create an index that allows client to query the view as follows:
public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
public A_View()
{
Map = docs => from doc in docs
select new ViewA
{
Id = doc.Id,
Name = doc.Name,
CurrencyName = doc.Currency.Name
};
Reduce = results => from result in results
group on new ViewA
{
Id = result.Id,
Name = result.Name,
CurrencyName = result.CurrencyName
} into g
select new ViewA
{
Id = g.Key.Id,
Name = g.Key.Name,
CurrencyName = g.Key.CurrencyName
};
}
}
This certainly works and produces the desired result of a view with the data transformed to the structure required at the client application. However, it is unworkably verbose, will be a maintenance nightmare and is probably fairly inefficient with all the redundant object construction.
Is there a simpler way of creating an index with the required structure (ViewA) given a collection of documents (DocA)?
FURTHER INFORMATION
The issue appears to be that in order to have the index hold the data in the transformed structure (ViewA), we have to do a Reduce. It appears that a Reduce must have both a GROUP ON and a SELECT in order to work as expected so the following are not valid:
INVALID REDUCE CLAUSE 1:
Reduce = results => from result in results
group on new ViewA
{
Id = result.Id,
Name = result.Name,
CurrencyName = result.CurrencyName
} into g
select g.Key;
This produces: System.InvalidOperationException: Variable initializer select must have a lambda expression with an object create expression
Clearly we need to have the 'select new'.
INVALID REDUCE CLAUSE 2:
Reduce = results => from result in results
select new ViewA
{
Id = result.Id,
Name = result.Name,
CurrencyName = result.CurrencyName
};
This prduces: System.InvalidCastException: Unable to cast object of type 'ICSharpCode.NRefactory.Ast.IdentifierExpression' to type 'ICSharpCode.NRefactory.Ast.InvocationExpression'.
Clearly, we also need to have the 'group on new'.
Thanks for any assistance you can provide.
(Note: removing the type (ViewA) from the constructor calls has no effect on the above)
UPDATE WITH CORRECT APPROACH
As outlined in Daniel's blog mentioned in the answer below, here is the correct way to do this for this example:
public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
public A_View()
{
Map = docs => from doc in docs
select new ViewA
{
Id = doc.Id,
Name = doc.Name,
CurrencyName = doc.Currency.Name
};
// Top-level properties on ViewA that match those on DocA
// do not need to be stored in the index.
Store(x => x.CurrencyName, FieldStorage.Yes);
}
}
One solution, simply flatten in the Map and configure the index to store only properties that do not exist in DocA.
public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
public A_View()
{
Map = docs => from doc in docs
select new ViewA
{
Id = doc.Id,
Name = doc.Name,
CurrencyName = doc.Currency.Name
};
// Top-level properties on ViewA that match those on DocA
// do not need to be stored in the index.
Store(x => x.CurrencyName, FieldStorage.Yes);
}
}

Categories