Linq Query for querying a list of DataSet - c#

I have a list of DataSet.
for example:
List<DataSet> list = new List<DataSet>();
For my task, the number of DataSet in the list and the number of DataTable in each DataSet will be known at the run time.
Now I want to get those tables from the DataSets that contains a certain string in their names, for instance say 'Group1'.
I am trying with the following code:
var ds= from set in list from table in set
where li.Where(e=>e.Tables.Contains("Group")) select table;
But i am getting the error as 'An expression of type System.Data.DataSet is not allowed in a subsequent from clause in a query expression with source typeList'.
Please help me with the correct approach.

I've tried to replicate your data structure by creating another class. Hope this helps.
namespace TestCode
{
class Program
{
static void Main(string[] args)
{
var list = new List<TC> {new TC(2), new TC(2), new TC(3), new TC(4), new TC(5), new TC(2)};
var dt = list.Where( // Contains 3 elements
x => x.X == 2
);
//var ds = from set in list
// from table in set
// where li.Where(e => e.Tables.Contains("Group"))
// select table;
}
}
internal class TC
{
public int X { get; set; }
internal TC(int val)
{
X = val;
}
}
}

You original query is close. It just needs to be fleshed out a bit. First off it helps to declare the type in the from statement. Also specify you want the table collection from the set. The where clause should just need to examine the TableName property of the tables:
List<DataSet> list = new List<DataSet>();
var ds = from DataSet set in list
from DataTable table in set.Tables
where table.TableName.Contains("Group")
select table;

This gets the tables with the contained name:
var tables = list.SelectMany(x => x.Tables.Cast<DataTable>())
.Where(x => x.TableName.Contains("Group"));

Related

How to get data as IEnumerable<MODEL> from DataTable by using LINQ?

I have a DataTable with some attributes data get from a database, each data in that DataTable belong to a product, and each product could have more than one attribute.
So i have another DataTable which has all the products in a foreach by looping through each row i'm adding each product to it's List<Plu> like this:
var productAttr = new List<Plu.Attributi>();
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
productAttr,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
For now the productAttr is empty but now i need to add to each product it's attributes, so with the following function i get a DataTable filled with data from database with all product attributes:
var attributi = Attributi(connection, idNegozio);
and then i was trying to do something like this inside the foreach
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
from row in attributi.AsEnumerable() where row.Field<int>("ID_PLUREP_VAT") == id select row,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
But the LINQ returns a EnumerableRowCollection while i need a IEnumerable<Plu.Attribute>, so i was wondering if there is a lazy way to cast the .AsEnumerable to IEnumerable<Plu.Attrbute>...
The problem is, that the DataTable only knows which values are in the cells. It does not know what these values stand for. It does not know that the number in column 0 is in fact an Id. It doesn't know that the string in column 1 is the Name of a Customer, and the DateTime in column 2 is the Birthday of the Customer.
If you will be using the contents of this Datatable (or similar DataTables) for other queries in the future, you need some translation from DataRow to the items that they stand for.
Once you've got the translation from DataRow to Plu, you can convert your DataTable to an IEnumerable<Plu>, and do other LINQ processing on it.
Usage will be like:
DataTable table = ...
var mySelectedData = table.AsEnumerable().ToPlus()
.Where(plu => ...)
.Select(plu => new {...})
.ToList();
You need two extension methods: one that converts a DataRow to a Plu and one that converts a sequence of DataRows to a sequence of Plus. See extension methods demystified
public static Plu ToPlu(this DataRow row)
{
// TODO implement
}
public static IEnumerable<Plu> ToPlus(this IEnumerable<DataRow> dataRows)
{
// TODO: exception if null dataRows
return dataRows.Select(row => row.ToPlu());
}
If desired, create an extension method from DataTable to extract the Plus:
public static IEnumerable<Plu> ExtractPlus(this DataTable table)
{
// TODO: exception if table null
return table.AsEnumerable().ToPlus();
}
Usage:
DataTable table = ...
IEnumerable<Plu> plus = table.ExtractPlus();
I haven't got the faintest idea what a Plu is, and you forgot to mention the relevant properties of the Plu, so I'll give you an example of a table that contains Customers:
class Customer
{
public int Id {get; set;} // Id will be in column 0
public string Name {get; set;} // Name will be in column 1
...
}
public static Customer ToCustomer(this DataRow row)
{
return new Customer
{
Id = (int)row[0],
Name = (string)row[1],
};
}
If desired, instead of columnIndex you can use the name of the column.
So by only creating a ToPlu, and a one-liner method to convert sequences of DataRows to a sequence of Plus, you've extended LINQ with your methods to read your tables.
To be on the safe side, consider creating an extension method that converts a sequence of Plus to a DataTable. This way, the layout of the table is in one location: ToPlu(DataRow) and ToDataRow(Plu). Future changes in the table layout will be easier to manage, users of your DataTable will only think in sequences of Plus.
You can do something like below. If you want IEnumerable<Plu> you can remove the .ToList() from the end.
dt.AsEnumerable().Select(x => new Plu {
Id = x.Field<int>("ID_PLUREP"),
CodicePrep = x.Field<string>("CODICE_PRP"),
....
Attributes = attributi.AsEnumerable()
.Where(y => y.Field<int>("ID_PLUREP_VAT") == x.Field<int>("ID_PLUREP"))
.Select(z => new Attributi
{
....
}).ToList(),
....
}).ToList();

