DbExtensions - How to create WHERE clause with OR conditions? - ado.net

I'm trying to create WHERE clause with OR conditions using DbExtensions.
I'm trying to generate SQL statement which looks like
SELECT ID, NAME
FROM EMPLOYEE
WHERE ID = 100 OR NAME = 'TEST'
My C# code is
var sql = SQL.SELECT("ID, FIRSTNAME")
.FROM("EMPLOYEE")
.WHERE("ID = {0}", 10)
.WHERE("NAME = {0}", "TEST");
How do I get the OR seperator using the above mentioned DbExtensions library?

I have found a definition for logical OR operator here
public SqlBuilder _OR<T>(IEnumerable<T> items, string itemFormat, Func<T, object[]> parametersFactory) {
return _ForEach(items, "({0})", itemFormat, " OR ", parametersFactory);
}
And some code examples here
public SqlBuilder Or() {
int[][] parameters = { new[] { 1, 2 }, new[] { 3, 4} };
return SQL
.SELECT("p.ProductID, p.ProductName")
.FROM("Products p")
.WHERE()
._OR(parameters, "(p.CategoryID = {0} AND p.SupplierID = {1})", p => new object[] { p[0], p[1] })
.ORDER_BY("p.ProductName, p.ProductID DESC");
}
I think (by analogy with example) in your case code should be something like this (but I can't test it for sure):
var params = new string[] { "TEST" };
var sql = SQL.SELECT("ID, FIRSTNAME")
.FROM("EMPLOYEE")
.WHERE("ID = {0}", 10)
._OR(params, "NAME = {0}", p => new object[] { p })
Hope this helps :)
By the way... have you tried this way?
var sql = SQL.SELECT("ID, FIRSTNAME")
.FROM("EMPLOYEE")
.WHERE(string.Format("ID = {0} OR NAME = '{1}'", 10, "TEST"))

It's simpler than you think:
var sql = SQL
.SELECT("ID, FIRSTNAME")
.FROM("EMPLOYEE")
.WHERE("(ID = {0} OR NAME = {1})", 10, "TEST");

One can use:
.AppendClause("OR", ",", "NAME = {0}",new object[]{"TEST"});

Related

How to loop through dbcontext all dbset in Entity Framework Core to get count?

I have 20 Dbsets in my context which I want to get the row count for each dbset to make sure all dbset row count is 0. To get the count for one dbset, this is my code:
var person = context.Persons.Count();
Is there a way to loop through the context, get the count for each dbset dynamically?
There is solution. Usage is simple:
var tablesinfo = ctx.GetTablesInfo();
if (tablesinfo != null)
{
var withRecords = tablesinfo
.IgnoreQueryFilters()
.Where(ti => ti.RecordCount > 0)
.ToArray();
}
Extension returns IQueryable<TableInfo> and you can reuse this query later. Probably you will need to filter out Views, but I think you can handle that. Note that IgnoreQueryFilters can be important if you have Global Query Filters defined.
What extension do:
It scans Model for entity types registered for particular DbContext and generates big Concat of Count queries. Here we have to do that via grouping by constant value.
Schematically it will generate the following LINQ query:
var tablesinfo =
ctx.Set<Entity1>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity1", RecordCount = g.Count()})
.Concat(ctx.Set<Entity2>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity2", RecordCount = g.Count()}))
.Concat(ctx.Set<Entity3>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity3", RecordCount = g.Count()}))
...
Which wll be converted to the following SQL:
SELECT "Entity1" AS TableName, COUNT(*) AS RecordCount FROM Entity1
UNION ALL
SELECT "Entity2" AS TableName, COUNT(*) AS RecordCount FROM Entity2
UNION ALL
SELECT "Entity3" AS TableName, COUNT(*) AS RecordCount FROM Entity3
...
Implementation:
public static class QueryableExtensions
{
public class TableInfo
{
public string TableName { get; set; } = null!;
public int RecordCount { get; set; }
}
public static IQueryable<TableInfo> GetTablesInfo(this DbContext ctx)
{
Expression query = null;
IQueryProvider provider = null;
var ctxConst = Expression.Constant(ctx);
var groupingKey = Expression.Constant(1);
// gathering information for MemberInit creation
var newExpression = Expression.New(typeof(TableInfo).GetConstructor(Type.EmptyTypes));
var tableNameProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.TableName));
var recordCountProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.RecordCount));
foreach (var entityType in ctx.Model.GetEntityTypes())
{
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var tableName = entityType.GetTableName();
// ctx.Set<entityType>()
var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});
// here we initialize IQueryProvider, which is needed for creating final query
provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;
// grouping paraneter has generic type, we have to specify it
var groupingParameter = Expression.Parameter(typeof(IGrouping<,>).MakeGenericType(typeof(int), entityParam.Type), "g");
// g => new TableInfo { TableName = "tableName", RecordCount = g.Count() }
var selector = Expression.MemberInit(newExpression,
Expression.Bind(tableNameProperty, Expression.Constant(tableName)),
Expression.Bind(recordCountProperty,
Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] {entityParam.Type}, groupingParameter)));
// ctx.Set<entityType>.GroupBy(e => 1)
var groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[]
{
entityParam.Type,
typeof(int)
},
setQuery,
Expression.Lambda(groupingKey, entityParam)
);
// ctx.Set<entityType>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "tableName", RecordCount = g.Count()}))
groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] {groupingParameter.Type, typeof(TableInfo)},
groupByCall,
Expression.Lambda(selector, groupingParameter));
// generate Concat if needed
if (query != null)
query = Expression.Call(typeof(Queryable), nameof(Queryable.Concat), new[] {typeof(TableInfo)}, query,
groupByCall);
else
query = groupByCall;
}
// unusual situation, but Model can have no registered entities
if (query == null)
return null;
return provider.CreateQuery<TableInfo>(query);
}
}

