Is there a Cleaner Dapper way to Update only columns that changed with Dapper? - dapper

I'm looking for a way to update only set properties in Dapper. i.e Update an Entity's property only if its not null.
I'm solving the same problem with a rather crude approach as shown below but I believe there should be a cleaner dapper way of doing this.
public void UpdateCustomer(Customer cust)
{
try
{
StringBuilder sb = new StringBuilder("UPDATE CUSTOMER_SETUP SET DATE_MODIFIED = #DATE_MODIFIED ");
if(cust.BUSINESSNAME != null) sb.Append(",BUSINESSNAME = #BUSINESSNAME ");
if (cust.BUSINESS_ADDRESS != null) sb.Append(",BUSINESS_ADDRESS = #BUSINESS_ADDRESS ");
if (cust.CONTACT_NAME != null) sb.Append(",CONTACT_NAME = #CONTACT_NAME ");
if (cust.CONTACT_TITLE != null) sb.Append(",CONTACT_TITLE = #CONTACT_TITLE ");
if (cust.CONTACT_PHONE1 != null) sb.Append(",CONTACT_PHONE1 = #CONTACT_PHONE1 ");
if (cust.CONTACT_PHONE2 != null) sb.Append(",CONTACT_PHONE2 = #CONTACT_PHONE2 ");
if (cust.CONTACT_EMAIL != null) sb.Append(",CONTACT_EMAIL = #CONTACT_EMAIL ");
if (cust.CONTACT_URL != null) sb.Append(",CONTACT_URL = #CONTACT_URL ");
if (cust.DATE_CREATED != null) sb.Append(",DATE_CREATED = #DATE_CREATED ");
if (cust.CUSTOMER_TYPE != null) sb.Append(",CUSTOMER_TYPE = #CUSTOMER_TYPE ");
if (cust.SUBSCRIPTION_TYPE != null) sb.Append(",SUBSCRIPTION_TYPE = #SUBSCRIPTION_TYPE ");
sb.Append("WHERE ID = #ID ");
sb.Append("; SELECT CAST(SCOPE_IDENTITY() as int ");
var sql = sb.ToString();
using (connection = new SqlConnection(connectString))
{
connection.Execute(sql, cust);
}
}
catch (Exception ex)
{
throw ex;
}
}

The feature you are looking for is called Change Tracking. This feature is one of the part of a bigger Unit Of Work pattern.
Dapper do not support Change Tracking.
There are few add-ons of Dapper those support this at different level. Refer this this blog post for comparison chart. As mentioned in chart, Dapper.Contrib and Dapper.Rainbow support it in different ways.
As #MarcGravell said in comment, null values for POCO properties is common. It does not always mean "do not update that field". That may also mean "set that DB field to null (or DBNull)". As there is no one guaranteed meaning for the property value being null, most ORMs implement it same way as Dapper does.

We wrap Dapper.Rainbow's Snapshotter to work like a change tracker. You need an instance of the db object for it to work.
It works perfectly for us delivering a dictionary you could quite easily use to generate the SQL you're after.
It might look something like this:
public class Foo{public string Name{get;set;}}
var foo = new Foo();
var snapshotter = Snapshotter.Start(foo);
foo.Name = "A new name";
var dynparams = snapshotter.Diff(); //we basically wrap the snapshotter to give a dict here, but it's basically the same thing
foreach(var name in dynparams.ParameterNames){
sb.Append($",{name} = #{dynparams[name]} ");
}

Roll your own.
Add a private collection to the model, and copy the data when reading. Compare old to new, when updating. Build the SQL statement as needed.
Would there be a net performance gain by adding to the pre-database processing, over letting the columns "dry fire" to the database vendor? I think most database vendors are aware when the column data is unchanged, and will exclude these columns where necessary, such as an update trigger on specific columns.

Here is the example of Update query using Dapper that you want. It might help you.
public async Task<bool> UpdateDataByIdAsync(Data data)
{
using MySqlConnection connection = new MySqlConnection("your connection string");
const string sqlQuery = #"Update Datas Set name = #name, description = #description, tags = #tags
where data_id = #data_id;";
var rowAffected = await connection.ExecuteAsync(sqlQuery, data);
return rowAffected > 0;
}

