Create New Distinct List(of T) from Existing List(of T) Using LINQ - vb.net

How can I get a new distinct list from an existing list using LINQ? This is what I have so far and it is not distinct but does give me a new list.
Dim tmpQryColumn = (From a In _allAudits
Select New CheckBoxListItem
With {.Id = a.AuditColumn, .Name = a.AuditColumn}
).Distinct()
_columnList = New List(Of CheckBoxListItem)(tmpQryColumn)

I suspect the problem is that CheckboxListItem doesn't override Equals/GetHashCode so any two instances are effectively distinct. Try:
Dim columns = (From a In _allAudits
Select a.AuditColumn).Distinct()
_columnList = (From column in columns
Select New CheckBoxListItem
With {.Id = column, .Name = column}
).ToList()
I'm sure there's a simpler way of writing it in VB, but I'm not familiar enough with the syntax to write it. The C# version would be:
_columnList = _allAudits.Select(x => x.AuditColumn)
.Distinct()
.Select(x => new CheckboxListItem { Id = x, Name = x })
.ToList();

The Distinct extension method relies on the types in question properly implementing value equality. The most likely reason that it's failing here is that CheckBoxListItem doesn't implement equality based on the Id and Name properties.
To fix this do one of the following
Implement equality semantics for CheckBoxListItem
Create an IEqualityComparer<CheckBoxListItem> and use the overload of Distinct which takes that value

Related

LINQ Does not contain a definition for 'union'

what is wrong with this linq query that show me the error of Does not contain a definition for 'union'
(from rev in db.vM29s
where Years.Contains(rev.FinancialYear) && rev.Exclude=="No"
group rev by new { rev.RevenueCode, rev.FinancialYear } into g
select new
{
Revenuecode = g.Key.RevenueCode,
totalRevenue = g.Sum(x => x.RevenueAmount),
RevenueEnglish = (from a in db.RevenueCodes where a._RevenueCode == g.Key.RevenueCode select a.RevenueEng).FirstOrDefault(),
// MinorCode = (from a in db.MinorCodes where a._MinorCode == g.Key.MinorCode select a.MinorEng),
RevenueDari = (from a in db.RevenueCodes where a._RevenueCode == g.Key.RevenueCode select a.RevenueDari).FirstOrDefault(),
Yearss = g.Key.FinancialYear
}
)
.Union
(from u in db.rtastable
where Years.Contains(u.Year)
group u by new { u.objectcode, u.Year } into g
select new
{
Revenuecode = g.Key.objectcode,
totalRevenue = g.Sum(x => x.amount),
RevenueEnglish = (from a in db.RevenueCodes where a._RevenueCode == g.Key.objectcode select a.RevenueEng).FirstOrDefault(),
// MinorCode = (from a in db.MinorCodes where a._MinorCode == g.Key.MinorCode select a.MinorEng),
RevenueDari = (from a in db.RevenueCodes where a._RevenueCode == g.Key.objectcode select a.RevenueDari).FirstOrDefault(),
Yearss = g.Key.Year
}
)
.ToList();
If you included using System.Linq; and both Anonymous Types have exactly the same property names + property types, then what you did should work.
Yet it does not work. The solution is to check your Anonymous Types for subtle property name differences and/or subtle property type differences.
E.g. even an int vs a smallint or double or decimal will cause this build error:
'System.Collections.Generic.IEnumerable<AnonymousType#1>' does not contain a definition for 'Union' and the best extension method overload 'System.Linq.Queryable.Union(System.Linq.IQueryable, System.Collections.Generic.IEnumerable)' has some invalid arguments
Switching to .Concat() will not fix this: it has the same (obvious) restriction that the types on both sides must be compatible.
After you fix the naming or typing problem, I would recommend that you consider switching to .Concat(). The reason: .Union() will call .Equals() on all objects to eliminate duplicates, but that is pointless because no two Anonymous Objects that were created independently will ever be the same object (even if their contents would be the same).
If it was your specific intention to eliminate duplicates, then you need to create a class that holds your data and that implements .Equals() in a way that makes sense.
You should use Concat or using addRange if the data is allready in memory.

Return type of Linq on Datatable