LINQ to Entity cannot use System.Object.GetValue

Below find a method that does not work. We fail on the line query.Select(...
Below that find a method with hard coded object property names which does work. But, this method is obviously not dynamic, nor flexible. There may be many properties of a Customer I may wish to search on.
The error string is at bottom. I get it that somehow the LINQ to Entity is unable to deal with conversion of GetValue to some sort of TSQL. Would anyone know of how I might code this up?
public List<Customer> GetForQuery(params Tuple<string, string>[] keyValuePairs) {
using (var db = new DBEntities()) {
var availableProperties = typeof(Customer).GetTypeInfo().DeclaredProperties.ToList();
var query = db.Customers.Select(c => c);
foreach (Tuple<string, string> pair in keyValuePairs) {
PropertyInfo pi = availableProperties.First(p => p.Name.Equals(pair.Item1));
if (pi == null)
continue;
query = query.Where(u => pi.GetValue(u, null).ToString().StartsWith(pair.Item2));
}
var results = query.Select(c => c).ToList();
return results;
}
}
How I might call the above:
CustomerController custController = new CustomerController();
List<Customer> results = custController.GetForQuery(Tuple.Create<string, string>("FName", "Bob" ));
The working fixed method:
public List<Customer> GetForQuery(string firstName = "", string lastName = "", string phoneNumber = "") {
using (var db = new DBEntities()) {
var query = db.Customers.Select(c => c);
if (firstName.HasContent())
query = query.Where(u => u.FName.StartsWith(firstName));
if (lastName.HasContent())
query = query.Where(u => u.LName.StartsWith(lastName));
if (phoneNumber.HasContent())
query = query.Where(u => u.EveningPhone.StartsWith(phoneNumber));
var results = query.Select(c => c).ToList();
return results;
}
}
ERROR:
LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object, System.Object[])' method, and this method cannot be translated into a store expression.

Importing DBF file in SQL Server 2012

