Calling a function within Entity Framework Select - vb.net

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?

Related

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());

Statement in linq lambda expression

I have a list of it, that is my SelectedValue from some ComboBox.
Dim AppsInt = MyApps.CheckedItems.Select(Function(x) Convert.ToInt32(x.Value)).ToList()
And i have this object that is a list( of t)
Dim myObj = New List( Of Item )
Dim FooItem = New item ( 42 )
What I want is to get my list of Int into my object. With Something that would look like this in C#:
AppsInt.foreach( x => myObj .add(new Item(x) ) ) ;
What i have done so far is sending me a "do not produce a result" error:
AppsInt.ForEach( Function(it) myObj.Add(New Item(it)) )
How can i do it ? How to make this linq lambda work?
you should change function(it) to sub(it) .
Or:
Dim myObj = AppsInt.Select( Function(it) New Item(it)).ToArray()
Lambda expression inside you ForEach expression does not returns any result (and compiler said it to you). It means that you have two ways to solve it:
Add return statement into your lamda expression that will return anything:
AppsInt.ForEach(Function(it)
myObj.Add(New Item(it))
Return 1 ' it's not matter what you will return here.
End Function)
Change Function(it) to Sub(it). Sub is not obliged to return any value.
Second option is more preferable.

linq select C# to vb.net

I need the following LINQ statement corverted to VB.net. but
collection.Select(c => {c.PropertyToSet = value; return c;});
Here's what I have, but I want to do the 'return' instruction.
collection.Select(function (c) c.PropertyToSet = value ***return c*** )
Any ideas on how to get that converted?
Thanks a lot!
If you want to change all properties of an object in the collection, Select method is not appropriated method to do this.
Since the collection object is a List(Of T), you could try this with ForEach method:
collection.ForEach(Sub(c) c.PropertyToSet = value)
With Select method, you could try this:
collection = collection.Select(Sub(c) c.PropertyToSet = value)
Alternatively, you could use the language structure For Each for it:
For Each c as YourType In collection
c.PropertyToSet = value
Next
I think this is the way, did not test it but it should work
Collection.Select(Function(c)
c.PropertyToSet = value
Return c
End Function)
If you are looking to get a filtered IList back, try this:
collection.Where(Function(c) c.PropertyToSet = Value).ToList

Unable to cast the type 'System.String' to type 'System.Collections.Generic.IEnumerable

My query below works fine when there is only category to return but as soon as as there is more than one I get Sequence contains more than one element error message. I would like to return all the relevant categories. So I changed the PostCategory in the DTO from string to a List and that is when I get the casting error. I also tried changing it to a IList(of String) and IList(of be_Category) and adding ToList to ca.CategoryName. That didn't work.
My query with the joins:
Public Function SelectByID(id As Integer) As PostDTO Implements IPostRepository.SelectByID
Dim post As New PostDTO
Using db As Ctx = New Ctx
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = ca.CategoryName,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).Single
End Using
Return post
End Function
So is it possible to project the sequence of Category Names into a new DTO or something else or is there another way to return all the categories? I guess since CategoryName is a string, L2E cannot project the strings into the list. Do I need a GroupBy to project category strings into a new form? I also tried AsEnumerable and I tried String.Join - neither worked.
The DTO is below - If PostCategory is a string then I can get a single category back to the view. I hope I have explained it clearly.
Public Class PostDTO
Public PostId As Integer
Public PostGuid As Guid
Public PostTitle As String
Public PostSummary As String
Public PostText As String
Public PostDateCreated As DateTime
Public PostIsPublished As Boolean
Public PostCategory As IList(Of be_PostCategory)
End Class
EDIT:
Updated SelectById:
Public Function SelectByID(id As Integer) As IEnumerable(Of PostDTO) Implements IPostRepository.SelectByID
Dim post As IEnumerable(Of PostDTO)
Using db As Ctx = New Ctx
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = ca.CategoryName,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).ToList
End Using
End Function
The Single method throws an exception when the number of elements returned by the query is not exactly 1. Try removing .Single() from the end of your query.
Also, we don't see what variable it's being assigned to. An anonymous type works well here, though if you don't use one, make sure it's correct i.e.
Dim result As IEnumerable(Of PostDTO) = From ca In db.be_Categories ...
Edit #1
I should add some clarification. When running a LINQ query, expect for the query to return any number of results, just like you would expect SQL or similar to do. In the case where you only expect one result however (i.e. Select Top 1 ...) then you can use .Single(). Getting back to your case, your query is against an Entity Framework data source I can only imagine (Ctx is that, correct?). As indicated by the MSDN documentation, you will return a DbQuery(Of PostDTO), which is the data type returned by a LINQ to Entities query against a DbContext. This type, depending on what you want to do with it, can be cast to several interfaces. See its definition
Public Class DbQuery(Of TResult) _
Implements IOrderedQueryable(Of TResult), IQueryable(Of TResult), _
IEnumerable(Of TResult), IOrderedQueryable, IQueryable, IEnumerable, _
IListSource, IDbAsyncEnumerable(Of TResult), IDbAsyncEnumerable
The IEnumberable(Of TResult), with TResult being a PostDTO in your case, is what you can cast to so you can enumerate the results, providing a lot of functionality like further queries, sorting, getting average, max, min, etc. Hope this clears it up.
Edit #2
Finally getting to the bottom of the problem. The first part gives you a single Post, but with nothing in the PostCategory. The second part puts the list of categories into that single post.
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = Nothing,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).FirstOrDefault()
post.PostCategory = (From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Where p.PostRowID = id
Select ca.CategoryName).ToList()

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!