Here is a snippet found on Github, if you are using SimpleCrud
/// <summary>
/// Updates table T with the values in param.
/// The table must have a key named "Id" and the value of id must be included in the "param" anon object. The Id value is used as the "where" clause in the generated SQL
/// </summary>
/// <typeparam name="T">Type to update. Translates to table name</typeparam>
/// <param name="connection"></param>
/// <param name="param">An anonymous object with key=value types</param>
/// <returns>The Id of the updated row. If no row was updated or id was not part of fields, returns null</returns>
public static object UpdateFields<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeOut = null, CommandType? commandType = null)
{
var names = new List<string>();
object id = null;
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(param))
{
if (!"Id".Equals(property.Name, StringComparison.InvariantCultureIgnoreCase))
names.Add(property.Name);
else
id = property.GetValue(param);
}
if (id != null && names.Count > 0)
{
var sql = string.Format("UPDATE {1} SET {0} WHERE Id=#Id", string.Join(",", names.Select(t => { t = t + "=#" + t; return t; })), GetTableName(typeof(T)));
if (Debugger.IsAttached)
Trace.WriteLine(string.Format("UpdateFields: {0}", sql));
return connection.Execute(sql, param, transaction, commandTimeOut, commandType) > 0 ? id : null;
}
return null;
}
public static object UpdateFields<T>(this IDbConnection connection, object fields, CommandDefinition commandDefinition)
{
return UpdateFields<T>(connection, fields, commandDefinition.Transaction, commandDefinition.CommandTimeout, commandDefinition.CommandType);
}

Related

How to get Type of a column using DacFx api?

In SSDT project, using DacFx API, I am querying in memory model of a database. I have been trying to retrieve Type of a column, but cannot figure it out. Any idea how this could be done?
private void TestMethod()
{
using(TSqlModel model =
new TSqlModel(SqlServerVersion.Sql110, new TSqlModelOptions {}))
{
string[] scripts = new[]
{
"CREATE TABLE t1 (c1 NVARCHAR(30) NOT NULL)",
"CREATE TABLE t2 (c2 INT NOT NULL)"
};
foreach (string script in scripts)
{
model.AddObjects(script);
}
var tables = model.GetObjects(DacQueryScopes.All, Table.TypeClass);
var cols = tables.First().GetReferenced(Table.Columns);
foreach (TSqlObject col in cols)
{
//?? how can I get the data type of the column?
string dataType = col.??;
if (dataType == "int")
{
//my thing here
}
}
}
}
Without using the strongly-typed model, it'd be:
var dataType = col.GetReferenced(Column.DataType).First();
var dataTypeName = dataType.Name.Parts[0];
The strongly-typed model can make this easier, though. Check out https://github.com/Microsoft/DACExtensions/tree/master/DacFxStronglyTypedModel
The correct code should be:
string dataType = col.DataType;

Finding data type information for certain objects in DacFx API

