Retrieving XML from database with Dapper - dapper

I am using Dapper to query a table that includes an XML field:
CREATE TABLE Workflow
(
Guid uniqueidentifier not null,
State xml not null
)
which is then mapped to a property of type XDocument:
public class Workflow
{
public Guid InstanceId { get;set; }
public XDocument State { get;set; }
}
but when I try to query the table, I get the following error:
Error parsing column 1 (State= - String)
at Dapper.SqlMapper.ThrowDataException(Exception ex, Int32 index, IDataReader reader, Object value) in d:\\Dev\\dapper-dot-net\\Dapper NET40\\SqlMapper.cs:line 4045
at Deserialize038b29f4-d97d-4b62-b45b-786bd7d50e7a(IDataReader )
at Dapper.SqlMapper.<QueryImpl>d__11`1.MoveNext() in d:\\Dev\\dapper-dot-net\\Dapper NET40\\SqlMapper.cs:line 1572
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in d:\\Dev\\dapper-dot-net\\Dapper NET40\\SqlMapper.cs:line 1443
at MyProject.DapperBase.Query[TResult](String command, DynamicParameters parameters, IDbTransaction transaction, Boolean buffered, Int32 commandTimeout) in d:\\MyProject\\DapperBase.cs:line 122
at MyProject.WorkflowData.Get(Guid identifier) in d:\\MyProject\\WorkflowData.cs:line 41
at MyProject.WorkflowLogic.Save(Workflow workflow) in d:\\MyProject\\WorkflowLogic.cs:line 34
at MyProject.WorkflowsController.Save(Guid id, WorkflowRequest request) in d:\\MyProject\\WorkflowsController.cs:line 97
InnerException: Invalid cast from 'System.String' to 'System.Xml.Linq.XDocument'.
at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType)
at Deserialize038b29f4-d97d-4b62-b45b-786bd7d50e7a(IDataReader )
Other than modifying my POCO to use a string datatype and then convert the string into an XDocument elsewhere, is there a way of getting Dapper to correctly deserialise the XML from the database?

In the end, I just brute-forced it:
public class Workflow
{
public Guid InstanceId { get;set; }
public XDocument StateIn { set { State = value.ToString(); } }
public string State { get;set; }
public XDocument StateOut { get { return XDocument.Parse(State); } }
}
Dapper plays with the State value, and I just set the value on StateIn and read it off StateOut. I feel a little bit dirty coming up with a solution like this, but hey, it works.

Perhaps creating a custom type handler can help? Something like:
public class XDocumentTypeHandler : SqlMapper.TypeHandler<XDocument>
{
public override void SetValue(IDbDataParameter parameter, XDocument value)
{
// set value in db parameter.
}
public XDocument Parse(object value)
{
// parse value from db to an XDocument.
}
}
You have to add the type handler with SqlMapper.AddTypeHandler().
See a sample implementation.

Related

how to deserialize this json on vb.net?

i have this data in json format:
{
"status":{
"timestamp":"2021-01-07T17:13:48.471Z",
"error_code":0,
"error_message":null,
"elapsed":12,
"credit_count":1,
"notice":null,
"total_count":4115
},
"data":[
{
"id":1,
"name":"Bitcoin",
"symbol":"BTC",
"slug":"bitcoin",
"num_market_pairs":9732,
"date_added":"2013-04-28T00:00:00.000Z",
"tags":[
"mineable",
"pow",
"sha-256",
"store-of-value",
"state-channels"
],
"max_supply":21000000,
"circulating_supply":18593700,
"total_supply":18593700,
"platform":null,
"cmc_rank":1,
"last_updated":"2021-01-07T17:12:02.000Z",
"quote":{
"USD":{
"price":39362.167971369854,
"volume_24h":78135138852.86674,
"percent_change_1h":2.61231359,
"percent_change_24h":12.47756102,
"percent_change_7d":36.98956944,
"market_cap":731888342609.2596,
"last_updated":"2021-01-07T17:12:02.000Z"
}
}
},
{
"id":1027,
"name":"Ethereum",
"symbol":"ETH",
"slug":"ethereum",
"num_market_pairs":5934,
"date_added":"2015-08-07T00:00:00.000Z",
"tags":[
"mineable",
"pow",
"smart-contracts"
],
"max_supply":null,
"circulating_supply":114155463.749,
"total_supply":114155463.749,
"platform":null,
"cmc_rank":2,
"last_updated":"2021-01-07T17:12:02.000Z",
"quote":{
"USD":{
"price":1261.606649005652,
"volume_24h":39345516218.36576,
"percent_change_1h":3.1558102,
"percent_change_24h":7.80752209,
"percent_change_7d":71.81090319,
"market_cap":144019292086.06207,
"last_updated":"2021-01-07T17:12:02.000Z"
}
}
}
]
}
and, in vb.net, with special paste i got this definition:
Public Class Rootobject
Public Property status As Status
Public Property data() As Datum
End Class
Public Class Status
Public Property timestamp As Date
Public Property error_code As Integer
Public Property error_message As Object
Public Property elapsed As Integer
Public Property credit_count As Integer
Public Property notice As Object
Public Property total_count As Integer
End Class
Public Class Datum
Public Property id As Integer
Public Property name As String
Public Property symbol As String
Public Property slug As String
Public Property num_market_pairs As Integer
Public Property date_added As Date
Public Property tags() As String
Public Property max_supply As Integer
Public Property circulating_supply As Integer
Public Property total_supply As Integer
Public Property platform As Object
Public Property cmc_rank As Integer
Public Property last_updated As Date
Public Property quote As Quote
End Class
Public Class Quote
Public Property USD As USD
End Class
Public Class USD
Public Property price As Single
Public Property volume_24h As Single
Public Property percent_change_1h As Single
Public Property percent_change_24h As Single
Public Property percent_change_7d As Single
Public Property market_cap As Single
Public Property last_updated As Date
End Class
but when i try with this command to deserialize
Dim m As IEnumerable(Of Rootobject) =
JsonConvert.DeserializeObject(Of IEnumerable(Of Rootobject))(res)
i get this error:
Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON object
(e.g. {"name":"value"}) into type
'System.Collections.Generic.IEnumerable`1[CoinMarketCap.Rootobject]'
because the type requires a JSON array (e.g. [1,2,3])
to deserialize correctly. To fix this error either change the JSON to a
JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a
normal .NET type (e.g. not a primitive type like integer, not a
collection type like an array or List) that can be deserialized from a
JSON object. JsonObjectAttribute can also be added to the type to force
it to deserialize from a JSON object. Path 'status', line 1, position 10.'
im stucked... any idea?
Thanks in advance.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object.
In other words, change
Dim m As IEnumerable(Of Rootobject) = JsonConvert.DeserializeObject(Of IEnumerable(Of Rootobject))(res)
to
Dim m As Rootobject = JsonConvert.DeserializeObject(Of Rootobject)(res)

