How to apply a SQL statement on one or more DataTable's? - sql

Question
Please help me writing a C# or vb.net routine that will accept
a DataTable
a SQL query as a string
and create
a DataTable with the result of the query applied to the in put DataTable
To my knowledge, the tool to run SQL in .net is linq, but this does not lead me to a sollution.
In VB.net terms: How do I implement a function like this
Public Function SelectFromDataTable(Sql As String, T1 As DataTable) As DataTable
// Apply Sql to T1
End Function
(or even better, like this)
Public Function SelectFromDataTable(Sql As String, T1 As DataTable, Optional T2 As DataTable) As DataTable
// Apply Sql to T1 and T2
End Function
What I tried so far
For some reason, I thought linq could be the solution, but that is no requirement.
Trial 1
if I look for the combination of linq and DataTable's I get that typicle syntax in which you write sql-like code inline in your .net code, as on. Queries in LINQ to DataSet
I want the query to be defined outside my routine,so can you also create such queries from a SQL string?
Trial 2
Looking for the combination of linq and SQL, I get examples using a SqlDataAdapter, but they need a SqlConnection, which apparently must point to a database, as in How to receive a SQL-Statement as a DataTable
However, for me, not only the destination, but also the source should be a DataTable, so can you also create a SqlConnection to DataTables?
Context
If you are curious where my question comes from:
BluePrism is a graphic Robotic Process Automation (RPA) tool. It has one container object, called a collection, which is under the hood a .net DataTable and gives verry little support to manipulate these.
Fortunately, one can create so called "business objects" in .net and implements "Action" that receive and return variables. (This is meant to manipulate other applications, but can also be used to manipulate data.)
We already have such an object, which we called Collection Manipulation. One of the actions, Filter Collection, is implemented as
Dim NewRow As DataRow
Collection_Out = Collection_In.Clone
For Each parentRow As DataRow In Collection_In.Select(Select_Condition)
NewRow = Collection_Out.NewRow
For Each c As DataColumn In NewRow.Table.Columns
NewRow(c.ColumnName) = parentRow(c.ColumnName)
Next
Collection_Out.Rows.Add(NewRow)
Next
NewRow = Nothing
Collection_In = Nothing
I would like to implement a general purpose Action, to runs queries against my collection like
select category, sum(unit_price * units) as total_price
from invoice
group by category;
select article, order.units - delivery.units as units_missing
from order, delivery
where order.article = delivery.article;

If I understood your question correct, you want a SQL syntax like multi-purpose select for data tables.
Based on the info found here: https://www.hanselman.com/blog/TheWeeklySourceCode48DynamicQueryableMakesCustomLINQExpressionsEasier.aspx, I wrote the following example further down. You can expand it as you see fit.
TL;DR Add the System.Linq.Dynamic NuGet package so you can use strings for where clauses amongst others.
BTW: Writing a query string parser, to parse for instance "select category, sum(unit_price * units) as total_price from invoice group by category;" is entirely possible, but IMHO you will spend a lot of time to gain little.
using System.Data;
using System.Linq;
using System.Linq.Dynamic;
namespace Foo {
public class Bar {
/// <summary>
///
/// </summary>
/// <param name="from"></param>
/// <param name="where"></param>
/// <param name="skipRows"></param>
/// <param name="takeRows"></param>
/// <param name="orderBy">Needed for range selections (skipRows, takeRows) </param>
/// <returns></returns>
public DataTable GeneralPurposeSelect(DataTable from, string where = null, int? skipRows = null, int? takeRows = null, string orderBy = "Id") {
var fromQryAble = from.AsEnumerable().AsQueryable();
IQueryable<DataRow> toQryAble = null;
if (!string.IsNullOrEmpty(where)) {
toQryAble = fromQryAble.Where(where);
}
if (takeRows != null) {
if (skipRows == null) {
skipRows = 0;
}
}
if (skipRows != 0) {
if (takeRows == null) {
takeRows = int.MaxValue;
}
}
if (takeRows != null) {
if (skipRows == null) {
skipRows = 0;
}
toQryAble = toQryAble == null ?
fromQryAble.OrderBy(orderBy).Skip(skipRows.Value).Take(takeRows.Value) :
toQryAble.OrderBy(orderBy).Skip(skipRows.Value).Take(takeRows.Value);
}
return toQryAble == null ? from : toQryAble.CopyToDataTable();
}
}
}

