Referring to an attached table's fields in Linq-to-SQL - c#

I'm creating a windows forms application to display all the data in my database.
So the database has a table called Items, a sub-table called Tags, and these two tables have a 1:M relationship. I can add a Linq-to-SQL class to my Project, then create a datagrid to access the main Items table without any problems.
Looking at the properties of the Items class that was created by Linq-to-SQL, I can see it has the Tags table attached to it (EntitySet), but I can't access any of the Tags fields! Is there some way to do this?
I want to create a new column in the datagrid, called TagsList, and for each Item, I want to display a concatenated list of all corresponding Tags.Name. I figured I could do something like adding a new property to the Items class as so:
public partial class Items
{
public string TagsList
{
return this.Tags.Name.Aggregate((x, y) => x + ", " + y));
}
}
But obviously, this isn't working, because there is no definition for it. Help?

I am assuming that Items.Tags is of type ICollection<Tags> (or IQueryable or IEnumerable, I can't remember which linq-to-sql uses) as you state that Items and Tags have a one-to-many relationship. If this is not the case let me know and I'll delete or edit my answer as appropriate.
Name is a property on each individual Tags and not the collection which is why Tags.Name is incorrect.
Try the following:
return this.Tags.Select(tag => tag.Name).Aggregate((x, y) => x + ", " + y));
Tags.Select(tag => tag.Name) will return IEnumerable<string> of names and then you aggregate that.

Related

Is there a way in linq wherin i can insert a row(from dictionary) in datatable using the list of column names c#?

