Convert DataTable to Array - c#

I want to convert a DataTable with 10 rows or more into an Array like this:
SqlConfiguration clist2 = new SqlConfiguration();
clist2.QueryString = "SELECT caption_id,caption,description FROM sws_template_detail WHERE template_id = 1";
DataTable db = clist2.GetRecords;
ListItem datalist = new ListItem();
foreach(DataRow row in db.Rows)
{
datalist = new ListItem
{
id = row["caption_id"].ToString(),
title = row["caption"].ToString(),
description = row["description"].ToString()
};
}
var section = new Section
{
title = "Title",
items = new ListItem[]
{
datalist
}
};
But the resulting data is only 1 row, how can I solve it?

You can do it in 1 line (well, 1 statement):
var section = new Section
{
title = "Title",
items = dataTable.Rows
.Cast<DataRow>()
.Select( row => new ListItem()
{
id = row["caption_id" ].ToString(),
title = row["caption" ].ToString(),
description = row["description"].ToString()
} )
.ToArray()
};
Though there's a lot of code smells going on here...
Why is an class named SqlConfiguration being used to execute a SQL query via a property named QueryString.
Public members in C#/.NET should be PascalCase not camelCase.
So it should be Section.Title, Section.Items, ListItem.Id, ListItem.Title, and ListItem.Description.
Don't use object-initializers for required members of an object, because there's no compile-time guarantees that they'll be populated.
If a ListItem must have an Id, Title, and Description then they should be passed-in as constructor parameters.
Using array-types (like ListItem[]) is usually not a good idea because array-types have the worst set of attributes in comparison to others: they're fixed-size but also mutable.
Whereas usually you want something resizable-and-mutable (e.g. List<T>) or completely immutable (e.g. ImmutableArray<T> or at least IReadOnlyList<T>).
Mutable elements
Resizable
Variance
T[] (Array types )
Yes
No
Unsafe
List<T>
Yes
Yes
Invariant
ImmutableArray<T>
No
No
Invariant
IReadOnlyList<T>
No
No
Covariant safe

try this
var items_array=new List<ListItem>();
foreach(DataRow row in db.Rows)
{
items_array.add(new ListItem
{
id = row["caption_id"].ToString(),
title = row["caption"].ToString(),
description = row["description"].ToString()
});
}
var section = new Section
{
title = "Title",
items = items_array.toArray()
};

Loading the data into a DataTable and then converting it into a List wastes both CPU and RAM. You can use an ORM like EF Core or Dapper to execute a query and return the results in the shape you want. For example, using Dapper, what you want is a single line:
var sql=#"select caption_id as Id, caption as Title, description
FROM sws_template_detail
WHERE template_id = 1";
var items=connection.Query<ListItem>(sql).ToArray();
Query<T> returns the results as an IEnumerable<T>. This is converted to an array using ToArray().
Dapper allows you to easily write parameterized queries instead of concatenating strings to construct a query:
var sql=#"select caption_id as Id, caption as Title, description
FROM sws_template_detail
WHERE template_id = #id";
var items=connection.Query<ListItem>(sql,new {id=1}).ToArray();
The query can be executed asynchronously using QueryAsync;
var items=(await connection.QueryAsync<ListItem>(sql,new {id=1}))
.ToArray();

Related

LINQ - Deserialize JSON column and filter