Dapper return specfic field from query

I am working on connecting to SQL database and setting variables in my C# application to match returned values from a Dapper Query.
I am able to return the correct row information (I used a datagridview to show that i get the correct row, and when i debug i see the right data) but how do I set a program variable to just one of the columns? here is some code showing my process
Connect & run stored procedure:
using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
List<User> userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>();
Variables.UserID = txt_userid.Text.Trim();
datagridview1.DataSource = userinfo; //this displays the right information, however I want to store the information as a public variable(like above)
Variables.Userfull = userinfo; //when debugging this shows I have the right information but its all columns of user
User Class:
class User
{
public string UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int AccessLevel { get; set; }
public int FirstUse { get; set; }
public string Password { get; set; }
public int LoginCounter { get; set; }
public string LastLogin { get; set; }
public string FirstLogin { get; set; }
public string UserFullName { get; set; }
}
EDIT
per answer, I have tried to switch the command to executescalar with this code:
var userinfo = conn.ExecuteScalar<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure);
This allows me to set
Variables.UserID = userinfo.UserID;
without red-squiggle lines however during run-time i get an error on the execute scalar of "System.InvalidCastException: 'Invalid cast from 'System.String' to 'Name_Space.User'.'
I have checked the User Class to ensure the data type matches with the Database and i see no problems there, I'm not sure what I'm doing wrong?
If you want value from only one column of only one row, use ExecuteScalar which is similar to ADO.NET ExecuteScalar. It will return object with which you have to deal further.
string sql = "SELECT COL1 FROM Table1 WHERE ID = 1";
//OR
//string sql = "SELECT TOP 1 COL1 FROM Table1";
//OR similar
object colValue = conn.ExecuteScalar(sql, ....);
If matching record not found, return value will be null.
Check Dapper documentation for other generic variants of method:
public static T ExecuteScalar<T>(this IDbConnection cnn, CommandDefinition command);
If you want single column from multiple rows, the way you are doing this now is correct. Just modify your SQL query/Stored Procedure to return the same. Dapper will only map returned column. All other properties in your User will remain unassigned.
If you simply want to assign values to Variables.UserID instead of binding those using DataSource, then just do that like:
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
//and so on....
So, complete code will be something like below:
using(SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
User userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>().First();
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
}

Value is not a convertible object

