How can you connect to a ProgressDB data provider with Dapper? - c#

Please read the comments of the answer for a more complete understanding of what the problem is/was
First, I read through a lot of the other SO questions related to this and still can't get this to work with a basic setup. Here is the related question I have already read:
Passing query parameters in Dapper using OleDb
EDIT: The troubleshooting below is somewhat misleading. The only thing that was going wrong was the query syntax from the Github example was not valid using the ProgressDB OpenEdge driver.
The problem with that question's answer and with the example given in the documented Git examples is that a true ODBC object is not being used, but rather an OleDbConnection object. This causes problems with the scenario where I am trying to use Dapper. Some background and restrictions to my scenario:
I cannot change the DB technology, we are connecting to an Progress DB. The connection string to connect to the DB: connectionString="PROVIDER=MSDASQL;DRIVER={Progress OpenEdge 10.2A Driver};HOST=...;PORT=...;DB=mfgsys;UID=...;PWD=...;DIL=READ UNCOMMITTED" Notice the Provider: MSDASQL
According to MSDN, https://msdn.microsoft.com/en-us/library/a6cd7c08%28v=vs.110%29.aspx - "The .NET Framework Data Provider for OLE DB does not work with the OLE DB provider for ODBC (MSDASQL). To access an ODBC data source using ADO.NET, use the .NET Framework Data Provider for ODBC."
When I attempt to use the OdbcConnection object with Dapper I get the following error: "System.Data.Odbc.OdbcException : ERROR [HY000] [DataDirect][ODBC Progress OpenEdge Wire Protocol driver][OPENEDGE]Syntax error in SQL statement at or about "= ?, Age = ?" (10713)"
I am using the exact same query syntax as the other SO question:
var row = _odbcConn.Query("select Id = ?, Age = ?", new DynamicParameters(new{foo = 12, bar = 23}) {RemoveUnused = false}).Single();
I also removed the DynamicParameters object and attempted with a dynamic object with same result:
var row = _odbcConn.Query("select Id = ?, Age = ?", new{foo = 12, bar = 23}).Single();
Is there a way to accomplish this simple query using an OdbcConnection object? Or does this really have more to do with the specific Progress driver we are using and as such precludes using Dapper?
Edit
Including working ADO.Net code per requests below, the Build.FromReader<EmployeeDataModel>(reader) just loops through the reader and maps the columns with hard coding and is confirmed to work:
public class EmployeeRepository : IEmployeeRepository
{
private readonly OdbcConnection _sqlConn = new OdbcConnection();
public EmployeeRepository() : this(ConfigurationManager.ConnectionStrings["TCI_Epicor"].ConnectionString) { }
public EmployeeRepository(string connString)
{
_sqlConn.ConnectionString = connString;
}
public EmployeeDataModel GetById(string id)
{
try
{
_sqlConn.Open();
using (OdbcCommand command = new OdbcCommand())
{
command.Connection = _sqlConn;
command.CommandType = CommandType.Text;
command.CommandText = GetEmployeeDataQuery();
command.Parameters.Add("empID", OdbcType.NVarChar);
command.Parameters["empID"].Value = id;
var reader = command.ExecuteReader();
return Build.FromReader<EmployeeDataModel>(reader);
}
}
catch
{
return new EmployeeDataModel();
}
finally
{
_sqlConn.Close();
}
}
private string GetEmployeeDataQuery()
{
var sb = new StringBuilder();
sb.AppendLine("SELECT EmpID as 'EmployeeID',");
sb.AppendLine(" FirstName + ' ' + LastName as 'EmployeeName'");
sb.AppendLine(" FROM MFGSYS.PUB.EmpBasic");
sb.AppendLine(" WHERE EmpID = ?");
return sb.ToString();
}
}

If the problem is using anonymous (?) parameters, then:
var row = _odbcConn.Query(
"select Id = ?foo?, Age = ?bar?", new { foo = 12, bar = 23 }
).Single();
Dapper will rewrite that as per your original query, but will know which parameter to put where.
However, if the problem is that the ODBC provider does not support parameters: I can't help much with that :( If you can show how to do it in working ADO.NET code, I can probably show you how to do it easier via dapper.

Related

Connection to snowflake database using C#