I'm writing a code generation tool against Sql Server Data Tools and I need to be able to get the data type for:
View Columns
Computed Columns on a table
Where is this information? For tables (with the exception of computed columns), it's here:
TSqlObject table; //retrieved elsewhere
TSqlObject column = table.GetReferenced(Table.Columns).First(); //first column
TSqlObject dataType = column.GetReferenced(Column.DataType).FirstOrDefault();
For computed columns, dataType above is null.
For views, I've tried:
TSqlObject view; //retrieved elsewhere
TSqlObject column = view.GetReferenced(View.Columns).First(); //first column
TSqlObject dataType = column.GetReferenced(Column.DataType).FirstOrDefault();//null
Is this information anywhere? Are there any other options for getting this information other than publishing the source .DACPAC to a database?
EDIT: in response to Ed Elliot below (regarding the use of the strongly-typed DacFx model)
The following code fails to bring back type information for the view:
TSqlTypedModel model = new TSqlTypedModel(#"path.dacpac");
var view = model.GetObjects<TSqlView>(Microsoft.SqlServer.Dac.Model.DacQueryScopes.UserDefined).FirstOrDefault();
var viewcolumns = view.Columns;
//false
bool viewHasTypeInformation = viewcolumns.Any(c => c.DataType.Count() > 0);
var table = model.GetObjects<TSqlTable>(Microsoft.SqlServer.Dac.Model.DacQueryScopes.UserDefined).FirstOrDefault();
var tablecolumns = table.Columns;
//true
bool tableHasTypeInformation = tablecolumns.Any(c => c.DataType.Count() > 0);
I'm starting to think this is a limitation of the DAC model itself.
oooh great topic :)
The easiest way to query using the DacFxStronglyTypedModel object which is available:
https://github.com/Microsoft/DACExtensions
It is a bit odd in that it is a sample which you build and then it gives you easy access to query the DacFx:
https://github.com/Microsoft/DACExtensions/tree/master/DacFxStronglyTypedModel
To get a strongly typed model do:
var model = new TSqlTypedModel("dacpacPath");
Then when you query it for all the views (or whatever) you get a list of typed objects which are a lot "saner" than the DacFx.
The interface that you get back for views:
ISql120TSqlView (change the version number to your version number) has an IEnumerable of Columns:
IEnumerable<Microsoft.SqlServer.Dac.Extensions.Prototype.ISql120TSqlColumn> Columns
{
get;
}
The column interface then has an IEnumerable of DataTypes:
IEnumerable<Microsoft.SqlServer.Dac.Extensions.Prototype.ISqlDataType> DataType
{
get;
}
I haven't got a windows machine right now to get you a full demo but that should be enough, if you dont' get what you need put a comment and i'll get a sample tomorrow (if no one else does in the meantime).
To get the list of columns on a view do:
var views = model.GetObjects<TSqlView>(DacQueryScopes.UserDefined);
foreach (var v in views)
{
Console.WriteLine(v.Columns.Count());
}
This works for me with the 130 version of the Dac dll's.
To get to the details for computed columns you need to view the "ExpressionDependencies" on the column (see v.Columns) which is the same as for tables.
EDIT
So I have been having a play and there are some things that you just can't determine until runtime so the DacFx won't be able to figure out the type for those and they only way I know of it actually generating the record set and examining what you get back but there is some things we can do with computed columns, if we take this example:
create table [dbo].[the_table]
(
[Id] INT not null primary key,
[StringCol] varchar(234) not null,
[a] int,
[b] decimal,
[a_and_b] as [a] + [b]
)
for columns, Id, StringCol, a and b when we use the strongly typed dacfx we can get the column types by doing this:
var tables = model.GetObjects(DacQueryScopes.UserDefined);
foreach (var t in tables)
{
switch (c.ColumnType)
{
case ColumnType.Column:
ShowType(c.DataType);
break;
}
}
ShowType looks like this:
void ShowType(IEnumerable<ISqlDataType> types)
{
var builder = new StringBuilder();
foreach (var type in types)
{
var t = new TSqlDataType(type.Element);
builder.Append($"{t.SqlDataType.ToString()} ");
}
Console.Write(builder);
}
What we do is have a list of data types for each column, it might just be int or something like that but it is a list.
Now because we have a computed column, instead of just the data type(s) we have references to the underlying columns which we can get the data type(s) from:
void ShowDependencies(IEnumerable<ISqlModelElementReference> dependencies)
{
foreach (var dependency in dependencies)
{
if (dependency is TSqlColumnReference)
{
var column = new TSqlColumn(dependency.Element);
Console.Write(column.Name + " ");
ShowType(column.DataType);
}
}
}
to know when to call this version:
var tables = model.GetObjects<TSqlTable>(DacQueryScopes.UserDefined);
foreach (var t in tables)
{
Console.WriteLine($"table - {t.Name}");
foreach (var c in t.Columns)
{
Console.Write("\r\n" + c.Name.ToString() + " ");
switch (c.ColumnType)
{
case ColumnType.Column:
ShowType(c.DataType);
break;
case ColumnType.ComputedColumn:
Console.Write($"({c.Expression}) ");
ShowDependencies(c.ExpressionDependencies);
break;
for the sample table about we get this output:
table - [dbo].[the_table]
[dbo].[the_table].[Id] Int
[dbo].[the_table].[StringCol] VarChar
[dbo].[the_table].[a] Int
[dbo].[the_table].[b] Decimal
[dbo].[the_table].[a_and_b] ([a] + [b]) [dbo].[the_table].[a] Int [dbo].[the_table].[b] Decimal view - [dbo].[mutli_type]
we would then need to decide what the type is, as a guess sql will do an implicit cast as runtime to a decimal but at compile time I don't think it is known (happy to be corrected here!)
If we then take a view as an example:
create view the_view
as
select
*,
object_name(4) some_name,
123 as an_int
from
the_table
we have the columns from the base table which can be enumerated simply but the object_name and 123 are slightly harder, using the same code above but for views we get:
[dbo].[the_view].[Id] [dbo].[the_table].[Id] Int
[dbo].[the_view].[StringCol] [dbo].[the_table].[StringCol] VarChar
[dbo].[the_view].[a] [dbo].[the_table].[a] Int
[dbo].[the_view].[b] [dbo].[the_table].[b] Decimal
[dbo].[the_view].[a_and_b] [dbo].[the_table].[a_and_b]
[dbo].[the_view].[some_name] some_name = an_int =
[dbo].[the_view].[an_int] some_name = an_int =
So no type for the computed columns plus to get to the value for a_and_b we would need to further enumerate again to get the types we had above.
At this point what you have is a view with columns that point to functions / expressions and this is where is starts to get harder :) if you take the example above you could probably work out what object_name returned and determine that but when you get a view that is non-deterministic on either the data or data type what do you do?
If we take:
create view mutli_type
as
select case datepart(day, getdate())
when 1
then 100
when 2
then 'hello'
else
getdate()
end as multitype
depending on the day we get a different data type returned - double ouch.
If you really needed to know what the view returned you could get the select elements in the view and use the TSqlScript Dom to parse them into parts and try and infer each one, I have mocked up a sample that finds the getdate() function in this view to give you an idea of what you need to do but it isn't straightforward and I don't even want to consider stored procedures where you can pass in dynamic sql:
Full sample:
create table [dbo].[the_table]
(
[Id] INT not null primary key,
[StringCol] varchar(234) not null,
[a] int,
[b] decimal,
[a_and_b] as [a] + [b]
)
go
create view the_view
as
select *, object_name(4) some_name, 123 as an_int from the_table
go
create view mutli_type
as
select case datepart(day, getdate())
when 1
then 100
when 2
then 'hello'
else
getdate()
end as multitype
go
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.Dac.Extensions.Prototype;
using Microsoft.SqlServer.Dac.Model;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using ColumnType = Microsoft.SqlServer.Dac.Model.ColumnType;
namespace ConsoleApplication1
{
class Program
{
static void ShowType(IEnumerable<ISqlDataType> types)
{
var builder = new StringBuilder();
foreach (var type in types)
{
var t = new TSqlDataType(type.Element);
builder.Append($"{t.SqlDataType.ToString()} ");
}
Console.Write(builder);
}
static void ShowDependencies(IEnumerable<ISqlModelElementReference> dependencies)
{
foreach (var dependency in dependencies)
{
if (dependency is TSqlColumnReference)
{
var column = new TSqlColumn(dependency.Element);
Console.Write(column.Name + " ");
ShowType(column.DataType);
}
}
}
static void Main(string[] args)
{
var model = new TSqlTypedModel(#"path\Da.dacpac");
var views = model.GetObjects<TSqlView>(DacQueryScopes.UserDefined);
var tables = model.GetObjects<TSqlTable>(DacQueryScopes.UserDefined);
foreach (var t in tables)
{
Console.WriteLine($"table - {t.Name}");
foreach (var c in t.Columns)
{
Console.Write("\r\n" + c.Name.ToString() + " ");
switch (c.ColumnType)
{
case ColumnType.Column:
ShowType(c.DataType);
break;
case ColumnType.ComputedColumn:
Console.Write($"({c.Expression}) ");
ShowDependencies(c.ExpressionDependencies);
break;
case ColumnType.ColumnSet:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
foreach (var v in views)
{
Console.WriteLine($"view - {v.Name}");
foreach (var c in v.Columns)
{
Console.Write("\r\n" + c.Name.ToString() + " ");
var needDomParse = false;
switch (c.ColumnType)
{
case ColumnType.Column:
ShowType(c.DataType);
ShowDependencies(c.ExpressionDependencies);
break;
case ColumnType.ComputedColumn:
ShowType(c.DataType);
ShowDependencies(c.ExpressionDependencies);
if (!c.DataType.Any() && !c.ExpressionDependencies.Any())
{
needDomParse = true;
}
break;
case ColumnType.ColumnSet:
break;
default:
throw new ArgumentOutOfRangeException();
}
if (needDomParse)
{
//ouch
var create = new CreateViewStatement();
var parser = new TSql130Parser(false);
IList<ParseError> errors;
var fragment = parser.Parse(new StringReader(v.GetScript()), out errors);
var selectVisitor = new SelectVisitor();
fragment.Accept(selectVisitor);
foreach (var s in selectVisitor.Selects)
{
var spec = s.QueryExpression as QuerySpecification;
foreach (var element in spec.SelectElements)
{
var select = element as SelectScalarExpression;
if (select != null)
{
Console.Write(select.ColumnName.Value + " = ");
var caseExpression = select.Expression as SimpleCaseExpression;
if (caseExpression != null)
{
var func = caseExpression.ElseExpression as FunctionCall;
Console.WriteLine(func.FunctionName.Value);
}
}
}
}
}
}
}
}
}
internal class SelectVisitor : TSqlConcreteFragmentVisitor
{
public List<SelectStatement> Selects = new List<SelectStatement>();
public override void Visit(SelectStatement node)
{
Selects.Add(node);
base.Visit(node);
}
}
}
I hope it helps, I know it isn't a just do this but hopefully explains some of the challenges :)
Ed

How to search for empty strings in a text field with Entity Framework?

I'd like to know how can I search for empty strings when I'm using a text type field with Entity Framework.
I've looked the SQL query that Entity is generating and It's using LIKE to compare because It's searching in a text type field, so when I use .Equals(""), == "", == string.Empty, .Contains(""), .Contains(string.Empty), and everything else, It's returning all results because it sql query is like '' and the == command throws exception because It uses the = command that is not valid with text type field.
When I try to use .Equals(null), .Contains(null), == null, It returns nothing, because It is generating FIELD ISNULL command.
I already tried the .Lenght == 0 but It throws an exception...
This works for me:
public class POCO
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
static void Main(string[] args)
{
var pocos = new List<POCO>
{
new POCO { Id = 1, Name = "John", Description = "basic" },
new POCO { Id = 2, Name = "Jane", Description = "" },
new POCO { Id = 3, Name = "Joey", Description = string.Empty }
};
pocos.Where(x => x.Description == string.Empty)
.ToList()
.ForEach(x => Console.WriteLine($"{x.Id} {x.Name} {x.Description}"));
}
However the issue MAY BE that your T4 generated object is not fully realized with data you can use, if you are using Entity Framework. EG the translation from the database is not populating objects to interrogate correctly. I would just do an operation like this to see:
using (var context = new YOURCONTEXTNAME())
{
var persons = context.YOURDATABASEOBJECT.ToList();
persons.ForEach(x => Console.WriteLine($"{x.COLUMNINQUESTION}"));
}
If you are successfully having data in it, it should be retrieved. I would not use text if possible. Use a varchar(max) nvarchar(max) xml, whatever text will be deprecated eventually and is bad form so to speak to continue using at this point.
EDIT
Okay I see, the answer is you cannot interogate the object until it is fully realized when it is text. I did a test on my local database and created a context and tested it and sure enough you cannot do a '== string.empty', '== ""', or 'String.IsNullOrEmpty()' on a text. However you can do it once the object is materialized in a realized object. EG:
// Won't work as context does not understand type
//var persons = context.tePersons.Where(x => x.Description == string.Empty).ToList();
//Works fine as transformation got the object translated to a string in .NET
var start = context.tePersons.ToList();
var persons = start.Where(x => x.Description == String.Empty).ToList();
This poses a problem obviously as you need to get ALL your data potentially before performing a predicate. Not the best means by any measure. You could do a sql object for this instead then to do a function, proc, or view to change this.

Entity Framework : Create a model from Dictionary<TKey,TValue> to be mapped to a database table

Earlier I had a table named ApplicationConfiguration which simply had [Key],[Value] columns to store some config data. This was queried straight away using SQL queries.
Now I intend to make use of Entity Framework (EF) Code First approach to query this table. The specialty of this table is that the table will have only a fixed number of rows in its lifetime. Only the Value column can be updated.
So as per the code first approach, we have to first write our POCO classes with its properties that will be mapped to columns in the underlying table. However, I wish to have a Dictionary<> structure to represent these configuration KV pairs. My concern is, will EF be able to fire update queries against any updation to the the value of a particular pair.
Also since I am using Code First approach, I would want some seed data(i.e the fixed number of rows and its initial content) to the added after the table itself is created on the fly when the application is first executed.
If Dictionary<> cannot be used, please suggest some alternative. Thanks in advance.
Coded this way:
public class ApplicationConfiguration
{
public int Id { get; set; }
public string Key { get; set; }
public int Value { get; set; } // should be string, but I'm lazy
}
class Context : DbContext
{
internal class ContextInitializer : DropCreateDatabaseIfModelChanges<Context>
{
protected override void Seed(Context context)
{
var defaults = new List<ApplicationConfiguration>
{
new ApplicationConfiguration {Key = "Top", Value = 5},
new ApplicationConfiguration {Key = "Bottom", Value = 7},
new ApplicationConfiguration {Key = "Left", Value = 1},
new ApplicationConfiguration {Key = "Right", Value = 3}
};
// foreach (var c in defaults)
// context.ConfigurationMap.Add(c.Key, c); // by design, no IReadOnlyDictionary.Add
foreach (var c in defaults)
context.ApplicationConfigurations.Add(c);
base.Seed(context);
}
}
public Context()
{
Database.SetInitializer(new ContextInitializer());
}
private IDbSet<ApplicationConfiguration> ApplicationConfigurations
{
get { return Set<ApplicationConfiguration>(); }
}
public IReadOnlyDictionary<string, ApplicationConfiguration> ConfigurationMap
{
get { return ApplicationConfigurations.ToDictionary(kvp => kvp.Key, kvp => kvp); }
}
}
Used this way:
using (var context = new Context())
{
ReadConfigurationOnly(context.ConfigurationMap);
}
using (var context = new Context())
{
ModifyConfiguration(context.ConfigurationMap);
context.SaveChanges();
}
static void ReadConfigurationOnly(IReadOnlyDictionary<string, ApplicationConfiguration> configuration)
{
foreach (var k in configuration.Keys)
Console.WriteLine("{0} = {1}", k, configuration[k].Value);
}
static void ModifyConfiguration(IReadOnlyDictionary<string, ApplicationConfiguration> configuration)
{
foreach (var k in configuration.Keys)
configuration[k].Value++; // this is why I was lazy, using an int for a string
}
So, I wrote it up this way — using an int Value property rather than a string — just so I could run the "Used this way" code over and over, and see the database update each time, without having to come up with some other way to change Value in an interesting way.
It's not quite as nifty here to use a IReadOnlyDictionary<string, ApplicatonConfiguration> instead of a IReadOnlyDictionary<string, string>, the way we'd really like, but that's more than made up for by the fact that we can easily modify our collection values without resorting to a clumsier Set method taking a dictionary as input. The drawback, of course, is that we have to settle for configuration[key].Value = "new value" rather than configuration[key] = "new value", but — as I say — I think it's worth it.
EDIT
Dang! I wrote this code up specifically to answer this question, but I think I like it so much, I'm going to add it to my bag of tricks ... this would fit in really well when my company goes from local databases to Azure instances in the cloud, and the current app.config has to go into the database.
Now all I need is a ContextInitializer taking a System.Configuration.ConfigurationManager as a ctor parameter in order to seed a new database from an existing app.config ...
I don't think you can map a table directly to a Dictionary; you will probably have to write your own wrapper to fill a dictionary from the table and update it back to the DB. Entities are each a row of a given table... Something like this (untested):
public Dictionary<string, string> GetDictionary()
{
Dictionary<string, string> dic = new Dictionary<string, string>();
using (var db = new Context())
{
var configs = db.ApplicationConfiguration.Select();
foreach (var entry in configs)
{
dic.Add(config.Key, config.Value);
}
}
return dic;
}
public void SaveConfig(Dictionary<string, string> dic)
{
using (var db = new Context())
{
foreach (KeyValuePair kvp in dic)
{
if (!db.ApplicationConfiguration.First(a => a.Key == kvp.Key).Value == kvp.Value)
{
var ac = new ApplicationConfiguration();
ac.Key = kvp.Key;
ac.Value = kvp.Value;
db.Entry(ac).State = EntityState.Modified;
}
}
db.SaveChanges();
}
}
For your second question, you want to use the Seed() method to add initial values to the database. See here for an example implementation.

Is it possible to use `SqlDbType.Structured` to pass Table-Valued Parameters in NHibernate?

I want to pass a collection of ids to a stored procedure that will be mapped using NHibernate. This technique was introduced in Sql Server 2008 ( more info here => Table-Valued Parameters ). I just don't want to pass multiple ids within an nvarchar parameter and then chop its value on the SQL Server side.
My first, ad hoc, idea was to implement my own IType.
public class Sql2008Structured : IType {
private static readonly SqlType[] x = new[] { new SqlType(DbType.Object) };
public SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping) {
return x;
}
public bool IsCollectionType {
get { return true; }
}
public int GetColumnSpan(NHibernate.Engine.IMapping mapping) {
return 1;
}
public void NullSafeSet(DbCommand st, object value, int index, NHibernate.Engine.ISessionImplementor session) {
var s = st as SqlCommand;
if (s != null) {
s.Parameters[index].SqlDbType = SqlDbType.Structured;
s.Parameters[index].TypeName = "IntTable";
s.Parameters[index].Value = value;
}
else {
throw new NotImplementedException();
}
}
#region IType Members...
#region ICacheAssembler Members...
}
No more methods are implemented; a throw new NotImplementedException(); is in all the rest. Next, I created a simple extension for IQuery.
public static class StructuredExtensions {
private static readonly Sql2008Structured structured = new Sql2008Structured();
public static IQuery SetStructured(this IQuery query, string name, DataTable dt) {
return query.SetParameter(name, dt, structured);
}
}
Typical usage for me is
DataTable dt = ...;
ISession s = ...;
var l = s.CreateSQLQuery("EXEC some_sp #id = :id, #par1 = :par1")
.SetStructured("id", dt)
.SetParameter("par1", ...)
.SetResultTransformer(Transformers.AliasToBean<SomeEntity>())
.List<SomeEntity>();
Ok, but what is an "IntTable"? It's the name of SQL type created to pass table value arguments.
CREATE TYPE IntTable AS TABLE
(
ID INT
);
And some_sp could be like
CREATE PROCEDURE some_sp
#id IntTable READONLY,
#par1 ...
AS
BEGIN
...
END
It only works with Sql Server 2008 of course and in this particular implementation with a single-column DataTable.
var dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
It's POC only, not a complete solution, but it works and might be useful when customized. If someone knows a better/shorter solution let us know.
A simpler solution than the accepted answer would be to use ADO.NET. NHibernate allows users to enlist IDbCommands into NHibernate transactions.
DataTable myIntsDataTable = new DataTable();
myIntsDataTable.Columns.Add("ID", typeof(int));
// ... Add rows to DataTable
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
IDbCommand command = new SqlCommand("StoredProcedureName");
command.Connection = session.Connection;
command.CommandType = CommandType.StoredProcedure;
var parameter = new SqlParameter();
parameter.ParameterName = "IntTable";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Value = myIntsDataTable;
command.Parameters.Add(parameter);
session.Transaction.Enlist(command);
command.ExecuteNonQuery();
}
For my case, my stored procedure needs to be called in the middle of an open transaction.
If there is an open transaction, this code works because it is automatically reusing the existing transaction of the NHibernate session:
NHibernateSession.GetNamedQuery("SaveStoredProc")
.SetInt64("spData", 500)
.ExecuteUpdate();
However, for my new Stored Procedure, the parameter is not as simple as an Int64. It's a table-valued-parameter (User Defined Table Type)
My problem is that I cannot find the proper Set function.
I tried SetParameter("spData", tvpObj), but it's returning this error:
Could not determine a type for class: …
Anyways, after some trial and error, this approach below seems to work.
The Enlist() function is the key in this approach. It basically tells the SQLCommand to use the existing transaction. Without it, there will be an error saying
ExecuteNonQuery requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction…
using (SqlCommand cmd = NHibernateSession.Connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = "MyStoredProc";
NHibernateSession.Transaction.Enlist(cmd); // Because there is a pending transaction
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#wiData", SqlDbType.Structured) { Value = wiSnSqlList });
int affected = cmd.ExecuteNonQuery();
}
Since I am using the SqlParameter class with this approach, SqlDbType.Structured is available.
This is the function where wiSnList gets assigned:
private IEnumerable<SqlDataRecord> TransformWiSnListToSql(IList<SHWorkInstructionSnapshot> wiSnList)
{
if (wiSnList == null)
{
yield break;
}
var schema = new[]
{
new SqlMetaData("OriginalId", SqlDbType.BigInt), //0
new SqlMetaData("ReportId", SqlDbType.BigInt), //1
new SqlMetaData("Description", SqlDbType.DateTime), //2
};
SqlDataRecord row = new SqlDataRecord(schema);
foreach (var wi in wiSnList)
{
row.SetSqlInt64(0, wi.OriginalId);
row.SetSqlInt64(1, wi.ShiftHandoverReportId);
if (wi.Description == null)
{
row.SetDBNull(2);
}
else
{
row.SetSqlString(2, wi.Description);
}
yield return row;
}
}
You can pass collections of values without the hassle.
Example:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate will create a parameter for each element.

Resources