I have a simple query and Poco that I'm using with Dapper like so:
var jc = this.dbConnection.ExecuteScalar<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id});
Poco:
public class JcUser
{
public string UserName { get; set; }
public string Password { get; set; }
public int CustomerId{ get; set; }
public int TextProfileId { get; set; }
public int UxProfileId { get; set; }
}
When this executes it throws an exception with the message
Value is not a convertible object: System.String to JcUser
The stack trace ends up at: at System.Convert.ToType (System.Object value, System.Type conversionType, IFormatProvider provider, Boolean try_target_to_type)
Any ideas why its doing this?
Thanks
UPDATE: Using var jc = this.dbConnection.Query<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id}).First(); appears to work. I also realise I'm a moron and ExecuteScalar is only for one value. However, is my update the best way to retrieve only one row?
ExecuteScalar maps to the ADO.NET method of the same name. It returns at most one cell: one grid, one row, one column. As such, it is not intended for use with complex objects, and cannot work correctly in your case as you have multiple columns.
Dapper assumes you would only use that with simple types like int, string etc.
In your case, use:
var jc = this.dbConnection.Query<JcUser>(
sql, args).SingleOrDefault();
If you want to avoid a hidden List<> allocation you could also pass buffered: false.

How can I make Dapper.NET throw when result set has unmapped columns?