How to deserialize/serialize a property with JSON string array value and then filter (using where clause) in LINQ inside a lambda expression?
void Main()
{
var regionList = new List<Row>() {
new Row { RegionJsonList = "[\"QLD\",\"NSW\"]" },
new Row { RegionJsonList = "[\"TAZ\",\"SA\"]" },
new Row { RegionJsonList = "[\"QLD\",\"VIC\"]" }
};
var filterRegionList = new List<string>() {
"QLD", "NSW"
};
var queryable = regionList.AsQueryable();
// this is obviously wrong, i just want to find the Row that contains one on filterRegionList
var result = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Contains(filterRegionList));
result.Count().Dump(); // should be 2
}
class Row
{
public string RegionJsonList { get;set; }
}
Following would work:
var result =
filterRegionList.Aggregate(regionList,(current,filter) =>
current.Where( r => r.RegionJsonList.Contains(filter)).ToList())
Aggregating the filterRegionList and regionList and thus applying filters for the final result. I did not find a requirement to Deserialize the RegionJsonList, since this would work as is, but you may add that part in case you are keen.
Also we are applying And filter via aggregation, it checks for the rows which contains both the filters, and thus provide the result, you may modify filter to achieve more number of rows, like following will select two entries from original regionList
var filterRegionList = new List<string>() { "QLD" };
To filter for rows that contain at least one of the entries from filterRegionList, you can use Enumerable.Intersect and check for non-empty intersections:
var resultAny = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Intersect(filterRegionList).Any());
To filter for rows that contain all of the entries from filterRegionList, you can use Enumerable.Except to remove the row's entries from the filter list. If everything gets removed, it's a match:
var resultAll = queryable.Where(r => !filterRegionList.Except(JsonConvert.DeserializeObject<string[]>(r.RegionJsonList)).Any());
(It wasn't entirely clear from your question which you wanted.)

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;}
}

Lambda SQL Query / Manipulate String At Query Or After Result