Load a list from a SQL Server database to C# combobox

I have a database with 2 categories that I turn into a list and plan to use it as a data source for a drop down combobox in C#
Category B is connected to category B for example (in database):
CatA CatB
a 1
a 2
b 3
b 4
The plan is to use droplist to choose catA and on change it will load CatB to the 2nd droplist.
So if I choose CATA=a I will get CATB=1,2
The code to get CATA
public List<string> getCatAlist()
{
List<string> catAlist = new List<string>();
List<string> filteredList = new List<string>();
_con.Open();
_cmd.CommandText = "SELECT * FROM category";
var dr = _cmd.ExecuteReader();
while (dr.Read())
catAlist.Add(dr["catA"].ToString());
_con.Close();
filteredList = catAlist.Distinct().ToList();
return filteredList;
}
The code to get CATB
public List<string> getCatBlist(string catA)
{
List<string> catBlist = new List<string>();
_con.Open();
_cmd.CommandText = "SELECT * FROM category WHERE catA='"+catA+"';";
var dr = _cmd.ExecuteReader();
while (dr.Read())
catBlist.Add(dr["catB"].ToString());
_con.Close();
return catBlist;
}
The problem is to set the list's as datasource for the combobox
I open a connection to the database and on the first run it loads the list, but when I try to use it again it converts it to a static list for the datasource.
Any idea on how to fix this?
If your categories are not very dynamic you can create Stored Proc where you make 2 calls - this optional, you can make two separate calls to DB
Select ... From Cat...
Select ... From Cat...
In .Net use
cmd.ExecuteReader
And use
reader.NextResult
...to retrieve each set of records.
Create classes for your categories (pseudo-code)
class CatA {
int value
string display
}
class CatB {
int parent
int value
string display
}
Fill your lists
List<CatA> listA;
List<CatA> listB;
Set your CatA combo properties
cboA.valuemember = "value";
cboA.displaymember = "display";
cboA.datasource = listA; // set DS last for better performance
Now, you can do this on listA SelectedIndexChanged
// Read comment
var subListCatB = listB.Where(i => i.parent == ((CatA)listA.SelectedItem).value).ToList()
//I think you need to use 'where' here but if you have A, which has no B then you need null check
cboB.valuemember = "value";
cboB.displaymember = "display";
cboB.datasource = subListCatB ; // set DS last for better performance
This is it

Linq To SQL Select Dynamic Columns