Using the example code below as context... When I run this query I get the 'Id' field coming back as default value (which is 0 for an int). I would like to tell dapper to run in a manner where it would throw an exception if there is a column in the result set that does not get mapped to a property on my result object. (I understand that the issue is just that I need to remove the extra 'd' in the SQL query but I'm interested in having this expose itself more explicitly)
I've been unable to find anything on this topic. Please let me know if this is even possible with Dapper.
Thanks in advance (besides this issue, and for anyone who hasn't taken the plunge, Dapper really is the greatest thing since sliced bread!).
class CustomerRecord
{
public int Id { get; set; }
public string Name { get; set; }
}
CustomerRecord[] GetCustomerRecords()
{
CustomerRecord[] ret;
var sql = #"SELECT
CustomerRecordId AS Idd,
CustomerName as Name
FROM CustomerRecord";
using (var connection = new SqlConnection(this.connectionString))
{
ret = connection.Query<CustomerRecord>(sql).ToArray();
}
return ret;
}
You could create your own type map where you use Dapper's DefaultTypeMap and throw an exception when it cannot find the member:
public class ThrowWhenNullTypeMap<T> : SqlMapper.ITypeMap
{
private readonly SqlMapper.ITypeMap _defaultTypeMap = new DefaultTypeMap(typeof(T));
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _defaultTypeMap.FindConstructor(names, types);
}
public ConstructorInfo FindExplicitConstructor()
{
return _defaultTypeMap.FindExplicitConstructor();
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return _defaultTypeMap.GetConstructorParameter(constructor, columnName);
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
var member = _defaultTypeMap.GetMember(columnName);
if (member == null)
{
throw new Exception();
}
return member;
}
}
Downside of this, is that you have to configure all the type maps for every entity:
SqlMapper.SetTypeMap(typeof(CustomerRecord), typeof(ThrowWhenNullTypeMap<CustomerRecord>));
This could be configured using reflection, however.
I came here after I solved this same problem for the IEnumerable<dynamic> methods in Dapper. Then I found the proposal to solve the issue for Query<T>; but that doesn't seem to be going anywhere.
My answer builds on the answer proposed by #HenkMollema, and uses his class in the solution, so credit to him for that...
To solve the IEnumerable<dynamic> scenario, I had created a "SafeDynamic" class (follow the link above to see that). I refactored the static "Create" method into an extension method:
public static class EnumerableDynamicExtensions
{
public static IEnumerable<dynamic> Safe(this IEnumerable<dynamic> rows)
{
return rows.Select(x => new SafeDynamic(x));
}
}
and then I created a DapperExtensions class to provide 'Safe' versions of Query and Read (Read is used after QueryMultiple), to give me...
internal static class DapperExtensions
{
public static IEnumerable<dynamic> SafeQuery(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
return cnn.Query(sql, param, transaction, buffered, commandTimeout, commandType).Safe();
}
public static IEnumerable<dynamic> SafeRead(this SqlMapper.GridReader gridReader, bool buffered = true)
{
return gridReader.Read(buffered).Safe();
}
}
So to solve this issue I added a "SafeQuery<T>" method to DapperExtensions, which takes care of setting up that type mapping for you:
private static readonly IDictionary<Type, object> TypesThatHaveMapper = new Dictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
if (TypesThatHaveMapper.ContainsKey(typeof(T)) == false)
{
SqlMapper.SetTypeMap(typeof(T), new ThrowWhenNullTypeMap<T>());
TypesThatHaveMapper.Add(typeof(T), null);
}
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
So if the original poster changes the call to Query to become SafeQuery, it should do what he requested
Edit 25/1/17
Improvements to avoid threading issues on the static dictionary:
private static readonly ConcurrentDictionary<Type, object> TypesThatHaveMapper = new ConcurrentDictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
TypesThatHaveMapper.AddOrUpdate(typeof(T), AddValue, UpdateValue);
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
private static object AddValue(Type type)
{
SqlMapper.SetTypeMap(type, XXX); // Apologies... XXX is left to the reader, as my implementation has moved on significantly.
return null;
}
private static object UpdateValue(Type type, object existingValue)
{
return null;
}
I'd like to expand on #Richardissimo 's answer by providing a visual studio project that includes his "SafeQuery" extention to Dapper, wrapped up nice and neat and tested.
https://github.com/LarrySmith-1437/SafeDapper
I use this in all my projects now to help keep the DAL clean of mismapped data, and felt the need to share. I would have posted up a Nuget, but the dependency on Dapper itself makes it much easier to post the project where consumers can update the reference to the Dapper version they want. Consume in good health, all.
Based on this thread and some other resources on SO, I've created an extension method without any custom mapper. What I needed was to throw when some property of my DTO was not set because for example SQL query has some column missing in SELECT statement.
This way my DTO would be set with default property silently and that's kinda dangerous.
The code can be simplified a little by not checking firstly for all properties being present in result, but throwing exception in the last Select call where we could iterate through properties of our type and check if query result has this property as well.
public static class Extensions
{
public static async Task<IEnumerable<T>> SafeQueryAsync<T>(
this IDbConnection cnn,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = default(int?),
CommandType? commandType = default(CommandType?))
where T : new()
{
Dictionary<string, PropertyInfo> propertySetters = typeof(T)
.GetProperties().Where(p => p.CanRead && p.CanWrite)
.ToDictionary(p => p.Name.ToLowerInvariant(), p => p);
HashSet<string> typeProperties = propertySetters
.Select(p => p.Key)
.ToHashSet();
var rows = (await cnn.QueryAsync(sql, param, transaction, commandTimeout, commandType)).ToArray();
if (!rows.Any())
{
return Enumerable.Empty<T>();
}
var firstRow = rows.First();
HashSet<string> rowColumns = ((IDictionary<string, object>) firstRow)
.Select(kvp=>kvp.Key.ToLowerInvariant()).ToHashSet();
var notMappedColumns = typeProperties.Except(rowColumns).ToArray();
if (notMappedColumns.Any())
{
throw new InvalidOperationException(
$"Not all type properties had corresponding columns in SQL query. Query result lacks [{string.Join(", ", notMappedColumns)}]");
}
return rows.Select(row =>
{
IDictionary<string, object> rowDict = (IDictionary<string, object>) row;
T instance = new T();
rowDict.Where(o => propertySetters.ContainsKey(o.Key.ToLowerInvariant()))
.ToList().ForEach(o => propertySetters[o.Key.ToLowerInvariant()].SetValue(instance, o.Value));
return instance;
}).AsEnumerable();
}
}

Specified Cast is not Invalid (Enum with int value, Dapper)

I have a class with a (simple, first cut) implementation of user roles:
class User {
public Role Role { get; set; }
// ...
public User() { this.Role = Role.Normal; }
public void Save() { Membership.CreateUser(...) } // System.Web.Security.Membership
}
enum Role : int {
Invalid = 0,
Normal = 1,
SuperUser = 4096
}
Before adding the role, everything worked fine (if that matters).
Now, when I try to fetch users, this line fails:
toReturn = conn.Query<User>("SELECT TOP 1 * FROM dbo.UserProfile WHERE 1=1");
The stack trace (from ELMAH):
System.Data.DataException: Error parsing column 2 (Role=1 - Int16) ---> System.InvalidCastException: Specified cast is not valid.
at Deserialize06df745b-4fad-4d55-aada-632ce72e3607(IDataReader )
--- End of inner exception stack trace ---
at Dapper.SqlMapper.ThrowDataException(Exception ex, Int32 index, IDataReader reader) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 2126
at Deserialize06df745b-4fad-4d55-aada-632ce72e3607(IDataReader )
at Dapper.SqlMapper.<QueryInternal>d__d`1.MoveNext() in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 827
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 770
In the database, the column type for Role is smallint.
I'm using Dapper 1.12.1 from NuGet.
Gah. The answer was to make the database and class definitions match.
For smallint (which is what MigratorDotNet generated for me), I needed the enum to derive from short, not int. Everything works now.
Possibly useful Google Code issue: https://code.google.com/p/dapper-dot-net/issues/detail?id=32

Resources