I have a List<Dictionary<string,string>> something like this:
[0] key1 val,key2 val,key3 val
[1] key1 val,key2 val,key3 val
[2] key1 val,key2 val,key3 val
And i have a list of column names in the same order as columns in the datatable.
I want to filter only those keys which are there inside the list from the dictionary and also insert it in the proper order.
I'm able to filter the required keys to be inserted but then how do i insert it in the proper order in linq.
var colList = new List<string>() { "key3", "key1"};
dict.ForEach(p => jsonDataTable.Rows.Add(p.Where(q=>colList.Contains(q.key)).Select(r => r.Value).ToArray()));
I cannot do like this because number of columns will vary and also the method must work when we pass any list of column names:
foreach(var item in dict)
jsonDatatable.Rows.Add(item[colList[0]], item[colList[1]]);
Please suggest some ways.
LINQ will never ever change the input sources. You can only extract data from it.
Divide problems in subproblems
The only way to change the input sources is by using the extracted data to update your sources. Make sure that before you update the source you have materialized your query (= ToList() etc)
You can divide your problem into subproblems:
Convert the table into a sequence of columns in the correct order
convert the sequence of columns into a sequence of column names (still in the correct order)
use the column names and the dictionary to fetch the requested data.
By separating your problem into these steps, you prepare your solution for reusability. If in future you change your table to a DataGridView, or a table in an entity framework database, or a CSV file, or maybe even JSON, you can reuse the latter steps. If in future you need to use the column names for something else, you can still use the earlier steps.
To be able to use the code in a LINQ-like way, my advice would be to create extension method. If you are unfamiliar with extension methods, read Extension Methods Demystified
You will be more familiar with the layout of your table (System.Data.DataTable? Windows.Forms.DataGridView? DataGrid in Windows.Controls?) and your columns, so you'll have to create the first ones yourself. In the example I use MyTable and MyColumn; replace them with your own Table and Column classes.
public static IEnumerable<MyColumn> ToColumns(this MyTable)
{
// TODO: return the columns of the table
}
public static IEnumerable<string> ToColumnNames(this IEnumerable<MyColumn> columns)
{
return columns.Select(column => ...);
}
If the column name is just a property of the column, I wouldn't bother creating the second procedure. However, the nice thing is that it hides where you get the name from. So to be future-changes-proof, maybe create the method anyway.
You said these columns were sorted. If you want to be able to use ThenBy(...) consider returning an IOrderedEnumerable<MyColumn>. If you won't sort the sorted result, I wouldn't bother.
Usage:
MyTable table = ...
IEnumerable<string> columnNames = table.ToColumns().ToColumnNames();
or:
IEnumerable<string> columnNames = table.ToColumns()
.Select(column => column.Name);
The third subproblem is the interesting one.
Join and GroupJoin
In LINQ whenever you have two tables and you want to use a property of the elements in one table to match them with the properties of another table, consider to use (Group-)Join.
If you only want items of the first table that match exactly one item of the other table, use Join: "Get Customer with his Address", "Get Product with its Supplier". "Book with its Author"
On the other hand, if you expect that one item of the first table matches zero or more items from the other table, use GroupJoin: "Schools, each with their Students", "Customers, each with their Orders", "Authors, each with their Books"
Some people still think in database terms. They tend to use some kind of Left Outer Join to fetch "Schools with their Students". The disadvantage of this is that if a School has 2000 Students, then the same data of the School is transferred 2000 times, once for every Student. GroupJoin will transfer the data of the School only once, and the data of every Student only once.
Back to your question
In your problem: every column name is the key of exactly one item in the Dictionary.
What do you want to do with column names without keys? If you want to discard them, use Join. If you still want to use the column names that have nothing in the Dictionary, use GroupJoin.
IEnumerable<string> columNames = ...
var result = columnNames.Join(myDictionary,
columName => columName, // from every columName take the columnName,
dictionaryItem => dictionaryItem.Key, // from every dictionary keyValuePair take the key
// parameter resultSelector: from every columnName and its matching dictionary keyValuePair
// make one new object:
(columnName, keyValuePair) => new
{
// Select the properties that you want:
Name = columnName,
// take the whole dictionary value:
Value = keyValuePair.Value,
// or select only the properties that you plan to use:
Address = new
{
Street = keyValuePair.Street,
City = keyValuePair.City,
PostCode = keyValuePair.Value.PostCode
...
},
});
If you use this more often: consider to create an extension method for this.
Note: the order of the result of a Join is not specified, so you'll have to Sort after the Order
Usage:
Table myTable = ...
var result = myTable.ToColumns()
.Select(column => column.Name)
.Join(...)
.Sort(joinResult => joinResult.Name)
.ToList();
Instead of filtering on the List<Dictionary<string, string>>, filter on the colList so that you will get in the same order and only if the colList is available in the List<Dictionary<string, string>>
This is as per my understanding, please comment if you need the result in any other way.
var dictAllValues = dict.SelectMany(x => x.Select(y => y.Value)).ToList();
// Now you can filter the colList using the above values
var filteredList = colList.Where(x => dictAllValues.Contains(x));
// or you can directly add to final list as below
jsonDataTable.Rows.AddRange(colList.Where(x => dictAllValues.Contains(x)).ToList());

How to do this kind of search in ASP.net MVC?