Related

Returning distinct data for a dropdownlist box with selectlistItem

I have a field in my database with duplicates. I want to use it in a dropdown list, which has to return distinct data.
Here is the method that I created to do this:
public IEnumerable<SelectListItem> GetBranches(string username)
{
using (var objData = new BranchEntities())
{
IEnumerable<SelectListItem> objdataresult = objData.ABC_USER.Select(c => new SelectListItem
{
Value = c.BRANCH_CODE.ToString(),
Text = c.BRANCH_CODE
}).Distinct(new Reuseablecomp.SelectListItemComparer());
return objdataresult;
}
}
Here is the class I am using:
public static class Reuseablecomp
{
public class SelectListItemComparer : IEqualityComparer<SelectListItem>
{
public bool Equals(SelectListItem x, SelectListItem y)
{
return x.Text == y.Text && x.Value == y.Value;
}
public int GetHashCode(SelectListItem item)
{
int hashText = item.Text == null ? 0 : item.Text.GetHashCode();
int hashValue = item.Value == null ? 0 : item.Value.GetHashCode();
return hashText ^ hashValue;
}
}
}
Nothing is returned and I get the error below. When I try a basic query without Distinct, everything works fine.
{"The operation cannot be completed because the DbContext has been disposed."}
System.Exception {System.InvalidOperationException}
Inner exception = null
How can I return distinct data for my dropdown?
Technically, your problem can be solved simply by appending .ToList() after your Distinct(...) call. The problem is that queries are evaluated JIT (just in time). In other words, until the actual data the query represents is needed, the query is not actually sent to the database. Calling ToList is one such thing that requires the actual data, and therefore will cause the query to be evaluated immediately.
However, the root cause of your problem is that you are doing this within a using statement. When the method exits, the query has not yet been evaluated, but you have now disposed of your context. Therefore, when it comes time to actually evaluate that query, there's no context to do it with and you get that exception. You should really never use a database context in conjuction with using. It's just a recipe for disaster. Your context should ideally be request-scoped and you should use dependency injection to feed it to whatever objects or methods need it.
Also, for what it's worth, you can simply move your Distinct call to before your Select and you won't need a custom IEqualityComparer any more. For example:
var objdataresult = objData.ABC_USER.Distinct().Select(c => new SelectListItem
{
Value = c.BRANCH_CODE.ToString(),
Text = c.BRANCH_CODE
});
Order of ops does matter here. Calling Distinct first includes it as part of the query to the database, but calling it after, as you're doing, runs it on the in-memory collection, once evaluated. The latter requires, then, custom logic to determine what constitutes distinct items in an IEnumerable<SelectListItem>, which is obviously not necessary for the database query version.

How do I add a lazy loaded column in EntitySpaces?

