I am trying to enumerate the registry to fetch a list of installed applications and return them via Linq to a DataTable in C#.
I have tried various things including sanitizing it as XML (which worked), however it seemed rather inefficient as ultimately, i require this as DataTable object.
Here is where i am at currently:
//Warning
public static DataRow DataRowInstalledApplication (this RegistryKey rgkey, string keyName)
{
RegistryKey key = rgkey.OpenSubKey(keyName, false);
try
{
//Application Name is mandetory for a given key.
if (key == null|| key.RegToString("DisplayName", false) == null )return null;
//Build a sanitised data row
var rowBuilder = new DataTable().NewRow();
rowBuilder["DisplayName"] = key.RegToString("DisplayName");
rowBuilder["UninstallString"] = key.RegToString("UninstallString");
rowBuilder["InstallLocation"] = key.RegToString("InstallLocation");
rowBuilder["Publisher"] = key.RegToString("Publisher");
rowBuilder["DisplayIcon"] = key.RegToString("DisplayIcon");
return rowBuilder;
}
finally
{
if (key != null) key.Close();
}
}
Here is the method that contains the Linq:
public DataTable GetRegistryApplicationDataTable(RegistryKey registryKey, string tableName)
{
if (registryKey != null)
{
try
{
//change to throw non critical error
var installedListXml = new DataTable(tableName, from name in registryKey.GetSubKeyNames()
let app = registryKey.DataRowInstalledApplication(name)
select app);
return installedListXml;
}
catch
{
return new DataTable(tableName);
}
finally
{
registryKey.Close();
}
}
return null;
}
The main problem i have is that i do not really understand Linq very well. Mainly how individual values are used during the iteration to call other things similar to
foreach (string value in collection)
{
Somefunction(value);
}
, in the case of the registry values i am trying to retrieve, i do not understand how to make a Linq query pass each key name to function, which will generate a row and the Linq Query return as a data table.
I would be grateful of any pointers! thanks
ps i call the above with
private static readonly RegistryKey HKLMUninstallKey = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
GetRegistryApplicationDataTable(HKLMUninstallKey, "Computer")
and expect back a data table called computer.
Your first problem is your linq statement. You are trying to pass an IEnumerable into a constructor that is expecting either a (string) or a (string, string). If you modify your logic to this:
var query = from name in registryKey.GetSubKeyNames()
let app = registryKey.DataRowInstalledApplication(name)
select app;
foreach(var result in query)
{
var installedListXml = new DataTable(tableName, result);
}
That would work IF result was actually a string, but it's a DataRow, which is not a valid parameter for a DataTable constructor. Instead, there is an extension method you can call on the resulting query to copy to a data table, like so:
var query = from name in registryKey.GetSubKeyNames()
let app = registryKey.DataRowInstalledApplication(name)
select app;
var installedListXml = query.CopyToDataTable();
When you write a LINQ query statement, it is not executed until you try to enumerate over the results. CopyToDataTable does this, so if you were to step through your code you will notice that your function DataRowInstalledApplication will not be called until you call CopyToDataTable and not when you first assign query. The result you receive is an Enumerator that you can treat just like any other, whether that's using in a foreach loop or calling ToList or so forth. Inside the linq query itself, you are actually iterating over other values, in this case GetSubKeyNames. It would be functionally equivalent if you were to do this instead:
var dataRows = new List<DataRow>();
foreach(var name in registryKey.GetSubKeyNames())
{
dataRows.Add(registryKey.DataRowInstalledApplication(name));
}
return dataRows.CopyToDataTable();
Related
I am developing a C# .Net MVC application and trying to implement a generic search method for entity fields. As our pages are growing i don't want to code a search method each time a new page is added.
For that, i am using Dynamic.Core LINQ Queries : Check it at : https://dynamic-linq.net/basic-simple-query
the way i implemented it works the following way : at user input in the view, the app sends an ajax request to the controller telling it to search that specific value and then display the new list where the previous one.
The problem is : i could make it case insensitive but not accent insensitive and was wondering if anyone could help me with that.
Here is my code :
public static List<T> SearchEntityList<T>(this IQueryable<T> entityList, string searchBy, List<string> fieldsToCheck)
{
if (searchBy == null)
return entityList.ToList();
searchBy = searchBy.ToLower().RemoveDiacriticsUtil();
// Dynamic LINQ Library
string query = "";
foreach (string str in fieldsToCheck)
{
query += str + ".ToString().ToLower().Contains(#0) ||";
}
if (query != null)
{
// Removes the last "OR" inserted on the foreach here on top
query = query.Substring(0,query.Length - 3);
try
{
entityList = entityList.Where(query, searchBy);
}
catch (Exception e)
{
// query is wrong, list wont be filtered.
return entityList.ToList();
}
}
List<T> filteredList = entityList.ToList(); ;
return filteredList;
}
the method receives a list of string representing the fields to check, for example : "Username"
then a string query is built and checked with the database.
This code works as expected and is case insensitive, now i want to add accent insensitive to it.
i modify this line
query += str + ".ToString().ToLower().Contains(#0) ||";
with this one
query += str + "Collate(" + str + ".toString(), \"SQL_Latin1_General_CP1_CI_AI\").Contains(#0) ||";
and now i cannot make it work.
Got this error :
"No applicable method 'Collate' exists in type '...'"
I tested a lot of other stuff such as RemoveDiacritics, etc.. but they dont work with dynamic string linq queries...
Was wondering if anyone already had the same problem. Thanks !
You're on the right track.
You need to register your custom extension methods for dynamic linq before us use it:
// Registration for the default custom type handler
[DynamicLinqType]
public static class MyExtensions
{
// taken from: https://stackoverflow.com/questions/359827/ignoring-accented-letters-in-string-comparison/368850
public static string RemoveDiacritics(this string text)
{
return string.Concat(
text.Normalize(NormalizationForm.FormD)
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
UnicodeCategory.NonSpacingMark)
).Normalize(NormalizationForm.FormC);
}
}
public static class Entry
{
public static void Main(string[] args)
{
// your input
var query = "éè";
// clean it using your extension method
var cleanQuery = query.RemoveDiacritics();
// a test set for this demo
IQueryable<string> testSet = new EnumerableQuery<string>(new List<string>()
{
"soméè", "tèèst", "séét", "BBeeBB", "NoMatchHere"
});
var results = testSet.Where("it.RemoveDiacritics().Contains(#0)", cleanQuery);
Debug.Assert(results.Count() == 4);
}
}
I am getting following error while executing simple select statement in my custom code.
Could not find specified column in results
Here is my code-
string queryBuilder="select BASKET_DESCRIPTION from MARKET_BASKET_REQUESTS order by BASKET_DESCRIPTION limit 1";
public T SelectSingle<T>(string queryBuilder) where T : new()//made new
{
T result = new T();
TableScheme dbTable = GetTableSchemeFromType(typeof(T));
IDataReader reader = ExecuteReader(queryBuilder);
result = ParseDataReaderToEntityListtttt<T>(reader,dbTable);
reader.Close();
return result;
}
private T ParseDataReaderToEntityListtttt<T>(IDataReader reader, TableScheme dbTable) where T : new()
{
Type type = typeof(T);
T result = new T();
while (reader.Read())
{
T t = new T();
foreach (var column in dbTable.Columns)
{
type.GetProperty(column.AssociatedPropertyName).SetValue(t, reader[column.ColumnName], null);
}
result = t;
}
return result;
}
Your SELECT statement only selects the BASKET_DESCRIPTION column, while your TableScheme (which is generated by the not shown method GetTableSchemeFromType(), but I guess it uses Type.GetProperties() to get all properties) is requesting other columns by name which aren't present in the result set, which is what the error is trying to tell you.
So either SELECT * (or at least all relevant columns), or stop building your own ORM and use an existing one.
Now I'm using Dapper + Dapper.Extensions. And yes, it's easy and awesome. But I faced with a problem: Dapper.Extensions has only Insert command and not InsertUpdateOnDUplicateKey. I want to add such method but I don't see good way to do it:
I want to make this method generic like Insert
I can't get cached list of properties for particular type because I don't want to use reflection directly to build raw sql
Possible way here to fork it on github but I want to make it in my project only. Does anybody know how to extend it? I understand this feature ("insert ... update on duplicate key") is supported only in MySQL. But I can't find extension points in DapperExtensions to add this functionality outside.
Update: this is my fork https://github.com/MaximTkachenko/Dapper-Extensions/commits/master
This piece of code has helped me enormously in MySQL -related projects, I definitely owe you one.
I do a lot of database-related development on both MySQL and MS SQL. I also try to share as much code as possible between my projects.
MS SQL has no direct equivalent for "ON DUPLICATE KEY UPDATE", so I was previously unable to use this extension when working with MS SQL.
While migrating a web application (that leans heavily on this Dapper.Extensions tweak) from MySQL to MS SQL, I finally decided to do something about it.
This code uses the "IF EXISTS => UPDATE ELSE INSERT" approach that basically does the same as "ON DUPLICATE KEY UPDATE" on MySQL.
Please note: the snippet assumes that you are taking care of transactions outside this method. Alternatively you could append "BEGIN TRAN" to the beginning and "COMMIT" to the end of the generated sql string.
public static class SqlGeneratorExt
{
public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap, bool hasIdentityKeyWithValue = false)
{
var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || (p.KeyType == KeyType.Identity && !hasIdentityKeyWithValue))).ToList();
var keys = columns.Where(c => c.KeyType != KeyType.NotAKey).Select(p => $"{generator.GetColumnName(classMap, p, false)}=#{p.Name}");
var nonkeycolumns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey).ToList();
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var tablename = generator.GetTableName(classMap);
var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
var valuesSetters = nonkeycolumns.Select(p => $"{generator.GetColumnName(classMap, p, false)}=#{p.Name}").ToList();
var where = keys.AppendStrings(seperator: " and ");
var sqlbuilder = new StringBuilder();
sqlbuilder.AppendLine($"IF EXISTS (select * from {tablename} WITH (UPDLOCK, HOLDLOCK) WHERE ({where})) ");
sqlbuilder.AppendLine(valuesSetters.Any() ? $"UPDATE {tablename} SET {valuesSetters.AppendStrings()} WHERE ({where}) " : "SELECT 0 ");
sqlbuilder.AppendLine($"ELSE INSERT INTO {tablename} ({columnNames.AppendStrings()}) VALUES ({parameters.AppendStrings()}) ");
return sqlbuilder.ToString();
}
}
Actually I closed my pull request and remove my fork because:
I see some open pull requests created in 2014
I found a way "inject" my code in Dapper.Extensions.
I remind my problem: I want to create more generic queries for Dapper.Extensions. It means I need to have access to mapping cache for entities, SqlGenerator etc. So here is my way. I want to add ability to make INSERT .. UPDATE ON DUPLICATE KEY for MySQL. I created extension method for ISqlGenerator
public static class SqlGeneratorExt
{
public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap)
{
var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity));
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
var valuesSetters = columns.Select(p => string.Format("{0}=VALUES({1})", generator.GetColumnName(classMap, p, false), p.Name));
string sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2}) ON DUPLICATE KEY UPDATE {3}",
generator.GetTableName(classMap),
columnNames.AppendStrings(),
parameters.AppendStrings(),
valuesSetters.AppendStrings());
return sql;
}
}
One more extension method for IDapperImplementor
public static class DapperImplementorExt
{
public static void InsertUpdateOnDuplicateKey<T>(this IDapperImplementor implementor, IDbConnection connection, IEnumerable<T> entities, int? commandTimeout = null) where T : class
{
IClassMapper classMap = implementor.SqlGenerator.Configuration.GetMap<T>();
var properties = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
string emptyGuidString = Guid.Empty.ToString();
foreach (var e in entities)
{
foreach (var column in properties)
{
if (column.KeyType == KeyType.Guid)
{
object value = column.PropertyInfo.GetValue(e, null);
string stringValue = value.ToString();
if (!string.IsNullOrEmpty(stringValue) && stringValue != emptyGuidString)
{
continue;
}
Guid comb = implementor.SqlGenerator.Configuration.GetNextGuid();
column.PropertyInfo.SetValue(e, comb, null);
}
}
}
string sql = implementor.SqlGenerator.InsertUpdateOnDuplicateKey(classMap);
connection.Execute(sql, entities, null, commandTimeout, CommandType.Text);
}
}
Now I can create new class derived from Database class to use my own sql
public class Db : Database
{
private readonly IDapperImplementor _dapperIml;
public Db(IDbConnection connection, ISqlGenerator sqlGenerator) : base(connection, sqlGenerator)
{
_dapperIml = new DapperImplementor(sqlGenerator);
}
public void InsertUpdateOnDuplicateKey<T>(IEnumerable<T> entities, int? commandTimeout) where T : class
{
_dapperIml.InsertUpdateOnDuplicateKey(Connection, entities, commandTimeout);
}
}
Yeah, it's required to create another DapperImplementor instance because DapperImplementor instance from base class is private :(. So now I can use my Db class to call my own generic sql queries and native queries from Dapper.Extension. Examples of usage Database class instead of IDbConnection extensions can be found here.
I'm new to using Dynamic Objects in C#. I am reading a CSV file very similarly to the code found here: http://my.safaribooksonline.com/book/programming/csharp/9780321637208/csharp-4dot0-features/ch08lev1sec3
I can reference the data I need with a static name, however I can not find the correct syntax to reference using a dynamic name at run time.
For example I have:
var records = from r in myDynamicClass.Records select r;
foreach(dynamic rec in records)
{
Console.WriteLine(rec.SomeColumn);
}
And this works fine if you know the "SomeColumn" name. I would prefer to have a column name a a string and be able to make the same type refrence at run time.
Since one has to create the class which inherits from DynamicObject, simply add an indexer to the class to achieve one's result via strings.
The following example uses the same properties found in the book example, the properties which holds the individual line data that has the column names. Below is the indexer on that class to achieve the result:
public class myDynamicClassDataLine : System.Dynamic.DynamicObject
{
string[] _lineContent; // Actual line data
List<string> _headers; // Associated headers (properties)
public string this[string indexer]
{
get
{
string result = string.Empty;
int index = _headers.IndexOf(indexer);
if (index >= 0 && index < _lineContent.Length)
result = _lineContent[index];
return result;
}
}
}
Then access the data such as
var csv =
#",,SomeColumn,,,
ab,cd,ef,,,"; // Ef is the "SomeColumn"
var data = new myDynamicClass(csv); // This holds multiple myDynamicClassDataLine items
Console.WriteLine (data.OfType<dynamic>().First()["SomeColumn"]); // "ef" is the output.
You will need to use reflection. To get the names you would use:
List<string> columnNames = new List<string>(records.GetType().GetProperties().Select(i => i.Name));
You can then loop through your results and output the values for each column like so:
foreach(dynamic rec in records)
{
foreach (string prop in columnNames)
Console.Write(rec.GetType().GetProperty (prop).GetValue (rec, null));
}
Try this
string column = "SomeColumn";
var result = rec.GetType().GetProperty (column).GetValue (rec, null);
I am trying to create a method that converts a regular sql statement to c# objects, So i decided to use Irony to parse the sql statement then i return the statement as an Action that contains the type of the statement and the values of it depending on the type
Here is my non completed code [ Because i got frustrated as i don't know what to do then ]
private List<Action> ParseStatement(string statement)
{
var parser = new Parser(new SqlGrammar());
var parsed = parser.Parse(statement);
var status = parsed.Status;
while (parsed.Status == ParseTreeStatus.Parsing)
{
Task.Yield();
}
if (status == ParseTreeStatus.Error)
throw new ArgumentException("The statement cannot be parsed.");
ParseTreeNode parsedStmt = parsed.Root.ChildNodes[0];
switch (parsedStmt.Term.Name)
{
case "insertStmt":
var table = parsedStmt.ChildNodes.Find(x => x.Term.Name == "Id").ChildNodes[0].Token.ValueString;
var valuesCount =
parsedStmt.ChildNodes.Find(x => x.Term.Name == "insertData").ChildNodes.Find(
x => x.Term.Name == "exprList").ChildNodes.Count;
var values = parsedStmt.ChildNodes.Find(x => x.Term.Name == "insertData").ChildNodes.Find(
x => x.Term.Name == "exprList").ChildNodes;
foreach (var value in values)
{
string type = value.Token.Terminal.Name;
}
break;
}
return null;
}
private Type ParseType(string type)
{
switch (type)
{
case "number":
return typeof (int);
case "string":
return typeof (string);
}
return null;
}
So the Question Here is : How could i make use of Irony to convert a string SQL Statement to a c# objects ?
Here is an example of what i want to achieve :
INSERT INTO Persons VALUES (4,'Nilsen', 'Johan', 'Bakken 2',
'Stavanger')
And get it converted to
return new Action<string type, string table, int val1, string val2, string val3, string val4, string val5>;
Dynamically depending on what the method have read from the statement.
I hope i have well explained my idea so you can help me guys, And if there is something unclear please tell me and i will try to explain it.
I was trying to parse SQL with Irony as well. I gave up because the sample SQL parser in Irony don't handle: CTEs, Order by column number, half the special statements like
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
While I had a great time learning about Irony, I don't have the coding chops to implement all the aforementioned parts correctly.
I ended up using the Microsoft-provided SQL parsing library. Sample code for LINQPad 5 below:
// Add a reference to
// C:\Program Files (x86)\Microsoft SQL Server\130\SDK\Assemblies\
// Microsoft.SqlServer.TransactSql.ScriptDom.dll
//
// https://blogs.msdn.microsoft.com/gertd/2008/08/21/getting-to-the-crown-jewels/
public void Main()
{
var sqlFilePath = #"C:\Users\Colin\Documents\Vonigo\database-scripts\Client\Estimate\spClient_EstimateAddNew.sql";
bool fQuotedIdenfifiers = false;
var parser = new TSql100Parser(fQuotedIdenfifiers);
string inputScript = File.ReadAllText(sqlFilePath);
IList<ParseError> errors;
using (StringReader sr = new StringReader(inputScript))
{
var fragment = parser.Parse(sr, out errors);
fragment.Dump();
}
}
If you are not doing this as a fun exercise I would recommend using Linq to SQL to generate your stub classes or Entity Framework as Drunken Code Monkey mentioned in the comments.
Here's a good article to get you started: Generating EF code from existing DB