How to get values from multiple columns as IEnumerable using SqlKata (Dapper) - c#

I'm using SQL and I have a table shown below in my DB.
Id Remark1 Remark2 Remark3 Remark4
------------------------------------------------
1 AAA BBB CCC DDD
2 EEE FFF GGG HHH
How can I get all the values from Remark* columns as IEnumerable<string> using the following DTO?
class MyDTO
{
public int ID { get; }
public IEnumerable<string> Remarks { get; }
}
NOTE: I'm using SqlKata (Dapper), which you can also use when answering.

If you're using Dapper, maybe just use the non-generic Query API. This returns each row as dynamic, but this can also be cast to IDictionary<string,object>, which allows you to access each named column (for example, via foreach).
foreach (IDictionary<string, object> row in conn.Query(sql, args))
{
var obj = new MyDto();
var vals = new List<string>();
obj.Remarks = list;
foreach ((var key, var value) in row)
{
if (key == nameof(obj.Id))
obj.Id = (int)value;
else
vals.Add((string)value);
}
// do something with obj
}
```

Another option would be to create an ugly looking - but working - UNION ALL query:
SELECT Id, Remark1 as Remark FROM Table
UNION ALL
SELECT Id, Remark2 as Remark FROM Table
UNION ALL
SELECT Id, Remark... as Remark FROM Table
That's can be done in a for..loop using SqlKata, calling the Union inside the loop
https://sqlkata.com/docs/combine#union-except-intersect
Non-Tested code sample:
var q = new Query("Table").Select("Id", "Remark1 as Remark");
for(int r=2;r<=50;i++)
{
var u = new Query("Table").Select("Id", $"Remark{r} as Remark");
q = q.Union(u);
}

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

How to return dynamic object from SQL query

I have situation where a storeprocdure return collection, but I do not how the object structure because the query is very dynamic.
One query can return:
Id | Location | MarketSegment | ... n columns
and another can return
Id | Sales Rep | Location | Region | ... n columns
I am simply just return a "object" as you can see in the code below. I know this won't work, but how can I set it up so it does?
using (DbContext db = new Context())
{
var items = db.Database.SqlQuery<object>(
"SP #Param1, #Param2",
new SqlParameter("Param1", ped),
new SqlParameter("Param2", 25)
).ToList();
return Request.CreateResponse<List<object>>(HttpStatusCode.OK, items);
}
EDIT:
I don't know if showing the SP will help in anyways, except if I can explain it more.
Each columns are represented as Custom Fields. Users are able to create n numbers of Custom Fields. So If you run the SP for User1 and he has 5 custom fields, then each custom fields will be represented in Columns, but If User2 has 3 custom fields, only 3 columns will be represented. What I don't have control over is the Custom Field Name and number of custom fields.
If on SQL 2016 or newer, add "FOR JSON AUTO" to your query to return as JSON, e.g:
var json = db.Database.SqlQuery<string>("Select x, y, z FROM tbl FOR JSON AUTO").First();
Then use Json.Net to create a dynamic object using
var myDynamic = JObject.Parse(json)
You can't use SqlQuery<T> for custom fields.
Creates a raw SQL query that will return elements of the given generic
type. The type can be any type that has properties that match the
names of the columns returned from the query, or can be a simple
primitive type. - MSDN
But, you can use ExecuteReader to achieve that.
using (var db = new Context())
{
db.Database.Connection.Open();
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "SP #Param1, #Param2";
cmd.Parameters.Add(new SqlParameter("Param1", ped));
cmd.Parameters.Add(new SqlParameter("Param2", 25));
List<List<object>> items = new List<List<object>>();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var item = new List<Object>();
items.Add(item);
for (int i = 0; i < reader.FieldCount ; i++)
item.Add(reader[i]);
}
return Request.CreateResponse<List<object>>(HttpStatusCode.OK, items);
}
If you know what all the possible columns could be returned, there is no issue with using a class that has more properties than you need.
public class Data
{
public int ID {get;set;}
public string SalesRep {get;set;}//Simply will be empty in the first example, but populated in the second.
public string Location {get;set;}
}

How to use GroupBy using Dynamic LINQ

I am trying to do a GroupBy using Dynamic LINQ but have trouble getting it to work.
This is some sample code illustrating the problem:
List<dtoMyAlbum> listAlbums = new List<dtoMyAlbum>();
for (int i = 0; i < 5000; i++)
{
dtoMyAlbum album = new dtoMyAlbum
{
Author = "My Author",
BookID = i,
CurrSymbol = "USD",
Price = 23.23,
Shop = i % 3 == 0 ? "TESCO" : "HMV"
};
listAlbums.Add(album);
}
IQueryable<dtoMyAlbum> mydata = listAlbums.AsQueryable();
int count = mydata.Count();
//var mydataGrouped = mydata.GroupBy(a => a.Shop); // <-- this works well (but is not dynamic....)
var mydataGrouped = mydata.GroupBy("Shop"); // <-- does not compile but is kind of what I want...
foreach (var group in mydataGrouped)
{
//count = group.Count();
}
I realise that I am missing the 'elementSelector' in the GroupBy overload but all I want to do is to end up with (in this case) two sets of dtoMyAlbum objects so I wish to select ALL elements for all sets...
How would I go about this?
There is default it defined, you can use it to return matched elements:
var mydataGrouped = mydata.GroupBy("Shop", "it");
To iterate through results you should additionally Select elements to name it and use dynamics:
var mydataGrouped = mydata.GroupBy("Shop", "it").Select("new (it.Key as Shop, it as Albums)");
foreach (dynamic group in mydataGrouped)
{
foreach (dynamic album in group.Albums)
{
Console.WriteLine(album.Author);
}
}
You may construct the group by expression dynamically or give a try to this Dynamic LINQ library presented on ScottGu's page:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
This is the method I have copied from DynamicQuery:
public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) {..}
In my example I provide the keySelector and the elementSelector.
listAlbums.AsQueryable().GroupBy("it.Shop", "it").Select("Author"); You can use new with GroupBy or with select for the new types.
It like this in OOP class.

Get specific value from datatable with where-clause

I want to query a specific value from a DataTable.
Lets say i have a DataTable which contains 2 columns:
id
item_name
Now what I want to do is like i would do it with mysql: SELECT * FROM "DataTable" WHERE item_name = 'MyItemName'
And then get the id that belongs to that 'item_name'...
int blah;
while (MyReader.Read())
{
blah = MyReader.GetInt32("id");
}
Now: how can I do this using DataTable?
I've got a snippet but I can't seem to show the returned value in a messagebox:
string test = Item1txt.Text;
var query = producten.Rows.Cast<DataRow>().Where(x => x.Field<string>("item_name") == test);
foreach (var st in query)
{
MessageBox.Show(st.ToString());
// how can i show the id that belongs to "test" ?
}
query will be an IQueryable<DataRow>, so st will be a DataRow. Try this:
foreach (var st in query)
{
MessageBox.Show(st.Field<int>("id").ToString());
}
Or if you know there will only item with that item_name, here's an alternative version which does essentially the same thing, but is probably a bit easier to understand:
var st = producten.Rows.Cast<DataRow>().FirstOrDefault(x => x.Field<string>("item_name") == test);
if(item != null)
{
MessageBox.Show(st.Field<int>("id").ToString());
}
You can use linq directly on the datatable without the need of Rows or the Cast.
var query = producten.AsEnumerable().Where(x => x.Field<string>("item_name") == test);
foreach (var st in query)
{
MessageBox.Show(st.Field<int>("id"));
}
I usually use the Rowfilter property of the defaultview of the datatable, but I must admit I never did LINQ myself, so there's probably a better way now...

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.

Categories

Resources