If you do not have experience with or aren't currently using EntitySpaces ("ES") ORM this question is not meant for you.
I have a 10 year old application that after 4 years now needs my attention. My application uses a now defunct ORM called EntitySpaces and I'm hoping if you're reading this you have experience or maybe still use it too! Switching to another ORM is not an option at this time so I need to find a way to make this work.
Between the time I last actively worked on my application and now (ES Version 2012-09-30), EntitySpaces ("ES") has gone through a significant change in the underlying ADO.net back-end. The scenario that I'm seeking help on is when an entity collection is loaded with only a subset of the columns:
_products = new ProductCollection();
_products.Query.SelectAllExcept(_products.Query.ImageData);
_products.LoadAll();
I then override the properties that weren't loaded in the initial select so that I may lazyload them in the accessor. Here is an example of one such lazy-loaded property that used to work perfectly.
public override byte[] ImageData
{
get
{
bool rowIsDirty = base.es.RowState != DataRowState.Unchanged;
// Check if we have loaded the blob data
if(base.Row.Table != null && base.Row.Table.Columns.Contains(ProductMetadata.ColumnNames.ImageData) == false)
{
// add the column before we can save data to the entity
this.Row.Table.Columns.Add(ProductMetadata.ColumnNames.ImageData, typeof(byte[]));
}
if(base.Row[ProductMetadata.ColumnNames.ImageData] is System.DBNull)
{
// Need to load the data
Product product = new Product();
product.Query.Select(product.Query.ImageData).Where(product.Query.ProductID == base.ProductID);
if(product.Query.Load())
{
if (product.Row[ProductMetadata.ColumnNames.ImageData] is System.DBNull == false)
{
base.ImageData = product.ImageData;
if (rowIsDirty == false)
{
base.AcceptChanges();
}
}
}
}
return base.ImageData;
}
set
{
base.ImageData = value;
}
}
The interesting part is where I add the column to the underlying DataTable DataColumn collection:
this.Row.Table.Columns.Add(ProductMetadata.ColumnNames.ImageData, typeof(byte[]));
I had to comment out all the ADO.net related stuff from that accessor when I updated to the current (and open source) edition of ES (version 2012-09-30). That means that the "ImageData" column isn't properly configured and when I change it's data and attempt to save the entity I receive the following error:
Column 'ImageData' does not belong to table .
I've spent a few days looking through the ES source and experimenting and it appears that they no longer use a DataTable to back the entities, but instead are using a 'esSmartDictionary'.
My question is: Is there a known, supported way to accomplish the same lazy loaded behavior that used to work in the new version of ES? Where I can update a property (i.e. column) that wasn't included in the initial select by telling the ORM to add it to the entity backing store?
After analyzing how ES constructs the DataTable that is uses for updates it became clear that columns not included in the initial select (i.e. load) operation needed to be added to the esEntityCollectionBase.SelectedColumns dictionary. I added the following method to handle this.
/// <summary>
/// Appends the specified column to the SelectedColumns dictionary. The selected columns collection is
/// important as it serves as the basis for DataTable creation when updating an entity collection. If you've
/// lazy loaded a column (i.e. it wasn't included in the initial select) it will not be automatically
/// included in the selected columns collection. If you want to update the collection including the lazy
/// loaded column you need to use this method to add the column to the Select Columns list.
/// </summary>
/// <param name="columnName">The lazy loaded column name. Note: Use the {yourentityname}Metadata.ColumnNames
/// class to access the column names.</param>
public void AddLazyLoadedColumn(string columnName)
{
if(this.selectedColumns == null)
{
throw new Exception(
"You can only append a lazy-loaded Column to a partially selected entity collection");
}
if (this.selectedColumns.ContainsKey(columnName))
{
return;
}
else
{
// Using the count because I can't determine what the value is supposed to be or how it's used. From
// I can tell it's just the number of the column as it was selected: if 8 colums were selected the
// value would be 1 through 8 - ??
int columnValue = selectedColumns.Count;
this.selectedColumns.Add(columnName, columnValue);
}
}
You would use this method like this:
public override System.Byte[] ImageData
{
get
{
var collection = this.GetCollection();
if(collection != null)
{
collection.AddLazyLoadedColumn(ProductMetadata.ColumnNames.ImageData);
}
...
It's a shame that nobody is interested in the open source EntitySpaces. I'd be happy to work on it if I thought it had a future, but it doesn't appear so. :(
I'm still interested in any other approaches or insight from other users.

Return type from DAL class (Sql ce, Linq to Sql)

Using VS2008 and Sql CE 3.5, and preferably Linq to Sql.
I'm learning database, and unsure about DAL methods return types and how/where to map the data over to my business objects: I don't want direct UI binding.
A business object class UserData, and a class UserDataList (Inherits List(Of UserData)), is represented in the database by the table "Users". I use SQL Compact and run SqlMetal which creates dbml/designer.vb file. This gives me a class with a TableAttribute:
<Table()> _
Partial Public Class Users
I'm unsure how to use this class. Should my business object know about this class, such that the DAL can return the type Users, or List(Of Users) ?
So for example the "UserDataService Class" is a part of the DAL, and would have for example the functions GetAll and GetById. Will this be correct : ?
Public Class UserDataService
Public Function GetAll() As List(Of Users)
Dim ctx As New MyDB(connection)
Dim q As List(Of Users) = From n In ctx.Users Select n
Return q
End Function
Public Function GetById(ByVal id As Integer) As Users
Dim ctx As New MyDB(connection)
Dim q As Users = (From n In ctx.Users Where n.UserID = id Select n).Single
Return q
End Function
And then, would I perhaps have a method, say in the UserDataList class, like:
Public Class UserDataList
Inherits List(Of UserData)
Public Sub LoadFromDatabase()
Me.clear()
Dim database as New UserDataService
dim users as List(Of Users)
users = database.GetAll()
For each u in users
dim newUser as new UserData
newUser.Id = u.Id
newUser.Name = u.Name
Me.Add(newUser)
Next
End Sub
End Class
Is this a sensible approach? Would appreciate any suggestions/alternatives, as this is my first attempt on a database DAL.
cheers!
EDIT:
Seems I have problems with the query/return types of GetAll() and GetAllById().. Not sure how to do this..
Open Visual Studio.
Open the server connections tab and connect to your SQL server.
In your project, add a new Linq To SQL Data Context. This will add a new file. Once this is open in the designer, drag and drop the tables from your database into the SQL Data context.
At this point, you can now go to your code and say.
NameOfYourDataContext dc = new NameOfYourDataContext();
var query = from row in dc.table
where property == 0 //Filter if needed
select row;
//OR var query = dc.table.where(row => row.Property == 0);
int i = 0;
foreach(var row in query)
{
row.property = i++;
}
dc.SubmitChanges();
If you are really trying to control the class that is created then I recommend that you look into the SQL Metal tool which can create data contexts from an xml file.

Loading jqgrid from query with multiple joins

I am trying to load a sortable jqgrid 3.5 from a query with multiple joins in it and having much difficulty as I am a novice with both Linq and jqgrid. In order to allow for sorting I first was attempting to load it using dynamic SQL.
Since I am pulling columns from multiple tables I assume my return will be a class object which I will populate (or will it be a table). How can I return a IQueryable custom class object when using dynamic SQL with multiple .JOIN clauses. If this is impossible how do I return IQueryable data from a stored procedure call. It is easy to create dynamic SQL in the stored procedure - I am unsure how to load my grid with it however.
Sorry if this is all over the place but I can't seem to find a way. If you can recommend the most straight forward way to load my sortable grid from a query which has multiple joins in I am much appreciated.
My controller code:
public ActionResult GridData(string sidx, string sord, int page, int rows)
{
EquipTrak eqt = new EquipTrak();
var equipment = eqt.GetGridEquipment(sidx, sord);
var dataJson = new
{
total = 10000,
page = 1,
records = 10000,
rows = (from e in equipment
select new
{
equip_id = e.equip_id,
cell = new string[] {
e.equip_id,
e.equipType,
e.makeType,
String.Format("{0:MM/dd/yyyy}", e.serv_due_dt)
}
}).ToArray()
};
return Json(dataJson);
}
}
my class code (incomplete):
namespace ULS_Site.Models
{
public class EquipTrak
{
uls_dbDataContext ulsDB = new uls_dbDataContext();
public IQueryable<equipmentCls> GetGridEquipment(string sidx, string sord)
{
try
{
return
Not sure if this is the best or worst solution but I used SQL Server views to handle all the joining required. I could then use .Orderby and .Where against the view which was in my data context.

How to intercept and modify SQL query in Linq to SQL

I was wondering if there is any way to intercept and modify the sql generated from linq to Sql before the query is sent off?
Basically, we have a record security layer, that given a query like 'select * from records' it will modify the query to be something like 'select * from records WHERE [somesecurityfilter]'
I am trying to find the best way to intercept and modify the sql before its executed by the linq to sql provider.
Ok, first to directly answer your question (but read on for words of caution ;)), there is a way, albeit a finicky one, to do what you want.
// IQueryable<Customer> L2S query definition, db is DataContext (AdventureWorks)
var cs = from c in db.Customers
select c;
// extract command and append your stuff
DbCommand dbc = db.GetCommand(cs);
dbc.CommandText += " WHERE MiddleName = 'M.'";
// modify command and execute letting data context map it to IEnumerable<T>
var result = db.ExecuteQuery<Customer>(dbc.CommandText, new object[] { });
Now, the caveats.
You have to know which query is generated so you would know how to modify it, this prolongs development.
It falls out of L2S framework and thus creates a possible gaping hole for sustainable development, if anyone modifies a Linq it will hurt.
If your Linq causes parameters (has a where or other extension causing a WHERE section to appear with constants) it complicates things, you'll have to extract and pass those parameters to ExecuteQuery
All in all, possible but very troublesome. That being said you should consider using .Where() extension as Yaakov suggested. If you want to centrally controll security on object level using this approach you can create an extension to handle it for you
static class MySecurityExtensions
{
public static IQueryable<Customer> ApplySecurity(this IQueryable<Customer> source)
{
return source.Where(x => x.MiddleName == "M.");
}
}
//...
// now apply it to any Customer query
var cs = (from c in db.Customers select c).ApplySecurity();
so if you modify ApplySecurity it will automatically be applied to all linq queries on Customer object.
If you want to intercept the SQL generated by L2S and fiddle with that, your best option is to create a wrapper classes for SqlConnection, SqlCommand, DbProviderFactory etc. Give a wrapped instance of SqlConnection to the L2S datacontext constructor overload that takes a db connection. In the wrapped connection you can replace the DbProviderFactory with your own custom DbProviderFactory-derived class that returns wrapped versions of SqlCommand etc.
E.g.:
//sample wrapped SqlConnection:
public class MySqlConnectionWrapper : SqlConnection
{
private SqlConnecction _sqlConn = null;
public MySqlConnectionWrapper(string connectString)
{
_sqlConn = new SqlConnection(connectString);
}
public override void Open()
{
_sqlConn.Open();
}
//TODO: override everything else and pass on to _sqlConn...
protected override DbProviderFactory DbProviderFactory
{
//todo: return wrapped provider factory...
}
}
When using:
using (SomeDataContext dc = new SomeDataContext(new MySqlConnectionWrapper("connect strng"))
{
var q = from x in dc.SomeTable select x;
//...etc...
}
That said, do you really want to go down that road? You'll need to be able to parse the SQL statements and queries generated by L2S in order to modify them properly. If you can instead modify the linq queries to append whatever you want to add to them, that is probably a better alternative.
Remember that Linq queries are composable, so you can add 'extras' in a separate method if you have something that you want to add to many queries.
first thing come to my mind is to modify the query and return the result in Non-LINQ format
//Get linq-query as datatable-schema
public DataTable ToDataTable(System.Data.Linq.DataContext ctx, object query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
IDbCommand cmd = ctx.GetCommand((IQueryable)query);
System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
adapter.SelectCommand = (System.Data.SqlClient.SqlCommand)cmd;
DataTable dt = new DataTable("sd");
try
{
cmd.Connection.Open();
adapter.FillSchema(dt, SchemaType.Source);
adapter.Fill(dt);
}
finally
{
cmd.Connection.Close();
}
return dt;
}
try to add your condition to the selectCommand and see if it helps.
Try setting up a view in the DB that applies the security filter to the records as needed, and then when retrieving records through L2S. This will ensure that the records that you need will not be returned.
Alternatively, add a .Where() to the query before it is submitted that will apply the security filter. This will allow you to apply the filter programmatically (in case it needs to change based on the scenario).