Dynamic tables from a stored procedure using Linq to Entities - c#

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

Related

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.

How to insert self referencing entities into sql?

Assume I have the following structure for a sql table:
Name:
UserTable
Fields:
ID bigint IDENTITY(1, 1)
Name nvarchar(200) NOT NULL
ParentID bigint NULL
Note:
ParentID is a self referencing foreign key to the primary key ID which is optional.
Now switching over to my c#-project, I find myself wondering how to insert this entity many times from an import.
public static void InsertTable(DataTable table)
{
var connection = CreateConnection();
string query = "INSERT INTO [dbo].[User] (Name, ParentID) " +
"OUTPUT INSERTED.ID " +
"VALUES " +
"(#Name, #ParentID)";
using (connection)
{
for (int i = 0; i < table.Rows.Count; i++)
{
DataRow row = table.Rows[i];
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = query;
InsertParameters(row, command);
long insertedID = (long)command.ExecuteScalar();
row["ID"] = insertedID;
}
}
}
}
I set the parameters like this:
private static void InsertParameters(DataRow row, SqlCommand command)
{
string name = (string)row["Name"];
command.Parameters.AddWithValue("#Name", name);
if(row["ParentID"] is DBNull)
{
command.Parameters.AddWithValue("#ParentID", DBNull.Value);
}
else
{
command.Parameters.AddWithValue("#ParentID", (long)row["ParentID"]);
}
}
I figured that I won't be able to insert these entities into this table at any order. My approach was to try to insert entities with no reference to any parent first. While this can work in a simple example like this, I struggle to find an approach for multiple references.
I worked around this by just mapping the relations in some Dictionary<T1, T2> objects and revisit the ones with references later, when the ID-property of the referenced entity is set.
My problem with this is that I can clearly map one DataRow to another, but not insert them so easy, when I can not know the ID beforehand. I'd like to know if there are some better approaches to this.
I stumbled upon this particular problem while doing an import for some customer-related data. My solution so far is okay-ish, but not satisfactory. One case where it all breaks could be a loop reference, I think.
Anyway,
How would you tackle this problem and how to improve my method so far?
I would create stored procedure which does the whole process and can get the ids as such. Then in C# code call the sproc.
This is an example from my nuget package SQLJSONReader (github project page) where the SQL server sproc returns JSON and my reader ExecuteJsonReader then converts the table result, to a string of JSON.
string sproc = "dbo.DoIt";
string result;
using (SqlConnection conn = new SqlConnection(connection))
{
conn.Open();
using (var cmd = new SqlCommand(sproc, conn) { CommandType = CommandType.StoredProcedure, CommandTimeout = 600 })
{
if (parameters != null)
cmd.Parameters.AddRange(parameters);
var reader = await cmd.ExecuteJsonReaderAsync();
result = await reader.ReadAllAsync();
}
}
So your process is similar, just use your own reader.

Non-Stored Procedure Query (CommandType=Text) with Dapper and parameters