I'm using C#, EF5, and Lambda style queries against SQL.
I have the usual scenario of binding data to gridviews. Some of the results for my columns may be too long (character count) and so I only want to display the first 'n' characters. Let's say 10 characters for this example. When I truncate a result, I'd like to indicate this by appending "...". So, let's say the following last names are returned:
Mercer, Smith, Garcia-Jones
I'd like them to be returned like this:
Mercer, Smith, Garcia-Jon...
I was doing something like this:
using (var context = new iaiEntityConnection())
{
var query = context.applications.Where(c => c.id == applicationPrimaryKey);
var results = query.ToList();
foreach (var row in results)
{
if (row.employerName.Length > 10)
{
row.employerName = row.employerName.Substring(0, Math.Min(10, row.employerName.ToString().Length)) + "...";
}
if (row.jobTitle.Length > 10)
{
row.jobTitle = row.jobTitle.Substring(0, Math.Min(10, row.jobTitle.ToString().Length)) + "...";
}
}
gdvWorkHistory.DataSource = results;
gdvWorkHistory.DataBind();
However, if I change my query to select specific columns like this:
var query2 = context.applications.Select(c => new
{
c.id,
c.applicationCode,
c.applicationCategoryLong,
c.applicationType,
c.renew_certification.PGI_nameLast,
c.renew_certification.PGI_nameFirst,
c.renew_certification.PAI_homeCity,
c.renew_certification.PAI_homeState,
c.reviewStatusUser,
c.dateTimeSubmittedByUser
})
The result appears to become read-only if specific columns are selected, and I really should be selecting just the columns I need. I'm losing my ability to edit the result set.
So, I'm rethinking the entire approach. There must be away to select the first 'n' characters on select, right? Is there anyway to append the "..." if the length is > 10 on select? That seems trickier. Also, I guess I could parse through the gridview after bind and make this adjustment. Or, perhaps there is a way to maintain my ability to edit the result set when selecting specific columns?
I welcome your thoughts. Thanks!
To quote MSDN
Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first.
So you would have to define a class and select into that if you want read write capability.
e.g.
public class MyClass {
public int id { get; set; }
public string applicationCode {get; set; }
// rest of property defintions.
}
var query2 = context.applications.Select(c => new MyClass {
id = c.id,
applicationCode = c.applicationCode,
// Rest of assignments
};
As to just providing 10 character limit with ... appended. I'm going to assume you mean on the applicationcategoryLog field but you can use the same logic on other fields.
var query2 = context.applications.Select(c => new
{
c.id,
c.applicationCode,
applicationCategoryLong = (c.applicationCategoryLong ?? string.Empty).Length <= 10 ?
c.applicationCategoryLong :
c.applicationCategoryLong.Substring(0,10) + "...",
c.applicationType,
c.renew_certification.PGI_nameLast,
c.renew_certification.PGI_nameFirst,
c.renew_certification.PAI_homeCity,
c.renew_certification.PAI_homeState,
c.reviewStatusUser,
c.dateTimeSubmittedByUser
})

Filtering list objects from another list

I have the following class in my C# .NET 3.5 win forms app:
class Field {
string objectName;
string objectType;
string fieldName;
string fieldValue;
}
and a List fieldList that is a datasource for a checkedlistbox. This listbox shows all the distinct objectNames from my fieldList collection.
I want to create another checkedlistbox that contains fieldNames, but only shows fieldnames that have an associated checked objectName in the first list box.
So my question is how can I query the DataSource of the original list of objectNames to return the distinct set of fieldNames that are associated with a selected objectName?
That is not very easy to read so I will give an example:
Field1 {
objectName = 'objA'
FieldName = 'FieldA'
}
Field2 {
objectName = 'objA'
FieldName = 'FieldB'
}
Field3 {
objectName = 'objB'
FieldName = 'FieldA'
}
Field4 {
objectName = 'objC'
FieldName = 'FieldC'
}
So suppose in my checkbox I select objectNames objA and objB. Then my returned fields would be 'FieldA' and 'FieldB'.
How can I achieve this using LINQ or filtering my generic list of Fields? Can I utilise the 'select' or 'where' methods that are available to a list?
First, read the object names into an array or list; I'll fake that part. Then it should be something like:
string[] objectNames = { "objA", "objC" };
var hashSet = new HashSet<string>(objectNames);
var qry = (from row in data
where hashSet.Contains(row.objectName)
select row.fieldName).Distinct().ToList();
(edit)
To get the selected names (the bit I faked) you could try (untested):
var selectedNames = namesCheckedListBox.CheckedItems.Cast<Field>()
.Select(field => field.objectName);
var hashSet = new HashSet<string>(selectedNames);
(note no need to use Distinct() in the above, since HashSet<T> does that anyway)
var selectedNames = ... // List of selected names
var selectedFields = (from f in fieldList
where selectedNames.Contains(f.objectName)
select f.FieldName).Distinct().ToList();

Specifying return rows in LINQ2DataSet

I have a requirement to extract a distinct subset of rows from a DataTable, and thought LINQ2DataSets may be a useful and clean way to do this, however it appears that it is not possible to simply identify return rows from a LINQ2DS query as follows
var result = from r in fips.AsEnumerable() select
r.Field<string>("FACILITY_PROCESS_SUB_GROUP_CODE"),
r.Field<string>("PROCESS_SUB_GROUP_NAME"),
r.Field<string>("...
as I start getting errors after the first comma.
Is this a correct assumption, and how would I get around it to return a subset of columns from the dataset that I can apply a Distinct() method to?
You forgot the new statement and field names:
var result = from r
in fips.AsEnumerable()
select new
{
FacProcess = r.Field<string>("FACILITY_PROCESS_SUB_GROUP_CODE"),
GroupName = r.Field<string>("PROCESS_SUB_GROUP_NAME"),
Item3 = r.Field<string>("Item3")
};
You can also explicitly declare that you are going to use a type:
var result = from r
in fips.AsEnumerable()
select new MyType("InitClassParams")
{
FacProcess = r.Field<string>("FACILITY_PROCESS_SUB_GROUP_CODE"),
GroupName = r.Field<string>("PROCESS_SUB_GROUP_NAME"),
Item3 = r.Field<string>("Item3")
};
Scott Guthrie (VP Developer Devision, Microsoft) has some good info about LINQ (he talks about LINQ to SQL, but most of it applies regardless).
Then apply the distinct clause:
var result = from r
in fips.AsEnumerable()
select new
{
FacProcess = r.Field<string>("FACILITY_PROCESS_SUB_GROUP_CODE"),
GroupName = r.Field<string>("PROCESS_SUB_GROUP_NAME"),
Item3 = r.Field<string>("Item3")
}
distinct;
Then put it to a list or iterate over it. Nothing will be selected/distincted/etc until something like on of the following is run:
var list = result.ToList()
foreach(var item in result) {}

Categories

Resources