I have an ASP.NET MVC web application.
The SQL table has one column ProdNum and it contains data such as 4892-34-456-2311.
The user needs a form to search the database that includes this field.
The problem is that the user wants to have 4 separate fields in the UI razor view whereas each field should match with the 4 parts of data above between -.
For example ProdNum1, ProdNum2, ProdNum3 and ProdNum4 field should match with 4892, 34, 456, 2311.
Since the entire search form contains many fields including these 4 fields, the search logic is based on a predicate which is inherited from the PredicateBuilder class.
Something like this:
...other field to be filtered
if (!string.IsNullOrEmpty(ProdNum1) {
predicate = predicate.And(
t => t.ProdNum.toString().Split('-')[0].Contains(ProdNum1).ToList();
...other fields to be filtered
But the above code has run-time error:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities`
Does anybody know how to resolve this issue?
Thanks a lot for all responses, finally, I found an easy way to resolve it.
instead of rebuilding models and change the database tables, I just add extra space in the search strings to match the search criteria. since the data format always is: 4892-34-456-2311, so I use Startwith(PODNum1) to search first field, and use Contains("-" + PODNum2 + "-") to search second and third strings (replace PODNum1 to PODNum3), and use EndWith("-" + PODNum4) to search 4th string. This way, I don't need to change anything else, it is simple.
Again, thanks a lot for all responses, much appreciated.
If i understand this correct,you have one column which u want to act like 4 different column ? This isn't worth it...For that,you need to Split each rows column data,create a class to handle the splitted data and finally use a `List .Thats a useless workaround.I rather suggest u to use 4 columns instead.
But if you still want to go with your existing applied method,you first need to Split as i mentioned earlier.For that,here's an example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
string part1 = datareader(1).toString.Split("-")(0);///the 1st part of your column data
string part2 = datareader(1).toString.Split("-")(1);///the 2nd part of your column data
}
}
Now,as mentioned in the comments,you can rather a class to handle all the data.For example,let's call it mydata
public class mydata {
public string part1;
public string part2;
public string part3;
public string part4;
}
Now,within the While loop of the SqlDatareader,declare a new instance of this class and pass the values to it.An example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
Mydata alldata = new Mydata;
alldata.Part1 = datareader(1).toString.Split("-")(0);
alldata.Part2 = datareader(1).toString.Split("-")(1);
}
}
Create a list of the class in class-level
public class MyForm
{
List<MyData> storedData = new List<MyData>;
}
Within the while loop of the SqlDatareader,add this at the end :
storedData.Add(allData);
So finally, u have a list of all the splitted data..So write your filtering logic easily :)
As already mentioned in a comment, the error means that accessing data via index (see [0]) is not supported when translating your expression to SQL. Split('-') is also not supported hence you have to resort to the supported functions Substring() and IndexOf(startIndex).
You could do something like the following to first transform the string into 4 number strings ...
.Select(t => new {
t.ProdNum,
FirstNumber = t.ProdNum.Substring(0, t.ProdNum.IndexOf("-")),
Remainder = t.ProdNum.Substring(t.ProdNum.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
SecondNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
Remainder = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
t.SecondNumber,
ThirdNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
FourthNumber = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
... and then you could simply write something like
if (!string.IsNullOrEmpty(ProdNum3) {
predicate = predicate.And(
t => t.ThirdNumber.Contains(ProdNum3)

C# NHibernate QueryOver select multiple Properties

In my C# application I use NHibernate to get all rooms from my database table 'room'.
using (ISession pSession = NHibernateHelper.OpenSession())
{
IList<Room> roomList = pSession.QueryOver<Room>().
Where(x => x.FloorID == 3).
.List();
}
The table 'room' and also my Mapping class (Room.cs) contains lets say the following properties:
roomID
hash
date
identifier
I have a DataGridView which should display my table entries from the database: the code is:
roomDataGridView.DataSource = roomList;
That works fine so far. But now I decide that I dont want to show all the properties from the Room class, I only want to display
roomID
identifier
I tried the following:
roomDataGridView.DataSource = listOfRoomPropertiesForCurrentFloor.Select(x => new {x.Identifier, x.RoomID });
Unfortunately this does not work...nothing gets printed in my DataGridView.
Question: How can I store all properties from the room table in my room model BUT ONLY show TWO of the four properties in the DataGridView?
Make sure your column names are defined in the DataGridView and create the object, basically assign the x.Identifier to the name "Identifier" so it can be picked up and call the ToList() function
roomDataGridView.DataSource = listOfRoomPropertiesForCurrentFloor
.Select(x => new {Identifier = x.Identifier, RoomID = x.RoomID }).ToList();
Expecting that DataGridView attribute AutoCreateColumns should be set to true. There are already answers:
c# Hide a property in datagridview with datasource
Is there an Attribute I can use in my class to tell DataGridView not to create a column for it when bound to a List
Extract:
Mark the property which should not be displayed with an attribute [Browsable(false)]

Does Linq OrderBy Not Sort Original Collection?

I am using Linq To Sql as my database layer for the first time and I have run into an issue. Basically, I have a form that allows users to create predefined jobs. A predefined job can have many predefined job items. For example, a predefined job might be something like an oil change. The predefined job items would be oil, labor, etc. In my database PredefinedJobs is a table and PredefinedJobItems is another table with a foreign key back to PredefinedJobs. I have a form for adding predefined jobs that has the Linq-to-Sql class backing the form as a class variable. There is a ListView on the form that displays all of the jobs items. A new feature has required me to track the position of an item in the ListView. For example, if my item ListView looks like below, note the order:
Qty Name Desc ItemOrder
4 Oil Penzoil 0
1 Labor 1
Because the items are added via a child form I do not want to provide access to the ListView. So, I created the method below in an attempt to both create the ItemOrder and sort the collection on the PredefinedJob Linq to Sql object. It does not appear that the OrderBy function on the List actually sorts the collection on the PredefinedJob. What would be the best way to maintain order on the Linq to Sql collection (i.e. PredefinedJob.fkJobItems)? Or, would it be better to just pass a reference to my ListView into the child form that adds the items to the jobs where I have access to the selectedIndex?
private SortAndOrderItems(List<PredefinedJobsItem> items)
{
var nextItemOrderNumber = items.Max(max => max.ItemOrder) + 1;
foreach (var item in items)
{
if (item.ItemOrder == null)
{
item.ItemOrder = nextItemOrderNumber;
nextItemOrderNumber++;
}
}
items.OrderBy(i => i.ItemOrder).ToList();
}
OrderBy creates a new query, that will, when executed, not alter your original list.
Why not just the Sort method of the List?
items.Sort((a, b) => a.ItemOrder.CompareTo(b.ItemOrder));
I think you were looking for List<>.Sort
class Cmp : IComparer<PredefinedJobsItem>
{
public int Compare(PredefinedJobsItem x, PredefinedJobsItem y)
{
return x.ItemOrder.CompareTo(y.ItemOrder);
}
}
var comparison = new Cmp();
items.Sort(comparison);

Remove a property/column from a generic list

Due to some reason I cannot change the query so I have to do this in C#.
I have a class:
public class myClass
{
int id { get; set; }
string name { get; set; }
DateTime sDate { get; set; }
bool status { get; set; }
}
The data I am getting is fetched in this list. Now what I want is to remove those properties from a list that has null values. I may sound insane but you read it right. I thought of creating another list with only the selected properties, but any of the above properties can be null. So I have to devise a mechanism to filter my list based on this.
For more clarity consider the following example.
List<myClass> lstClass = some data source.
After getting the data the generic list(lstClass) looks like this.Consider the result set in a table:
Id Name Sdate status
1 a null null
2 b null null
3 c null false
Can i some how make my list look like this after removing the property sdate.
So the new list that I want to create should have only three properties.
Id Name status
1 a null
2 b null
3 c false
Any ideas? Can I do this using Linq?
PS: This has nothing to do with presentation. I don’t have a grid where I am not able to hide columns that Is not what I am looking for.
Assuming you have a generic list of myClass instances, you can create an anonymous type with only the needed properties:
List<myClass> list = ...;
var reducedList = list.Select(e => new {e.id, e.name, e.status}).ToList();
// note: call to ToList() is optional
foreach (var item in reducedList)
{
Console.WriteLine(item.id + " " + item.name + " " + item.status);
//note: item does not have a property "sDate"
}
I'm not sure you should solve your issue in the Data, but rather it's a presentation problem.
In which control do you want to display it ? Let's say you display it in DataGrid with AutoGenerateColumns=True, then you can 1) loop on columns/properties 2) for each column/property see if all property values for all rows are null and if so set column's visibility to Collapsed.
If you generate your columns by yourself it's even simpler : only add columns when content is not null for all rows.
If your DB content is dynamic, you might want to bind each row's visibility to a property that would state wether all rows are null or not for that property. Depending on how generic you want your code to be, the code might be very different, and in case you want to have generic solution, using Reflection to retrieve/get/set properties might be of some use.

Categories