Is it possible to dynamically limit the number of columns returned from a LINQ to SQL query?
I have a database SQL View with over 50 columns. My app has a domain object with over 50 properties, one for each column. In my winforms project I bind a list of domain objects to a grid. By default only a few of the columns are visible however the user can turn on/off any of the columns.
Users are complaining the grid takes too long to load. I captured the LINQ generated SQL query then executed it within SQL Server Management Studio and verified its slow. If I alter the SQL statement, removing all the invisible columns, it runs almost instantly. There is a direct correlation between performance and the number of columns in the query.
I'm wondering if its possible to dynamically alter the number of columns returned from the LINQ generated SQL query? For example, here is what my code currently looks like:
public List<Entity> GetEntities()
{
using (var context = new CensusEntities())
{
return (from e in context.Entities
select e).ToList();
}
}
The context.Entities object was generated from a SQL View that contains over 50 columns so when the above executes it generates SQL like "SELECT Col1, Col2, Col3, ... Col50 FROM Entity INNER JOIN...". I would like to change the method signature to look like this:
public List<Entity> GetEntities(string[] visibleColumns)
{
using (var context = new CensusEntities())
{
return (from e in context.Entities
select e).ToList();
}
}
I'm not sure how to alter the body of this method to change the generated SQL statement to only return the column values I care about, all others can be NULL.
Something like this should work:
List<string> columns = new List<string>();
columns.Add("EmployeeID");
columns.Add("HireDate");
columns.Add("City");
Add columns to your list ^.
var result = Class.ReturnList(columns);
Pass the List to a method ^.
public static List<Entity> ReturnList(List<string> VisibleColumns)
{
StringBuilder SqlStatement = new StringBuilder();
SqlStatement.Append("Select ");
for (int i = 0; i < VisibleColumns.Count; i++)
{
if (i == VisibleColumns.Count - 1)
{
SqlStatement.Append(VisibleColumns[i]);
}
else
{
SqlStatement.Append(VisibleColumns[i]);
SqlStatement.Append(",");
}
}
SqlStatement.Append(" FROM Entity");
using (var ctx = new DataClasses1DataContext())
{
var result = ctx.ExecuteQuery<Entity>(SqlStatement.ToString());
return result.ToList();
}
}
This basically just makes a SELECT statement with all the fields you passed in with the VisibleColumns list.
In this case, the SQL statement that will be generated by the strings in the VisibleColumns list is:
Select EmployeeID, HireDate, City From Employee
(note: i used the Northwind database to try this out, hence the EmployeeID etc column names. You should replace them with your own, obviously.)
It is not trivial to do this dynamically, but if you have a limited set of combinations of columns you want to retreive you can do an explicit select like this:
public List<Entity> GetEntities()
{
using (var context = new CensusEntities())
{
return (from e in context.Entities
select new
{
col1 = e.col1,
col4 = e.col4,
col5 = e.col5,
}
).ToList()
.Select(x=>new Entity{col1 = x.col1, col4 = x.col4, col5 = x.col5}).ToList();
}
}
The extra select step is necessary because LINQ2SQL won't create partial entities for you.
Create a method for each common combination of columns (especially the initial) the users wants to retrieve.
However to make this dynamic you can build a query with you entity stored as a property in an anonymous class and collect your result properties in another anonymous class in second property in the same anonymous class. Finally you select your entities from the collected objects into objects of the correct type.
public List<Entity> GetEntities()
{
using (var context = new CensusEntities())
{
var combinedResult = (from e in context.Entities
select new {
Entity = e,
CollectedValues = new
{
// Insert default values of the correct type as placeholders
col1 = 0, // or "" for string or false for bool
col2 = 0, // or "" for string or false for bool
// ...
col49 = 0, // or "" for string or false for bool
col50 = 0, // or "" for string or false for bool
}
);
// Then copy each requested property
// col1
if (useCol1)
{
var combinedResult = (from e in combinedResult
select new {
Entity = e,
CollectedValues = new
{
col1 = e.Enitity.col1, // <-- here we update with the real value
col2 = e.CollectedValues.col2, // <-- here we just use any previous value
// ...
col49 = e.CollectedValues.col49, // <-- here we just use any previous value
col50 = e.CollectedValues.col50, // <-- here we just use any previous value }
);
}
// col2
if (useCol2)
{
// same as last time
col1 = e.CollectedValues.col1, // <-- here we just use any previous value
col2 = e.Enitity.col2, // <-- here we update with the real value
// ...
}
// repeat for all columns, update the column you want to fetch
// Just get the collected objects, discard the temporary
// Entity property. When the query is executed here only
// The properties we actually have used from the Entity object
// will be fetched from the database and mapped.
return combinedResult.Select(x => x.CollectedValues).ToList()
.Select(x=>new Entity{col1 = x.col1, col2 = x.col2, ... col50 = x.col50}).ToList();
}
}
There will be lots of code, and a pain to maintain, but it should work.
If you are going this route I suggest that you build a code generator that builds this code with reflection from your LINQ context.
Try something like this
using (var context = new CensusEntities())
{
var q = from e in context.Entities
select e.myfield1,e.myfield2;
return q.Tolist();
}
The resulting query should be lighter and also all the data conversion that goes underneath.
But if you really need to build dynamic input, I think some dynamic sql should be involved. So
build the dynamic SQL and get a data table
use a datatable to a dynamic object conversion as shown here
How can I convert a DataTable into a Dynamic object?
BTW a lot of hard work, I think you should considered using the first block of code.

Convert DataTable to LINQ: Unable to query multiple fields

Importing a spreadsheet I have filled a DataTable object with that data and returns expected results.
Attempting to put this into a format I can easily query to search for problem records I have done the following
public void Something(DataTable dt)
{
var data = from row in dt.AsEnumerable()
select row["Order"].ToString();
}
Works as expected giving me a list of orders. However I cannot add other fields to this EnumerableRowCollection. Attempting to add other fields as follows gives me an error
public void Something(DataTable dt)
{
// row["Version"] throws an error on me
var data = from row in dt.AsEnumerable()
select row["Order"].ToString(), row["Version"].ToString();
}
Error: "A local variable named 'row' cannot be declared in this scope because it would give a different meaning to 'row' which is already used in a 'child' scope to donate something else"
I'm thinking I need to alias the column name but I'm having no luck. What am I missing here?
It sounds like you're writing a bad select statement. Try the following:
public void Something(DataTable dt)
{
var data = from row in dt.AsEnumerable()
select new {
Order = row["Order"].ToString(),
Something = row["Something"].ToString(),
Customer = row["Customer"].ToString(),
Address = row["Address"].ToString()
};
}
That will create a new collection of Anonymously Typed objects that you can iterate over and use as needed. Keep in mind, though, that you want be able to return data from the function. If you need that functionality, you need to create a concrete type to use (in place of anonymous types).
I think you should use select new like this query for example:
var q = from o in db.Orders
where o.Products.ProductName.StartsWith("Asset") &&
o.PaymentApproved == true
select new { name = o.Contacts.FirstName + " " +
o.Contacts.LastName,
product = o.Products.ProductName,
version = o.Products.Version +
(o.Products.SubVersion * 0.1)
};
You probably want the following.
var data = from row
in dt.AsEnumerable()
select new { Order = row["Order"].ToString(), Version = row["Version"].ToString() };

How to return first 50 characters of text in LINQ call

I have a small winapp that uses LinqToSQL as it's DAL. I am creating a summary view of all the CaseNotes for a given person and one of the fields is a Details box. I need to return only the first 50 characters of that column to my treeview function.
Any hints on how I do that? The below is how my TreeView function gets its data for display and the ContactDetails is the column in question.
public static DataTable GetTreeViewCNotes(int personID)
{
var context = new MATRIXDataContext();
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new { cn.CaseNoteID,cn.ContactDate, cn.ParentNote, cn.IsCaseLog, cn.ContactDetails };
var dataTable = caseNotesTree.CopyLinqToDataTable();
context.Dispose();
return dataTable;
}
ANSWER
I am posting this here in case any future searchers wonder what the solution looks like in the questions context.
public static DataTable GetTreeViewCNotes(int personID)
{
DataTable dataTable;
using (var context = new MATRIXDataContext())
{
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new
{
cn.CaseNoteID,
cn.ContactDate,
cn.ParentNote,
cn.IsCaseLog,
ContactDetailsPreview = cn.ContactDetails.Substring(0,50)
};
dataTable = caseNotesTree.CopyLinqToDataTable();
}
return dataTable;
}
String.Substring:
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new {
cn.CaseNoteID,
cn.ContactDate,
cn.ParentNote,
cn.IsCaseLog,
ContactDetailsClip = cn.ContactDetails.Substring(0, Math.Min(cn.ContactDetails.Length, 50))
};
Also, I would suggest wrapping your use of DataContexts in using blocks.
With LinQ you can also do the following :
new string( myString.Take(50).ToArray() );
cn.ContactDetails.Substring(0, 50);
In the "select new" line. Does that work?
LINQ syntax
string message=new string(myString.Take(50).ToArray());

Categories

Resources