I have over 200+ dbf files having different schema. I have used SSIS to import .dbf files, but in order to automate the task I want to use OpenRowset. OpenRowSet is working fine for Excel Files, but not for .dbf.
I have written
SELECT [LRSNum],[AppUpdD],[AppStat],[PIN] FROM OPENROWSET('MICROSOFT.ACE.OLEDB.12.0','dBASE 5.0;Database=D:\Sales- data\SalesSourcefile\2016\December\Shape\Martin\real_land\', 'SELECT [LRSNum],[AppUpdD],[AppStat],[PIN] FROM real_land.dbf');
Any help will be appreciated.
I am using SQL Server 2012, Windows 8.1.
Installed Foxpro driver, but when selected foxpro using DTS, it fails.
FYI - You can do automated uploads of DBFs to SQL Server using SQL Server Upsizing Wizard for FoxPro.
With this tool, you have to do the uploading from FoxPro, and the DBFs must be attached to a FoxPro DBC.
http://www.codemag.com/article/0703052
The latest version if available from the VFPx site.
Finally, this is working
SELECT * FROM OPENROWSET ('MICROSOFT.ACE.OLEDB.12.0','dBase 5.0;HDR=YES;IMEX=2;DATABASE=\Dbf Directory\',
'SELECT * FROM dbf_filename.dbf')
I once had to do this too and wrote some C# code for the import.
Just as a try: With this line I open the connection within C#
var con = new OdbcConnection("Driver={{Microsoft dBASE Driver (*.dbf)}};Dbq=C:\\SomePath;DriverID=277;");
Might be, that you can get something out of this.
Some C# code
The following code is taken from one of my C# projects. I modified it to be neutral, but I cannot guarantee, that this is working right so:
public List<string> Ambus;
public List<string> Tbls;
public List<string> errList;
public List<string> SQLs;
private void btnImport_Click(object sender, EventArgs e) {
SqlConnection sqlcon;
SqlCommand sqlcmd;
SQLs.Clear();
Tbls.Clear();
var con = new OdbcConnection("Driver={{Microsoft dBASE Driver (*.dbf)}};Dbq=C:\\SomePath;DriverID=277;");
con.Open();
var tbls = con.GetSchema(OdbcMetaDataCollectionNames.Tables);
foreach (System.Data.DataRow r in tbls.Rows) {
Tbls.Add(r["TABLE_NAME"].ToString());
}
DataTable cols = null;
var sb = new StringBuilder();
int i = 0;
foreach (var tblnm in Tbls) {
i++;
sb.Clear();
try {
cols = con.GetSchema(OdbcMetaDataCollectionNames.Columns, new string[] { null, null, tblnm, null });
sb.AppendFormat(" CREATE TABLE dbo.[{0}](TableName VARCHAR(100) NOT NULL ", tblnm);
int count = 0;
foreach (DataRow colrow in cols.Rows) {
var colInf = string.Format(" ,{0} {1} NULL", colrow["COLUMN_NAME"].ToString(), this.createDataType(colrow["TYPE_NAME"].ToString(), colrow["COLUMN_SIZE"].ToString(), colrow["DECIMAL_DIGITS"].ToString(), colrow["NUM_PREC_RADIX"].ToString()));
sb.Append(colInf);
count++;
}
sb.Append("); ");
SQLs.Add(sb.ToString());
sb.Clear();
var cmd = new OdbcCommand("SELECT * FROM [" + tblnm + "]", con);
var reader = cmd.ExecuteReader();
while (reader.Read()) {
var vals = createVals(cols, reader, tblnm);
string insStat = string.Format(" INSERT INTO dbo.[{0}] VALUES ('{0}',{1});", tblnm, vals);
SQLs.Add(insStat);
}
}
catch (Exception exo) {
errList.Add(string.Format("{0}:{1}", tblnm, exo.Message));
}
con.Close();
sqlcon = new SqlConnection("Data Source=SomeSQLServer;Initial Catalog=master;User ID=sa;pwd=SomePwd");
sqlcon.Open();
sqlcmd = new SqlCommand("USE SomeTargetDB;", sqlcon);
sqlcmd.ExecuteNonQuery();
i = 0;
foreach (string s in SQLs) {
i++;
//Progress-output: this.Text = string.Format("{0} von {1}", i, SQLs.Count);
sqlcmd = new SqlCommand(s, sqlcon);
sqlcmd.ExecuteNonQuery();
}
sqlcon.Close();
}
}
private string createDataType(string typ, string size, string dec, string prec) {
switch (typ.ToLower()) {
case "char":
return "NVARCHAR(" + size + ")";
case "logical":
return "BIT";
case "numeric":
dec = dec == string.Empty ? null : dec;
prec = prec == string.Empty ? null : prec;
int d = int.Parse(dec ?? "0");
int p = int.Parse(prec ?? "0");
if (d == 0 && p == 0)
return "INT";
else if (d > 0 && p > 0)
return string.Format("DECIMAL({0},{1})", d, p);
else if (d == 0 && p > 0)
return "FLOAT";
else
return null;
case "date":
return "DATETIME";
default:
return null;
}
}
private string createVals(DataTable cols, OdbcDataReader reader, string tblnm) {
var sb = new StringBuilder();
sb.Append("'" + tblnm + "'");
foreach (DataRow colrow in cols.Rows) {
var val = string.Empty;
try {
val = reader[colrow["COLUMN_NAME"].ToString()].ToString();
}
catch { }
if (val.Trim().Length == 0)
val = "NULL";
else {
if (colrow["TYPE_NAME"].ToString().ToLower() == "char")
val = val.Replace("'", "''");
if (colrow["TYPE_NAME"].ToString().ToLower() == "numeric")
val = val.Replace(".", "").Replace(",", ".");
if (colrow["TYPE_NAME"].ToString().ToLower() == "date") {
var d = DateTime.Parse(val, System.Globalization.CultureInfo.CurrentCulture);
if (d.Year < 1900 || d.Year > 2020)
d = new DateTime(1900, d.Month, d.Day, d.Hour, d.Minute, d.Second);
val = d.ToString("dd.MM.yyyy HH:mm:ss");
}
val = "'" + val + "'";
}
sb.AppendFormat(",{0}", val);
}
return sb.ToString();
}

GroupBy Clause: To be able to pass in a Column name as a string

In EF, Order By allows a column name to be used to eg OrderBy("Description")
I need to be able to do some thing similar with GroupBy
Other posts have solutions when the column type is known
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
// the Expression function
private static Expression<Func<TEntity,Guid?>> GetGroupByExpressionGuidNull<TEntity>(string property)
{
var item = Expression.Parameter(typeof(TEntity), "gb");
var itemProperty = Expression.PropertyOrField(item, property);
var lambda = Expression.Lambda<Func<TEntity, Guid?>>(itemProperty, item);
return lambda;
}
But my users may select 1 of any columns by which to group by
So how can I make the function above return an expression for group by
I have tried this:
public static Expression<Func<T, object>> GetMember<T>(string memberName)// where T : EntityObject
{
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
System.Reflection.PropertyInfo pi = typeof(T).GetProperty(memberName);
return (Expression<Func<T, object>>)Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(pe, pi), typeof(object)), pe);
}
but it produces : p =>Convert(p.PersonUID)
instead of:p =>p.PersonUID
Regards
GregJF
After a bit more testing (and a good night's sleep) I got the second method to work (Thanks to JA Rreyes )
My issue was that I was using this:
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
I should have being doing this:
var groupByExpression2 = GetMember<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2.Compile())
You can use the second method in my original post( GetMember ), but I use this method('cos I like it!): Thanks: Taher Rahgooy
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "gb");
var property = Expression.Property(arg, propertyName);
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
Regards
GregJF