I am trying to call a piece of SQL with parameters in Dapper. It is NOT a stored procedure (I have that working fine with parameters)
inputCustomerName = "Our Customer";
inputStationName = Null;
var parameters = new
{
customerName = inputCustomerName ,
stationName = inputStationName
};
...
using (var dbConn = dataProvider.CreateConnection)
{
dbConn.ConnectionString = connectionString;
dbConn.Open();
returnValue = dbConn.Query<T>(sql: sql, commandType: commandType, param: parameters);
dbConn.Close();
}
The relevant part of the SQL is
" ...
WHERE
Customer = ISNULL(#customerName,Customer)
AND Station = ISNULL(#stationName,Station)
";
I keep getting "Invalid type owner for DynamicMethod". (I got this also when using DynamicParameters instead of the anomymous object).
The SQL runs fine on the database itself (given I declare and #populate #customerName and stationName).
I suspect I have done something quite simple wrong - can anyone enlighten me?
The answer was, in the end, an issue in code not included in the question - for which I thoroughly apologise.
The issue was that T was an Interface, and that was what was causing Dapper to blow up. There's probably something under the hood that means you can only use classes, not interfaces as the type param to Query. Though I have never seen that restriction explicitly stated.
Shame, (but I can imagine a few reasons why)

Get the "sql datatype" of a column created in SQL server using C# and entity framework

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;
}

Create Reusable Linq To SQL For Stored Procedures

I am working on a new project that needs to use Linq To SQL. I have been asked to create a generic or reusable Linq to SQL class that can be used to execute stored procedures.
In ADO.Net I knew how to do this by just passing in a string of what I wanted to execute and I could pass in different strings for each query I need to run:
SqlCommand cmd = new SqlCommand("myStoredProc", conn); // etc, etc
I am struggling with how to create something similar in Linq To SQL, if it is even possible. I have created a .dbml file and added my stored procedure to it. As a result, I can return the results using the code below:
public List<myResultsStoreProc> GetData(string connectName)
{
MyDataContext db = new MyDataContext (GetConnectionString(connectName));
var query = db.myResultsStoreProc();
return query.ToList();
}
The code works but they want me to create one method that will return whatever stored procedure I tell it to run. I have searched online and talked to colleagues about this and have been unsuccessful in finding a way to create reusable stored proc class.
So is there a way to create a reusable Linq to SQL class to execute stored procs?
Edit:
What I am looking for is if there is a way to do something like the following?
public List<string> GetData(string connectName, string procedureName)
{
MyDataContext db = new MyDataContext (GetConnectionString(connectName));
var query = db.procedureName();
return query.ToList();
}
I have reviewed the MSDN docs on Linq To Sql and these are showing the table in the IEnumerable:
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
#"select c1.custid as CustomerID, c2.custName as ContactName
from customer1 as c1, customer2 as c2
where c1.custid = c2.custid"
);
I am looking for something very generic, where I can send in a string value of the stored proc that I want to execute. If this is not possible, is there any documentation on why it cannot be done this way? I need to prove why we cannot pass a string value of the name of the procedure to execute in Linq To Sql
DataContext.ExecuteCommand is not quite what you are looking for, as it only returns an int value. What you want instead is DataContext.ExecuteQuery, which is capable of executing a stored procedure and returning a dataset.
I would create a partial class for your DBML in which to store this function.
public List<T> GetDataNoParams(string procname)
{
var query = this.ExecuteQuery<T>("Exec " + procname);
return query.ToList();
}
public List<T> GetDataParams(string procname, Object[] parameters)
{
var query = this.ExecuteQuery<T>("Exec " + procname, parameters);
return query.ToList();
}
To call a stored procedure you would do:
GetDataNoParams("myprocedurename");
or
GetDataParams("myotherprocedure {0}, {1}, {2}", DateTime.Now, "sometextValue", 12345);
or
GetDataParams("myotherprocedure var1={0}, var2={1}, var3={2}", DateTime.Now, "sometextValue", 12345);
If you want to call procedures with no return value that is easy enough too, as I'm sure you can see, by creating a new method that doesn't store/return anything.
The inspiration came from http://msdn.microsoft.com/en-us/library/bb361109(v=vs.90).aspx.
The simplest answer to your question is that you can grab the Connection property of your MyDataContext and create and execute your own SqlCommands just like you would in straight up ADO.Net. I'm not sure if that will serve your purposes, especially if you want to retrieve entities from your LINQ to SQL model.
If you want to return entities from the model, then have a look at the DataContext.ExecuteCommand method.
When we drop a Table or StoredProcedure in our .dbml file it creates its class which communicates with the data layer and our business logic.
In Linq to SQL we have to have the StoredProcedures or Tables present in the .dbml file otherwise there is no way to call a generic method in Linq to SQL for calling a stored procedure by passing its name to a method.
But in ADO.Net we can do it (like you know)
SqlCommand cmd = new SqlCommand("myStoredProc", conn);

Categories