I have a datatable with two columns ID & Role.
Same ID can have multiple roles.
I need to convert this table to a comma separated grouped table.
I am trying to use following query but unable to solve the issue.
LINQ:
From row As DataRow In dtData.Rows.Cast(Of DataRow)
Group row By id = row.Field(Of Integer)("ID") Into Group
Select ID, Role = String.Join(",", From i In Group Select i.Field(Of String)("Role"))
Issue
Any help will be appreciated.
Update 1:
Table structure
Needed table Structure
You could create a linq like in your comments just that this returns a list of arrays of string:
Here is the code:
(From row As DataRow In myDatatable
Group row By id = row.Field(Of String)("ID") Into Group
Select {id, String.Join(",", From i In Group Select i.Field(Of String)("Role"))}).ToList
If you need the result in a datatable you can build a new datatable
Make a for each of result and use the activity Add data row. In ArrayRow add the item and in DataTable the new data table
If you use the activity Output data table you can see the results
I am kind of confused by what you are wanting as the ultimate outcome. An idea that may guide you but not be exactly what you want is you can change a DataTable to an anonymous projection and then get what you want out of that. You can do a 'Select' off a DataTable which enters into an extension method of 'what' do you want to select. If I was to do a new {} without any class or container object after the 'new' I would be scoped to just a method or not. This is a good advantage when you want to mold something for a specific use in just a single method to use tailored to a specific view.
static void Main(string[] args)
{
DataTable d = new DataTable();
d.Columns.Add("ItemName", typeof(string));
d.Columns.Add("MinValue", typeof(float));
d.Columns.Add("MaxValue", typeof(float));
d.Rows.Add("Widget1", 0.1, 0.2);
d.Rows.Add("Widget2", 0.2, 0.4);
d.Rows.Add("Widget3", 0.1, 0.2);
var dataTable = d.AsEnumerable();
//What do you want to select? The new {} without an indicator means anonymous type projection. This will exist only in
// the scope listed.
var data = dataTable.Select(x => new { ItemName = x[0], MinValue = x[1], MaxValue = x[2] }).ToList();
//My 'data' type is now well typed for it's properties in the scope it's in.
var joined = String.Join(", ", data.Select(x => x.ItemName).ToList());
Console.WriteLine(joined);
Console.WriteLine($"{data.Count}");
Console.ReadLine();
}
EDIT 1-26-18
Strange I thought I updated the code yesterday. To get a reusable object you could bind your front end to, you just make a POCO like so:
public class Foo
{
public string Bar { get; set; }
public string MinAndMax { get; set; }
}
While you could make another DataTable, frankly DataTables are like WinForms. They get the job done, but they are archaic and not friendly to do Linq with as easily as just a well formed POCO. And if you get into using Linq it will play better with well formed objects and they are easy to create.
var data = dataTable.Select(x => new Foo { Bar = x[0].ToString(), MinAndMax = $"{x[1]} {x[2]}" }).ToList();
//My 'data' type is now well typed for 'Foo' class and it's properties in the scope it's in.
var joined = String.Join(", ", data.Select(x => $"{x.Bar} {x.MinAndMax}").ToList());

Calling a function within Entity Framework Select