Linq to Nhibernate with Contains and SubString does not work?

There appears to be mismatch betweeen the SQL generated by NHibernate and SQL expected by SQL2008 in the following case:
public void PersistPerson()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using(var transaction = session.BeginTransaction())
{
session.Save(new Person {FirstName = "Foo", LastName = "Bar"});
session.Save(new Person {FirstName = "Foo", LastName = "Dah"});
session.Save(new Person {FirstName = "Foo", LastName = "Wah"});
transaction.Commit();
}
}
using (var session = sessionFactory.OpenSession())
{
using(var transaction = session.BeginTransaction())
{
var queryable = from p in session.Query<Person>() select p;
var lastNames = new[]{"B", "D"};
var result = queryable.Where(r => lastNames.Contains(r.LastName.Substring(0, 1))).ToList();
transaction.Commit();
Assert.That(result[0].LastName, Is.EqualTo("Bar"));
}
}
}
The resulting sql query generated by NHibernate for
var result = queryable.Where(r => lastNames.Contains(r.LastName.Substring(0, 1))).ToList();
is:
select person0_.Id as Id0_,
person0_.FirstName as FirstName0_,
person0_.LastName as LastName0_ from [Person] person0_ where upper(substring(person0_.LastName,
0 /* #p0 */,
1 /* #p1 */)) in ('B' /* #p2 */)
From the MSDN documentation for T-SQL SUBSTRING
http://msdn.microsoft.com/en-us/library/ms187748.aspx
SUBSTRING (value_expression ,start_expression ,length_expression )
although the documentation says otherwise, from the comments posted start_expression appears to be 1 - based (not 0 indexed)
For example:
SQL: SELECT x = SUBSTRING('abcdef', 0, 3);
RESULT: x = 'ab'
and NOT x = 'abc'
Any thoughts on how I can get around this ?
I think it's a bug. just change your codes to r.LastName.Substring(1, 1) and it works (resulting sql will be substring(1,1)).