I have an old C# console application that I'm currently upgrading to use Entity Framework (latest version) code first.
I have 20+ tables that will be created in the database.
One of my methods is
static void DeleteModelInDb(Model pdModel, ExporterContext db) {
string[] tables = new string[] { "PD_COLUMN", "PD_COLUMN_EXTENDED", "PD_COLUMNRULE", "PD_COLUMNSOURCE", "PD_DB_PACKAGE", "PD_DB_PACKAGE_PROC_PARAMETER", "PD_DB_PACKAGE_PROCEDURE", "PD_DB_PACKAGE_TYPE", "PD_DB_PACKAGE_VARIABLE", "PD_DIAGRAM", "PD_DIAGRAM_TABLE", "PD_DOMAIN", "PD_DOMAIN_VALUES", "PD_INDEX", "PD_INDEX_COLUMN", "PD_JOIN", "PD_MODEL", "PD_MODEL_EXTENDED", "PD_MODEL_CHANGES", "PD_MODELSOURCE", "PD_PACKAGE", "PD_REFERENCE", "PD_RULE", "PD_TABLE", "PD_TABLE_EXTENDED", "PD_TABLE_CHANGES", "PD_TABLE_KEY", "PD_TABLE_KEY_COLUMN", "PD_TABLESOURCE", "PD_VIEW", "PD_VIEWCOLUMN", "PD_VIEWSOURCE" };
SqlConnection con = new SqlConnection("definition of con...");
con.Open();
SqlCommand com = con.CreateCommand();
foreach (string table in tables)
{
com.CommandText = "DELETE FROM " + table + " WHERE MODEL_CODE = '" + pdModel.Code + "'";
com.ExecuteNonQuery();
}
com.Dispose();
}
I would like to do something like this using Entity framework. But I don't want to write this delete code for every table.
var x = (from y in db.PdColumn
where y.ModelCode == pdModel.Code
select y).FirstOrDefault();
db.PdColumn.Remove(x);
db.SaveChanges();
Can someone help?
You should be able to abstract over the deletion code by having all the relevant entities implement an interface with the ModelCode property, then make a generic method for deleting (I am assuming that ModelCode is of type string below):
interface IEntityWithCode {
string ModelCode {get;}
}
void DeleteFromDbSet<T>(DBSet<T> dbset, string modelcode) where T: IEntityWithCode {
var x = (from y in dbset
where y.ModelCode == modelcode
select y).FirstOrDefault();
dbset.Remove(x);
}
Since C# does not support variance for classes, I don't think you can have a loop over a list of dbsets in your new DeleteModelInDb method without resorting to nasty reflection hacks, so it will have to look something like this
void DeleteModelInDb(YourDbContext context, string modelcode) {
DeleteFromDbSet(context.SomeTable, modelcode);
DeleteFromDbSet(context.SomeOtherTable, modelcode);
//and so on
}
(Disclaimer: The code is not tested as I don't have an EF core setup handy ATM)
Related
How can I compare an array with a SQL query in C#?
I have tried below code to resolve my problem but it was not worked.
In the below code data is the name of array which holds some types of data.
DataTable dt = context.getData("Select * from emp_detail where EState = any(" +data+ ")", CommandType.Text);
You can serialize the data array into a properly formatted string and change select command to something like that:
Select * from emp_detail where EState in ('a', 'b', ...)
This solution is only suitable for arrays with few items.
I think you could do this to get a list of strings to compare with:
using System.Linq;
namespace ExampleNamespase
{
public class SimpleClass
{
private class TypeExample
{
public string Property { get; }
}
private void CompareData(TypeExample[] exampleArray)
{
var items = string.Join(", ", exampleArray.Select(t => t.Property));
var sql = $"Select * from emp_detail where EState in ({items}) ";
}
}
}
Or so if you are using a simple list of strings:
private void CompareData(string[] exampleArray)
{
var items = string.Join(", ", exampleArray);
var sql = $"Select * from emp_detail where EState in ({items}) ";
}
To prevent injections, use the Dapper library:
using System.Data;
using Dapper;
namespace ExampleNamespase
{
public class SimpleClass
{
private void CompareData(string[] exampleArray, IDbConnection connection)
{
var sql = "Select * from emp_detail where EState in #items ";
var result = connection.Query<YourTypeClass>(sql, new { items = exampleArray });
}
}
}
Explanation:
The first example uses the Linq library to retrieve the properties of an object using the Select method, and the string.Join method to concatenate the resulting list into a comma-separated string.
The second example demonstrates the same approach, only with an array of strings, the same string.Join method is used to join.
In the third example, the Dapper library is used to execute the request, I believe that this method should be preferred, since helps to avoid SQL injection, and also simplifies the work with the database. To execute a query, we can pass a list of objects with which we want to compare values in the database, and Dapper will be able to handle this itself. We just need to define variables with the # symbol in the request, and pass the object for processing, which will have attributes with the same names.
P.S. I apologize for my English))
Upd:
Try this:
private void CampareData(string[] exampleArray, IDbConnection connection)
{
var clearItems = exampleArray.Select(s => $"'{s}'").ToArray();
var sql = "Select * from emp_detail where EState in #items ";
var result = connection.Query<YourTypeClass>(sql, new { items = clearItems });
}
In this variant, we will wrap each item in quotes that are used by sql, and the query should contain something like: ... in ('item1', 'item2' ...)
I am converting data from sql into a list of PersonModel. But my question is: is there a faster way ( less code ) to get this done without using any framework / dapper. LINQ is ALLOWED.
( The code right now is just working fine, but maybe it can be done in a simpler way ).
This is the code I have right now:
public List<PersonModel> GetPerson_All()
{
var people = new List<PersonModel>();
//Get the connectionString from appconfig
using (var connection = new SqlConnection(GlobalConfig.CnnString(db)))
{
connection.Open();
//Using the stored procedure in the database.
using (var command = new SqlCommand("dbo.spPeople_GetAll", connection))
{
using (var reader = command.ExecuteReader())
{
//With a while loop, going trough each row to put all the data in the PersonModel class.
while (reader.Read())
{
var person = new PersonModel();
person.Id = (int)reader["Id"];
person.FirstName = (string) reader["FirstName"];
person.LastName = (string)reader["LastName"];
person.EmailAdress = (string)reader["EmailAddress"];
person.CellphoneNumber = (string)reader["CellphoneNumber"];
//Add the data into a list of PersoModel
people.Add(person);
}
}
}
}
return people;
}
With ( dapper ) you can put all the data inmediatly to a list. Is something like this possible without dapper?
public List<PersonModel> GetPerson_All()
{
List<PersonModel> output;
using (var connection = new SqlConnection(GlobalConfig.CnnString(db)))
{
output = connection.Query<PersonModel>("dbo.spPeople_GetAll").ToList();
}
return output;
}
No, there is not an more optimal way to do this then to use a method or mapper library like Dapper or Entity Framework: that is the entire reason such libraries exist.
If you have repeating code blocks like this, but don't want to use external libraries, you can try to refactor this to a method which executes a statement, iterates over the result and instantiates and fills objects.
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 trying to use the Dapper orm with the following simple query:
var sqlString = new StringBuilder();
sqlString.Append("select a.acct AccountNumber,");
sqlString.Append(" b.first_name FirstName,");
sqlString.Append(" b.last_name LastName,");
sqlString.Append(" a.rr RrNumber,");
sqlString.Append(" c.addr1 AddressLine1,");
sqlString.Append(" c.addr2 AddressLine2,");
sqlString.Append(" c.addr3 AddressLine3,");
sqlString.Append(" c.addr4 AddressLine4,");
sqlString.Append(" c.addr5 AddressLine5,");
sqlString.Append(" c.addr6 AddressLine6,");
sqlString.Append(" c.addr7 AddressLine7,");
sqlString.Append(" c.addr8 AddressLine8 ");
sqlString.Append("from (pub.mfclac as a left join pub.mfcl as b on a.client=b.client) ");
sqlString.Append("left join pub.mfclad as c on a.client=c.client ");
sqlString.Append("where a.acct = '#ZYX'");
var connection = new OdbcConnection(_connectionString);
var result = connection.Query(sqlString.ToString(),
new
{
ZYX = accountNumber
});
However when I execute this with an accountNumber known to exist, dapper returns nothing. So I tried to remove the quotes to verify that the parameter is in fact being replaced with the account number, however the error being returned from the server indicates a syntax error around "#ZYX". Which means dapper is not replacing the parameter with it's given value. Any ideas why this is happening? From the limited documentation out there, this should 'just work'.
Edit1
Couldn't get this to work. Using string.format to insert the parameter as a work around.
There are two issues here; firstly (although you note this in your question) where a.acct = '#ZYX', under SQL rules, does not make use of any parameter - it looks to match the literal string that happens to include an # sign. For SQL-Server (see note below), the correct usage would be where a.acct = #ZYX.
However! Since you are use OdbcConnection, named parameters do not apply. If you are actually connecting to something like SQL-Server, I would strongly recommend using the pure ADO.NET clients, which have better features and performance than ODBC. However, if ODBC is your only option: it does not use named parameters. Until a few days ago, this would have represented a major problem, but as per Passing query parameters in Dapper using OleDb, the code (but not yet the NuGet package) now supports ODBC. If you build from source (or wait for the next release), you should be able to use:
...
where a.acct = ?
in your command, and:
var result = connection.Query(sqlString.ToString(),
new {
anythingYouLike = accountNumber
});
Note that the name (anythingYouLike) is not used by ODBC, so can be... anything you like. In a more complex scenario, for example:
.Execute(sql, new { id = 123, name = "abc", when = DateTime.Now });
dapper uses some knowledge of how anonymous types are implemented to understand the original order of the values, so that they are added to the command in the correct sequence (id, name, when).
One final observation:
Which means dapper is not replacing the parameter with it's given value.
Dapper never replaces parameters with their given value. That is simply not the correct way to parameterize sql: the parameters are usually sent separately, ensuring:
there is no SQL injection risk
maximum query plan re-use
no issues of formatting
Note that some ADO.NET / ODBC providers could theoretically choose to implement things internally via replacement - but that is separate to dapper.
I landed here from dublicate question: Dapper must declare the scalar variable
Error: Must declare the scalar variable "#Name".
I created queries dynamically with this piece of code:
public static bool Insert<T>(T entity)
{
var tableName = entity.GetType().CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(TableAttribute))?.ConstructorArguments?.FirstOrDefault().Value as string;
if (string.IsNullOrEmpty(tableName))
throw new Exception($"Cannot save {entity.GetType().Name}. Database models should have [Table(\"tablename\")] attribute.");
DBSchema.TryGetValue(tableName.ToLower(), out var fields);
using (var con = new SqlConnection(ConnectionString))
{
con.Open();
var sql = $"INSERT INTO [{tableName}] (";
foreach (var field in fields.Where(x => x != "id"))
{
sql += $"[{field}]"+",";
}
sql = sql.TrimEnd(',');
sql += ")";
sql += " VALUES (";
foreach (var field in fields.Where(x => x != "id"))
{
sql += "#"+field + ",";
}
sql = sql.TrimEnd(',');
sql += ")";
var affectedRows = con.Execute(sql, entity);
return affectedRows > 0;
}
}
And I got the same error when my models was like this:
[Table("Users")]
public class User
{
public string Name;
public string Age;
}
I changed them to this:
[Table("Users")]
public class User
{
public string Name { get; set; }
public string Age { get; set; }
}
And it solved the problem for me.
I am dealing with SQLite in windows store application.I am updating the values in table using
var tagPage = db.QueryAsync<MyModel>("UPDATE MyModel SET Tag =2 WHERE id = 1");
db.UpdateAsync(tagPage);
It throws an NotSupportedException on SQLite.cs class over the method
public int Update(object obj, Type objType)
{
if (obj == null || objType == null)
{
return 0;
}
var map = GetMapping(objType);
var pk = map.PK;
if (pk == null)
{
throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
}
var cols = from p in map.Columns
where p != pk
select p;
var vals = from c in cols
select c.GetValue(obj);
var ps = new List<object>(vals);
ps.Add(pk.GetValue(obj));
var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols
select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
return Execute(q, ps.ToArray());
}
because I think it wont get the primery key, where I have provided primery key in table.
I tried it with async and await but no use, why it is happening? please help me
Regarding your NotSupportedException issue - I suspect that your model is missing the PrimaryKey attribute:
public class MyModel
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Tag { get; set; }
public string Foo { get; set; }
}
You will need the attribute(s) above, even if your db schema already has them defined. At this time, Sqlite-Net does not read schema data from the db.
Some other thoughts:
Firstly, you should use only either the synchronous Sqlite-Net API, or the async API. Don't use both.
I personally prefer the synchronous API, as it appears to be more up to date. The async API has not been updated in 11 months, whereas the synchronous API was updated 4 months ago. Besides this I never was able to figure out how to do transactions using the async API. Again, this is personal preference.
Secondly, the Query and QueryAsync methods should be used only for querying (for SELECTs). They should not be used for UPDATEs. For adding/changing data, you will want to use these synchronous methods: Execute, Update, Insert, etc. If using the async API, there are the async counterparts (ExecuteAsync, etc).
Please read the Sqlite-Net Project page, as you will find a lot of helpful information there regarding general API usage.