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.
Related
I am working on the security and user management of a new platform built entirely in .NET Core.
In particular I am trying to generate a random password for new users. I have loaded a large list of English words into a table and created a stored procedure to select random words from the table and compose a password in the correct-horse-battery-staple format.
The stored procedure (Passwords.GenerateRandomPassword) takes no parameters and returns a single line varchar column named password.
Everything works up to this point. I can run the query directly against the server and it works fine.
I have a method on my userRepository like so:
public async Task<string> GenerateRandomPassword()
{
}
but I cannot figure out how to get EF Core 3.14 to call this stored procedure and return a value.
Documentation may not be up to date, or maybe I'm missing an assembly reference.
The context object and the context.database object do not seem to contain any methods that look like they will allow me to execute a stored procedure and retrieve a value.
Documentation seems to suggest that there should be a FromSQL method or similar.
Any help will be greatly appreciated.
The general solution is to call db.Database.GetDbConnection(), which gives you the ADO.NET connection object that you can use directly.
eg
var con = (SqlConnection)db.Database.GetDbConnection();
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = "exec ...";
There's also the db.Database.ExecuteSqlxxx methods, which work for simple cases.
What you want to look at is keyless entity types.
This is a new feature in EF Core 3.0.
One usage is to retrieve data from raw sql queries.
Using PostgreSQL.
In PostgreSQL we create a function to generate a password:
CREATE OR REPLACE FUNCTION generate_password() RETURNS text AS $$
BEGIN
RETURN (SELECT substring(md5(random()::text) from 0 for 12));
END
$$ LANGUAGE plpgsql;
We create our entity:
public class PasswordGenerator
{
public string Password { get; set; }
}
In our application's DbContext we configure our entity:
public DbSet<PasswordGenerator> PasswordGenerator { get; set; }
public MyDbContext(DbContextOptions options)
: base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PasswordGenerator>().HasNoKey();
}
We use the FromSqlRaw method to call our function that returns our password:
public async Task<string> GetGeneratedPassword()
{
var pg = await _context.PasswordGenerator
.FromSqlRaw(#"SELECT * from generate_password() AS ""Password""")
.FirstAsync();
return pg.Password;
}
We use the alias "Password" to correctly map our query to our entity.
The two packages installed are: Microsoft.EntityFrameworkCore and Npgsql.EntityFrameworkCore.PostgreSQL.
Using SQL Server.
We create a stored procedure to generate a password:
CREATE OR ALTER PROCEDURE generate_password
AS
SET NOCOUNT ON
SELECT SUBSTRING (CONVERT(varchar(255), NEWID()), 0, 12) AS "Password"
RETURN
GO
Use the alias "Password" to correctly map our query to our entity.
Here's how we use the FromSqlRaw method:
public async Task<string> GetGeneratedPassword()
{
var pg = (await _context.PasswordGenerator
.FromSqlRaw("EXEC generate_password")
.ToListAsync())
.First();
return pg.Password;
}
LINQ queries expect our raw queries to be composable, which is why we call ToListAsync() right after the FromSqlRaw method.
SQL Server doesn't allow composing over stored procedure calls, so any
attempt to apply additional query operators to such a call will result
in invalid SQL. Use AsEnumerable or AsAsyncEnumerable method right
after FromSqlRaw or FromSqlInterpolated methods to make sure that EF
Core doesn't try to compose over a stored procedure.
The two packages installed are: Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer
UPDATE - Using the GetDbConnection method
Thanks to the answer provided by #David Browne - Microsoft, we can call the GetDbConnection extension method to access the database directly.
public async Task<string> GetGeneratedPassword()
{
var password = "";
var connection = _context.Database.GetDbConnection();
try
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
// SQL Server
command.CommandText = "EXEC generate_password";
// PostgreSQL
// command.CommandText = #"SELECT * from generate_password()";
using (var reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
password = reader.GetString(0);
}
}
}
}
finally
{
await connection.CloseAsync();
}
return password;
}
I have a table tblDetails consisting of 2 columns
Id INT NOT NULL
Name VARCHAR(20)
By using EntityFramework 6 and C# for my WPF application im fetching the data to display it in the front end. Also I want to get the "sql datatype" of the columns as well.
This is the entity model used for database connectivity:
public class tblDetails
{
public int Id{get; set;}
public string Name{get;set;}
}
For this I'm using the GetProperty() method for it.
Below is a sample of code:
private void GetDataType()
{
var col = typeof(tblDetails).GetProperty("Name");
string dataType = col.PropertyType.Name;
}
PROBLEM
dataType varibale is assigned with the value string but I want the exact Sql datatype of the column i.e. VARCHAR(20)
I've gone through a number of solutions like below but didn't get the solution to my problem.
How can I get data type of each column in a SQL Server table/view etc. using C# Entity Framework?
Anybody got a C# function that maps the SQL datatype of a column to its CLR equivalent?
Entity Framework Data Annotations Set StringLength VarChar
UPDATE
Below is the class in which I'm using the Dbcontext:
public class DomainService : DbContext
{
public DomainService() : base("ConnectionString")
{
Configuration.LazyLoadingEnabled = true;
#if DEBUG
Database.Log = (s) => Debug.WriteLine(s);
#endif
}
public DbSet<tblDetails> tblDetails { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Entity Framework (and actually any "generic ORM") are meant to be generic, and therefor you somehow lost the ability to know the exact datatype.
Even if you're looking at the attributes or mapping that sometimes are stored by different ORM's to enable some kind of validations prior to actually doing the work against the DB - you can't guarantee someone didn't change the schema on the SQL Server end - and didn't update the model.
In most cases it shouldn't really matter - working with the matching CLR data-types should give you enough info (for example, for user interface usage).
If you need to know the exact data type, as stored and known by the server, you have the following options:
If you're interested in table or view schema, you can use SMO (SQL Server Management object) to connect to the server and query the actual schema of the table or view.
If you want to know the schema returned by specific query, you can use sp_describe_first_result_set SP (https://msdn.microsoft.com/en-us/library/ff878602.aspx) that gets SQL query and return result-set describing each column you can expect in the result set of the query. You can probably run that once for the queries you use, and then cache the results or something.
Hope it helps.
Got a solution:
To get the datatype and length as well of variables of type VARCHAR, NCHAR & NVARCHAR
SqlConnection connection = new SqlConnection("ConnectionString");
connection.Open();
string sqlcmnd = string.Format(
#"SELECT CASE UPPER(DATA_Type)
WHEN 'NCHAR' THEN CONCAT(UPPER(DATA_Type),'(',ISNULL(CHARACTER_MAXIMUM_LENGTH,''),')')
WHEN 'VARCHAR' THEN CONCAT(UPPER(DATA_Type),'(',ISNULL(CHARACTER_MAXIMUM_LENGTH,''),')')
WHEN 'NVARCHAR' THEN CONCAT(UPPER(DATA_Type),'(',ISNULL(CHARACTER_MAXIMUM_LENGTH,''),')')
ELSE UPPER(DATA_Type) END AS DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{0}' AND COLUMN_NAME = '{1}'", tableName, ColumnName);
SqlCommand cmd = new SqlCommand(sqlcmnd, connection);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
string dataType = reader["DATA_TYPE"].ToString();
Use DbContext object instead your own created model class in reflection
DomainService context = new DomainService();
private void GetDataType()
{
var col = typeof(context.tblDetails).GetProperty("Name");
string dataType = col.PropertyType.Name;
}
So far I know two ways to query the database. One of it is by using MySQL's Libraries; By declaring "MySqlCommand", "MySqlDataReader", "MySqlConnection" and utilizing the inbuilt methods.
And another is through using the Entity Framework (Which is my preferred option).
I've however run into a bit of a problem with this later method. I personally blame my lack of knowledge. It would seem that when conveying data to the end user, Entity-Framework favours the ObservableCollection<> or List<>.
For me, this means that in the View, I'd be using a Datagrid or List control. Normally this would be fine.
But then, what happens if I don't want a datagrid control in the mix? What if I want the query's result conveyed in a textboxes? How can I do the below without sacrificing the usage of the entity framework? Is it even possible?
string config = "server=localhost; userid = root; database = databaseName";
MySqlConnection con = new MySqlConnection(config);
MySqlDataReader reader = null;
// Run the select query
string query = "SELECT * FROM students WHERE id = " +id;
MySqlCommand command = new MySqlCommand(query, con);
con.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
// Put the results in the appropriate strings so I can databind them
string studentName = (string)reader["studentName"];
string studentNum = (string)reader["studentNum"];
.....
}
con.Close();
How could I do this utilising the entity framework?
EF equivalents for this SQL query:
// note, that this leads to SQL injections,
// use parametrized queries instead!
"SELECT * FROM students WHERE id = " +id
will be:
var student = context.Students.Find(id) // this will lookup local data as well
or:
var student = context.Students.Single(_ => _.Id == id)
Both will return single Student instance, not a sequence/collection, and you can bind your UI to its properties or access them as usual:
student.StudentName
Try to something like that:
public static Studens GetStudent(int id)
{
return context.Studens.SingleOrDefault(x=>x.Id == id);
}
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.
I have a question about Entity Framework and Linq to Entities. My .NET version is 4.0. I'm refactoring the database access layer of an existing application, and I plan to use Linq to Entities (instead of today's DataReaders and SQL strings). The structure of the database cannot be altered.
My problem comes from a stored procedure, which, simplified, looks like the following:
CREATE PROCEDURE getElement #tableid as int, #elementid as int AS
BEGIN
DECLARE #tablename as varchar(50)
SELECT #tablename = tablename FROM tables WHERE tableid = #tableid
EXEC('SELECT * FROM ' + #tablename + ' WHERE elementid = ' + #elementid)
END
I know that the row(s) returned will have a column named elementid, and based on this value, I know what other columns to expect.
Today, this is solved with an SqlDataReader which does a dictionary-like lookup of the elemendid element.
public Element getElement(SqlDataReader dr)
{
switch((int)dr["elementid"])
{
case 1:
return getTextElement(dr);
case 2:
return getImageElement(dr);
//...
}
}
Element is an abstract base class. getTextElement returns a TextElement : Element, and getImageElement returns an ImageElement : Element.
How do I model this in Entity Framework? Complex types does not seem to cut it, since it does not seem to support dynamic properties. I have also looked at an EntityObject Generator, but I'm not really all that experienced with customizing T4 code (maybe I ought to learn for this problem?). The perfect solution for me would be to have the imported stored procedure return an object with the dynamic type, but Entity Framework 4 does not seem to support this.
I think the problem you are encountering is that the EF and Linq to Sql designers generate models based on the known structure of a table. You sproc evaluates the result using EXEC, so the procedure cannot be analysed to figure out what the result model will look like. I'm not sure this can be solved using an ORM tool, you may need to specialise two stored procedures, one for explicitly returning TextElement models, and one for ImageElement models.
I just thought that I would add how I solved this.
I created a couple of helper classes that emulates the behaviour of Linq to Entities, and use them on my special stored procedures. It's far from perfect, or even good, but it makes the resulting code look quite similar to Linq to Entities. This is important for me as the rest of my database layer will use Linq to Entities.
In the perfect world, I would be able to formulate a query to Linq to Entities, and then use the result somewhat similar to what I'm doing right now.
Here we go...
The code is used as follows:
var connectionString = new SqlConnectionStringBuilder
{
DataSource = #"C:\Temp\Northwind.mdf"
};
var commandText = "select * from Customers";
using (var rows = new SqlCommandHelper(connectionString.ToString(), System.Data.CommandType.Text, commandText))
{
foreach (dynamic row in rows)
{
try
{
Console.WriteLine(row.Fax ?? "Emtpy");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Invalid column name");
}
}
}
As you can see, the enumeration of the rows looks similar to how it would be if I had used Linq to Entities instead of SqlCommandHelper.
The SqlCommandHelper class is the following code:
class SqlCommandHelper : IEnumerable<DynamicSqlRow>, IDisposable
{
private SqlConnection connection;
private SqlCommand command;
public SqlCommandHelper(string connectionString, System.Data.CommandType commandType, string commandText, params SqlParameter[] parameters)
{
connection = new SqlConnection(connectionString);
command = new SqlCommand
{
CommandText = commandText,
CommandType = commandType,
Connection = connection
};
command.Parameters.AddRange(parameters);
}
public IEnumerator<DynamicSqlRow> GetEnumerator()
{
if (connection.State != System.Data.ConnectionState.Open)
{
connection.Open();
}
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return new DynamicSqlRow(reader);
}
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
command.Dispose();
connection.Dispose();
}
}
As you can see, the magic lies within DynamicSqlRow. I thing to note is that you'll need to import the System.Dynamic namespace for DynamicSqlRow to compile.
class DynamicSqlRow : DynamicObject
{
System.Data.IDataReader reader;
public DynamicSqlRow(System.Data.IDataReader reader)
{
this.reader = reader;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var row = reader[binder.Name];
result = row is DBNull ? null : row;
return true;
}
}
I hope that this code might be useful for anyone else, or that it makes someone think of a better solution.
A useful link for me was Walkthrough: Creating and Using Dynamic Objects from MSDN.
Take care