I'm new to snowflake database. I need to read the data from multiple tables and do data mapping like sql tables.
I'm trying to get the data from table using the below code:
`
using (IDbConnection snowflakedb = new SnowflakeDbConnection())
{
snowflakedb.ConnectionString = ConfigurationManager.ConnectionStrings["SnowFlakeParserConnection"].ConnectionString;
snowflakedb.Open();
var cmd = snowflakedb.CreateCommand();
cmd.CommandText = "select * from EMAILPARSER_TABLE;";
var reader = cmd.ExecuteReader();
dynamic accountList;
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
employeeList = reader.GetString(0);
}
snowflakedb.Close();
}
But its giving only single value- value of first row first column .How to read the entire row of the table? Later I also need to merge the data of two tables.
Also, When do we use snowflake ODBC driver connection:
var defaultConnection = _connectionFactory.GetOdbcConnection;
var snowflakeConnection = new OdbcConnection(defaultConnection.ConnectionString);
As #stuartd says, you are getting one column because that is all your code is requesting. You may want to try something like this code from the Github repo
// All types except TIME fail conversion when calling GetTimeSpan
for (int i = 0; i < 12; i++)
{
try
{
((SnowflakeDbDataReader)reader).GetTimeSpan(i);
Assert.Fail("Data should not be converted to TIME");
}
catch (SnowflakeDbException e)
{
Assert.AreEqual(270003, e.ErrorCode);
}
}
Also see this Stack Overflow answer for another approach using a data set.
As for when you should use the ODBC connection, consider using it as you would make that decision for any other database. One thing that has been noted before by other Snowflake developers is that the ODBC driver seems to be more strongly supported than the .Net driver.

Truncate table in entity framework core

How can I truncate a certain table with C# code, not SQL query?
I want the equivalent of TRUNCATE TABLE <table_name>
So far I've tried this:
context.Products.RemoveRange(context.Products);
however, it doesnt do anything
You can't, that's not what an ORM is for.
An ORM helps you access the database in terms of object access. Truncating a table is a data layer operation, not an object operation. The equivalent in Entity Framework would be to load all objects from the database and delete them one by one.
You don't want that, you want to truncate the table. Then dive down into SQL and truncate that table.
Surely there are extensions on Entity Framework that allow things like this, but in the end they will generate exactly the SQL you're preventing to execute, so why not simply do that yourself?
I have done things as below, for EF Core 5.0.11
This is working for SQL Server, Oracle, PostgreSQL
public class AnnotationHelper
{
/*
* https://stackoverflow.com/questions/45667126/how-to-get-table-name-of-mapped-entity-in-entity-framework-core
*/
private static string GetName(IEntityType entityType, string defaultSchemaName = "dbo")
{
//var schemaName = entityType.GetSchema();
//var tableName = entityType.GetTableName();
var schema = entityType.FindAnnotation("Relational:Schema").Value;
string tableName = entityType.GetAnnotation("Relational:TableName").Value.ToString();
string schemaName = schema == null ? defaultSchemaName : schema.ToString();
string name = string.Format("[{0}].[{1}]", schemaName, tableName);
return name;
}
public static string TableName<T>(DbSet<T> dbSet) where T : class
{
var entityType = dbSet.EntityType;
return GetName(entityType);
}
}
public static class EfHelper
{
/*
* need to install Microsoft.EntityFrameworkCore.Relational
*/
public static string Truncate<T>(this DbSet<T> dbSet) where T : class
{
var context = dbSet.GetService<ICurrentDbContext>().Context;
string cmd = $"TRUNCATE TABLE {AnnotationHelper.TableName(dbSet)}";
using (var command = context.Database.GetDbConnection().CreateCommand())
{
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
command.CommandText = cmd;
command.ExecuteNonQuery();
}
return cmd;
}
}
[Test]
public void Truncate()
{
Db.Users.Add(new User()
{
Name = "Name",
Email = "Email",
CreatedBy = "CreatedBy",
CreatedOn = DateTime.Now
});
Db.SaveChanges();
Assert.GreaterOrEqual(Db.Users.ToList().Count, 1);
/*Truncate table*/
Db.Users.Truncate();
Assert.AreEqual(0, Db.Users.ToList().Count);
}
re: extensions as mentioned by CodeCaster: If you are allowed to use NUGET packages in your code, you might find this library useful; it has 'Truncate' and 'TruncateAsync' exactly as you've asked about.
https://github.com/borisdj/EFCore.BulkExtensions
Yes, behind the scenes it is still using 'ExecuteSqlRaw', but using a c# method allows better error handling. For example, in some cases database security may not allow table truncation by the executing thread, so if that's important to your application you can handle it easier with a wrapper.

Error from SQL query

Currently I'm working on cleaning up some code on the backend of an application I'm contracted for maintenance to. I ran across a method where a call is being made to the DB via Oracle Data Reader. After examining the SQL, I realized it was not necessary to make the call to open up Oracle Data Reader seeing how the object being loaded up was already within the Context of our Entity Framework. I changed the code to follow use of the Entity Model instead. Below are the changes I made.
Original code
var POCs = new List<TBLPOC>();
Context.Database.Connection.Open();
var cmd = (OracleCommand)Context.Database.Connection.CreateCommand();
OracleDataReader reader;
var SQL = string.Empty;
if (IsAssociate == 0)
SQL = #"SELECT tblPOC.cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC AND
tblParcelToPOC.cntAsOf = 0 AND
tblParcelToPOC.cntParcel = " + cntParcel + " ORDER BY INITCAP(strLastName)";
else
SQL = #"SELECT cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC
WHERE tblPOC.cntPOC NOT IN ( SELECT cntPOC
FROM tblParcelToPOC
WHERE cntParcel = " + cntParcel + #"
AND cntAsOf = 0 )
AND tblPOC.ysnActive = 1 ORDER BY INITCAP(strLastName)";
cmd.CommandText = SQL;
cmd.CommandType = CommandType.Text;
using (reader = cmd.ExecuteReader())
{
while (reader.Read())
{
POCs.Add(new TBLPOC { CNTPOC = (decimal)reader[0],
STRLASTNAME = reader[1].ToString(),
STRFIRSTNAME = reader[2].ToString() });
}
}
Context.Database.Connection.Close();
return POCs;
Replacement code
var sql = string.Empty;
if (IsAssociate == 0)
sql = string.Format(#"SELECT tblPOC.cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC
AND tblParcelToPOC.cntAsOf = 0
AND tblParcelToPOC.cntParcel = {0}
ORDER BY INITCAP(strLastName)",
cntParcel);
else
sql = string.Format(#"SELECT cntPOC,INITCAP(strLastName), INITCAP(strFirstName)
FROM tblPOC
WHERE tblPOC.cntPOC NOT IN (SELECT cntPOC
FROM tblParcelToPOC
WHERE cntParcel = {0}
AND cntAsOf = 0)
AND tblPOC.ysnActive = 1
ORDER BY INITCAP(strLastName)",
cntParcel);
return Context.Database.SqlQuery<TBLPOC>(sql, "0").ToList<TBLPOC>();
The issue I'm having right now is when the replacement code is executed, I get the following error:
The data reader is incompatible with the specified 'TBLPOC'. A member of the type 'CNTPOCORGANIZATION', does not have a corresponding column in the data reader with the same name.
The field cntPOCOrganization does exist within tblPOC, as well as within the TBLPOC Entity. cntPOCOrganization is a nullable decimal (don't ask why decimal, I myself don't get why the previous contractors used decimals versus ints for identifiers...). However, in the past code and the newer code, there is no need to fill that field. I'm confused on why it is errors out on that particular field.
If anyone has any insight, I would truly appreciate it. Thanks.
EDIT: So after thinking on it a bit more and doing some research, I think I know what the issue is. In the Entity Model for TBLPOC, the cntPOCOrganization field is null, however, there is an object tied to this Entity Model called TBLPOCORGANIZATION. I'm pondering if it's trying to fill it. It too has cntPOCOrganization within itself and I'm guessing that maybe it is trying to fill itself and is what is causing the issue.
That maybe possibly why the previous contractor wrote the Oracle Command versus run it through the Entity Framework. I'm going to revert back for time being (on a deadline and really don't want to play too long with it). Thanks!
This error is issued when your EF entity model does not match the query result. If you post your entity model you are trying to fetch this in, the SQL can be fixed. In general you need to use:
sql = string.Format(#"SELECT tblPOC.cntPOC AS <your_EF_model_property_name_here>,INITCAP(strLastName) AS <your_EF_model_property_name_here>,INITCAP(strFirstName) AS <your_EF_model_property_name_here>
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC
AND tblParcelToPOC.cntAsOf = 0
AND tblParcelToPOC.cntParcel = {0}
ORDER BY INITCAP(strLastName)",
cntParcel);

Open a connection to DB using EF to execute adhoc query

I'm an asp.net guy hard-core and EF + MVC = ME + WTF.
I have a task to open a connection to each of our production database servers. Run a simple query "Select top 1 * from t1 where f1 > 1".
I thought I could just use system.data.sqlclient build the conn string, open the conn and execute a query.
That doesn't seem to be working. Each connection takes forever.
How would I get an instance of our db object to do this with EF. I've seen tons of dbcontext examples but I don't even know how to get that and what it is.
I need to connect to 20 seperate DB1,TBL1,FLD1 and execute the query above. If they all succeed return an int 200 to an MVC view if even one fails just return a 503.
Thanks!
You can get a reference to the underlying DbConnection in your EF using Database.Connection.
For example:
var dbConn = context.Database.Connection;
var cmd = dbConn.CreateCommand();
cmd.CommandText = "SELECT TOP 1 * from t1 WHERE f1 > 1";
var results = cmd.ExecuteReader();
More on Raw SQL Queries with Entity Framework
From Microsoft doc - Sending Raw Commands To The Database
A- If you do not know the entity
using (var context = new BloggingContext())
{
var blogNames = context.Database.SqlQuery<string>(
"SELECT Name FROM dbo.Blogs").ToList();
}
B- If you know the entity
using (var context = new BloggingContext())
{
var blogs = context.Blogs.SqlQuery("SELECT * FROM dbo.Blogs").ToList();
}

Inserting into MS Access DB safely via ADO.net

I'm really new to DB programming, and I'm throwing together a little test project that uses ado.net to interact with a MS Access database. I looked around online for the "best practice" way to do it, but couldn't find an up-to-date answer that I trusted.
I just want the "modern" way to insert into an access DB via ado.net while preventing SQL injection attacks. And if there's anything else I should be keeping in mind, let me know that too.
Oh and by the way, I'm aware that there are better options than MS Access. However, I'm doing this at work on lunch breaks and stuff, and my employer would prefer I don't clutter up SQL server space with silly DBs like this.
You could try Dapper. This is a way to execute arbitrary SQL against anything that implements IDbConnection in a manner which avoids SQL injection attacks and has a nice, clean, modern interface.
Failing that, just use OleDbCommand.Parameters.AddWithValue("fieldname", yourobj); (or the OdbcCommand equivalent. Your query would need to contain question marks as parameter placeholders. For Access, you need to add your arguments to the parameters collection in the same order as the fields appear in the SQL query, like this:
Selecting
string sql = "select * from mytable where MyField LIKE ? and MyOtherField = ?";
// Dapper
using (OleDbConnection dbConn = new OleDbConnection("your connection string))
{
dbConn.Open();
var result = dbConn.Query(sql, new { MyField = "some value", MyOtherField = 3 });
foreach (dynamic myrow in result)
{
// you can get at your table rows using myrow.MyField, myrow.SomeOtherField etc
// To avoid myrow being dynamic, call dbConn.Query<T> where T is some type you
// define that matches your table definition
}
}
// The "old-fashioned" way
using (OleDbConnection dbConn = new OleDbConnection("your connection string))
using (OleDbCommand dbCmd = dbConn.CreateCommand())
{
dbConn.Open();
dbCmd.CommandText = sql;
dbCmd.Parameters.AddWithValue("MyField", "some value"));
dbCmd.Parameters.AddWithValue("MyOtherField", 3));
OleDbDataReader reader = dbCmd.ExecuteReader();
while (reader.Read())
{
string myfield = reader["myfield"] == DBNull.Value ? null : (string)reader["myfield"];
int SomeOtherField = reader["someotherfield"] == DBNull.Value ? 0 : (int)reader["someotherfield"];
}
}
Inserting
string sql = "insert into mytable (MyField, MyOtherField) values (?, ?)";
// Dapper
using (OleDbConnection dbConn = new OleDbConnection("your connection string))
{
dbConn.Open();
dbConn.Execute(sql, new { MyField = "some value", MyOtherField = 3 });
}
// The "old-fashioned" way
using (OleDbConnection dbConn = new OleDbConnection("your connection string))
using (OleDbCommand dbCmd = dbConn.CreateCommand())
{
dbConn.Open();
dbCmd.CommandText = sql;
dbCmd.Parameters.AddWithValue("MyField", "some value"));
dbCmd.Parameters.AddWithValue("MyOtherField", 3));
dbCmd.ExecuteNonQuery(sql);
}
Access is also very picky about numeric types when writing code that moves data from objects to and from the database, if I remember correctly. You need to make sure that you are casting to and from exactly the correct types.

Categories