A property from my object (oJobs) is as follows:
Private _brandlist As List(Of DAL.Brand)
Public Property PostBrandList() As List(Of DAL.Brand)
Get
Return _brandlist
End Get
Set(ByVal value As List(Of DAL.Brand))
_brandlist = value
End Set
End Property
In the database, the brand list is stored as a string separated by comma e.g. the column 'brands' can be a string '3,45,2' where each number represents an id of a brand stored in another table.
my select query is as below:
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
Select New oJobs With { 'hiding all others for code brevity
.PostBrandList = 'problem is here'
}
Since j.BrandList will return a string, I will need to split that string and for each number, run another query to finally return and assign a List(Of DAL.Brand) into .PostBrandList
For those who might ask "what have you tried?",
I have run the query, then did a for each to add the list of brands later - succeeded but not optimal
Coded a function that takes the list as a parameter and returns a separate list of objects - very silly.
Also, I am not allowed to normalize the DB :(
Not tested and might need some tweaking but heres one idea. you will also need to change your property to an IEnumerable rather than List. Because the second linq query is embedded within the first, I believe it should execute it all as one query, but you should check it to make sure.
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
Select New oJobs With { 'hiding all others for code brevity
.PostBrandList = From b In db.Brands Where j.Brands = b.ID Or j.Brands.StartsWith(b.ID & ",") Or j.Brands.EndsWith("," & b.ID) Or j.Brands.Contains("," & b.ID & ",") Select b
}
In c# you can use
.Select(x=>new {x.BrandList})
.ToList() //Materialize first before calling function
.Select(x=> new oJobs{
PostBrandList =
db.Brands.Where(z=>
x.BrandList
.Split(',')
.Select(y=>int.Parse(y.Trim()))
.Contains(z.Id))
.ToList()
});
Note that you must materialize entity first before calling String.Split
I don't know how to translate that to VB.NET.
Of course it will cause SELECT n+1 problem because you can't use join.
If you can't normalize table, my other suggestion is to create indexed view (sql server), so you can use join and improve performance.
Indexed view https://msdn.microsoft.com/en-us/library/ms191432.aspx
You could try it with the Let statement:
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
/* li in the LINQ Statement represents one string value from the BrandList list */
Let postBrandElemsList = j.BrandList.Split(',').Select(Function(li) New DAL.Brand With { ... /* Field initializatione of the Class DAL.Brand here */ }
Select New oJobs With
{
.PostBrandList = postBrandElemsList
}
I'm sorry for the probably bad VB.NET syntax, you should check this when implementing it in your code.
Maybe you would just want to use the Split function on the column brands into an array and iterate through the result, using the Find function to retrieve the brand objects?

LINQ expression to expression tree via API in VB

I have a relatively simple LINQ expression which I need to convert into VB expression tree syntax. This is likely an easy task for folks that are familiar, but I am new to the realm of LINQ expression trees.
In my samples, you see a "New Int16() {}" array. That value must be parameterized at run-time with an array of values from another code element.
my query is:
from i in tblInstitutions
let ChildHasCategory = i.tblInstCtgyHistories.Where(Function(CtgyHist) CtgyHist.EndDate is Nothing AND ( (New Int16() {32,35,38,34}).Contains(CtgyHist.InstCtgyCodeFK)))
where ChildHasCategory.Any()
select i
Which can also be represented as:
tblInstitutions
.Select (i => new {
i = i,
ChildHasCategory = (IEnumerable<tblInstCtgyHistory>)(i.tblInstCtgyHistories)
.Where (
CtgyHist =>
((CtgyHist.EndDate == null) &
(IEnumerable<Int16>)(new Int16[] { 32, 35, 38, 34 } ).Contains (CtgyHist.InstCtgyCodeFK)
)
)
}
)
.Where ($VB$It => $VB$It.ChildHasCategory.Any ())
.Select ($VB$It => $VB$It.i)
This is going to be used in the context of a custom filter in an ASP.NET Dynamic Data web application. I'd like to mimic the default approach.
A sample of one of the other dynamic filter code-behind is:
Public Overrides Function GetQueryable(source As IQueryable) As IQueryable
Dim value = TextBox1.Text
If String.IsNullOrWhiteSpace(value) Then
Return source
End If
If DefaultValues IsNot Nothing Then
DefaultValues(Column.Name) = value
End If
Dim parameter = Expression.Parameter(source.ElementType)
Dim columnProperty = Expression.PropertyOrField(parameter, Column.Name)
Dim likeValue = Expression.Constant(value, GetType(String))
Dim condition = Expression.Call(columnProperty, GetType(String).GetMethod("Contains"), likeValue)
Dim where = Expression.Call(GetType(Queryable), "Where", New Type() {source.ElementType}, source.Expression, Expression.Lambda(condition, parameter))
Return source.Provider.CreateQuery(where)
End Function
I'm not sure you really need to worry about expression trees here. First off, we should be able to express your query as follows:
Dim targetCodes = new Int16() {32, 35, 38, 34 } ' This could be data driven as well
return from i in tblInstitutions
where i.tblInstCtgyHistories.Any(Function(ctgyHist) ctgyHist.EndDate is Nothing AndAlso
targetCodes.Contains(ctgyHist.InstCtgyCodeFK))
select i
Given that, under what case do you need the custom expression tree?

How do I return the column names of a LINQ entity

I am using LINQ to SQL queries to return data in my application. However I find it is now needful for me to return the column Names. Try as I might I have been completely unable to find out how to do this on the internet.
So if my LINQ entity table has the properties (Last_Name, First_name, Middle_Name) I need to return:
Last_name
First_Name
Middle_name
rather than the usual
Smith
John
Joe
You could certainly do it with some LINQ-To-Xml directly against the ".edmx" file or the embedded model resources in the compiled assembly.
The below query gets the field (not column) names. If you need the columns then just change the query to suit.
var edmxNS = XNamespace.Get(#"http://schemas.microsoft.com/ado/2007/06/edmx");
var schemaNS = XNamespace.Get(#"http://schemas.microsoft.com/ado/2006/04/edm");
var xd = XDocument.Load(#"{path}\Model.edmx");
var fields =
from e in xd
.Elements(edmxNS + "Edmx")
.Elements(edmxNS + "Runtime")
.Elements(edmxNS + "ConceptualModels")
.Elements(schemaNS + "Schema")
.Elements(schemaNS + "EntityType")
from p in e
.Elements(schemaNS + "Property")
select new
{
Entity = e.Attribute("Name").Value,
Member = p.Attribute("Name").Value,
Type = p.Attribute("Type").Value,
Nullable = bool.Parse(p.Attribute("Nullable").Value),
};
Lets assume you're talking about the Contact Table in the assembly named YourAssembly in a Context called MyDataContext
Using Reflection against a Table
You can use reflection to get the properties like you would any type
var properties = from property in
Type.GetType("YourAssembly.Contact").GetProperties()
select property.Name
;
foreach (var property in properties)
Console.WriteLine(property);
As shaunmartin notes this will return all properties not just Column Mapped ones. It should also be noted that this will return Public properties only. You'd need to include a BindingFlags value for the bindingAttr Parameter of GetProperties to get non-public properties
Using the Meta Model
You can use the Meta Model System.Data.Linq.Mapping to get the fields ( I added IsPersistant to only get the Column Mapped properties)
AttributeMappingSource mappping = new System.Data.Linq.Mapping.AttributeMappingSource();
var model = mappping.GetModel(typeof (MyDataContext));
var table = model.GetTable(typeof (Contact));
var qFields= from fields in table.RowType.DataMembers
where fields.IsPersistent == true
select fields;
foreach (var field in qFields)
Console.WriteLine(field.Name);
Using Reflection from a query result
If on the other hand you wanted it from a query result you can still use reflection.
MyDataContextdc = new MyDataContext();
Table<Contact> contacts = dc.GetTable<Contact>();
var q = from c in contacts
select new
{
c.FirstName,
c.LastName
};
var columns = q.First();
var properties = (from property in columns.GetType().GetProperties()
select property.Name).ToList();
I stumbled upon this answer to solve my own problem and used Conrad Frix 's answer. The question specified VB.NET though and that is what I program in. Here are Conrad's answers in VB.NET (they may not be a perfect translation, but they work):
Example 1
Dim PropertyNames1 = From Prprt In Type.GetType("LocalDB.tlbMeter").GetProperties()
Select Prprt.Name
Example 2
Dim LocalDB2 As New LocalDBDataContext
Dim bsmappping As New System.Data.Linq.Mapping.AttributeMappingSource()
Dim bsmodel = bsmappping.GetModel(LocalDB2.GetType())
Dim bstable = bsmodel.GetTable(LocalDB.tblMeters.GetType())
Dim PropertyNames2 As IQueryable(Of String) = From fields In bstable.RowType.DataMembers
Where fields.IsPersistent = True
Select fields.Member.Name 'IsPersistant to only get the Column Mapped properties
Example 3
Dim LocalDB3 As New LocalDBDataContext
Dim qMeters = From mtr In LocalDB3.tblMeters
Select mtr
Dim FirstResult As tblMeter = qMeters.First()
Dim PropertyNames3 As List(Of String) = From FN In FirstResult.GetType().GetProperties()
Select FN.Name.ToList()
To display the results:
For Each FieldName In PropertyNames1
Console.WriteLine(FieldName)
Next
For Each FieldName In PropertyNames2
Console.WriteLine(FieldName)
Next
For Each FieldName In PropertyNames3
Console.WriteLine(FieldName)
Next
Please also read Conrad's answer for notes on each method!