Incorrect mapping with Dapper, .NET Core and Postgres - c#

Thanks in advance. I'm seeing an issue with my mapping using Postgres and Dapper. I'm trying to map an Organization object, which has a one-to-many relationship with the Location object. The following is my mapping code:
public Organization GetOrganizationById(long id)
{
var query = #"SELECT
o.organization_id,
o.guid,
o.name,
o.is_active,
o.created,
l.location_id,
l.org_id,
l.guid,
l.internal_identifier,
l.name,
l.address,
l.city,
l.state,
l.zip,
l.phonenumber,
l.is_active,
l.opendate,
l.closedate,
l.created
FROM organization AS o
INNER JOIN location as l
ON o.organization_id = l.org_id
WHERE o.organization_id = #Id";
using (var con = new NpgsqlConnection(_connectionString))
{
var orgResult = con.Query<Organization, List<Location>, Organization>(
query,
(org, locations) =>
{
org.Locations = locations;
return org;
},
new {Id = id},
splitOn: "location_id").FirstOrDefault();
return orgResult;
}
}
I have the following objects created:
public class Organization
{
public long OrganizationId { get; set; }
public Guid Guid { get;set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTime Created { get; set; }
//[JsonIgnore]
public List<Location> Locations { get; set; }
}
and
public class Location
{
public long LocationId { get; set; }
public long OrgId { get; set; }
public Guid Guid { get; set; }
public string InternalIdentifier { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string PhoneNumber { get; set; }
public bool IsActive { get; set; }
public DateTime OpenDate { get; set; }
public DateTime? CloseDate { get; set; }
public DateTime Created { get; set; }
[JsonIgnore]
public Organization Organization { get; set; }
}
Now the issue is, when I get the results it's not even close to accurate. Though the query when copied into a SQL client it's returning the correct results.
Here's what I see in the JSON response along with what's incorrect:
{
"organizationId": 0, // This is incorrect. The organization ID is one (this appears to be the default int/long)
"guid": "4fc55437-8497-4388-be48-c6b8c5dfee93", // This is correct
"name": "TestOrg", // This is correct
"isActive": false, // This is incorrect. Both locations are active, as is the organization
"created": "2021-01-27T05:20:42.287925", // This is correct
"locations": [] // Incorrect, there should be two locations present
}
Anyway, is there anything you think I'm missing with the mapping that would prevent these records from not mapping to their POCOs correctly?

Dapper requires your model property names to mirror your table (not case sensitive).
You can try using the [ColumnName()] attribute above your properties. I've heard this works for some but it didn't for me. I ended up using AutoMapper and created a mapper object.
[ColumnName("SomeFieldName")]
public string SomeProperty { get; set; }
Before you look into that (cause it's a pain) try using Dapper's QueryMultiple() method.
Dapper's Query Multiple
You may have to play around with it a bit. It's been awhile since I've written the full QueryMultiple() method the Dapper way. I made a wrapper class to containerize my dapper functions (I just containerize all 3rd party packages incase of exceptions or something). Here's one of my methods using QueryMultiple(). to return 2 data sets using out parameters. You can adjust this to return as may sets as you need.
/// <summary>
/// Executes a query with multiple results and stores the result sets in the out parameters.
/// </summary>
/// <typeparam name="T1">The type of the first result set.</typeparam>
/// <typeparam name="T2">The type of the second result set.</typeparam>
/// <typeparam name="P">The parameter type. Generally an autonomous/dynamic object, a <see cref="DynamicParameters"/>, or an <see cref="ExpandoObject"/>.</typeparam>
/// <param name="sql">The SQL query string or stored procedure.</param>
/// <param name="parameters">The parameter(s) for the stored procedure.</param>
/// <param name="results1">The first result set.</param>
/// <param name="results2">The second result set.</param>
/// <param name="queryType">
/// <para>The query's command type.</para>
/// Defaults to <strong><see cref="CommandType.StoredProcedure"/></strong>.
/// </param>
public void ExecuteQueryMultiple<T1, T2, P>(string sql, P parameters,
ConnStringKey connectionKey, // This is a personal app setting enum...
out List<T1> results1, out List<T2> results2,
CommandType queryType = CommandType.StoredProcedure)
{
using (IDbConnection connection = new
SqlConnection(configurations.GetConnectionString(connectionKey)))
{
using (SqlMapper.GridReader sqlReader = connection.QueryMultiple(sql,
parameters, commandType: queryType, commandTimeout: ConnectionTimeout))
{
results1 = sqlReader.Read<T1>().AsList();
results2 = sqlReader.Read<T2>().AsList();
}
}
}
You can use something similar if your SQL was like this:
SELECT
o.organization_id,
o.guid,
o.name,
o.is_active,
o.created,
FROM organization AS o
WHERE o.organization_id = #Id
SELECT
l.location_id,
l.org_id,
l.guid,
l.internal_identifier,
l.name,
l.address,
l.city,
l.state,
l.zip,
l.phonenumber,
l.is_active,
l.opendate,
l.closedate,
l.created
FROM location AS l
WHERE l.org_id = #Id
Of course, then you'd have to aggregate the two result sets in the code.
Usage:
var someParams = new { Id = someId };
sql = "your sql";
_sql.ExecuteQueryMultiple<Organization, Location, dynamic>(
sql, someParams,
// this is an enum of mine that ties back to my appsettings.json or app.config file to get the connection string
MyConfigurationConnectionKey,
out List<Organization> organizations,
out List<Location> locations,
CommandType.Text);
// Aggregate the two data sets...
organizations.Locations = locations; // or however you need to do it

Related

Converting Kusto client response to list of objects returns empty objects

I am trying to convert the response received from running a Kusto query in which I am retrieving the schema of a table. My Kusto query looks like this:
tableName | getschema
The response for such a query, as seen in the Kusto Explorer looks something like this (incomplete)
Back in my C# code I have defined the following class:
public class DatasetColumn
{
/// <summary>
/// Name of the column
/// </summary>
public string? ColumnName { get; set; }
/// <summary>
/// Position of this column within the schema
/// </summary>
public int ColumnOrdinal { get; set; }
/// <summary>
/// Type of data contained in this column
/// </summary>
public string? DataType { get; set; }
/// <summary>
/// Type of data contained in this column
/// </summary>
public string? ColumnType { get; set; }
}
And I am attempting to just retrieve a list of DatasetColumn objects from the IDataReader using a method defined in Kusto.Cloud.Data.ExtendedDataReader named ToEnumerable:
using (var client = storageClientFactory.CreateCslQueryProvider(new Kusto.Data.KustoConnectionStringBuilder(connectionDetails!.ClusterUrl)))
{
var schemaQuery = KustoSchemaQueryBuilder.GetKustoSchemaQuery(connectionDetails!.EntityName);
var clientRequestProperties = new ClientRequestProperties() { ClientRequestId = Guid.NewGuid().ToString() };
var queryTask = client.ExecuteQueryAsync(connectionDetails.DatabaseName, schemaQuery, clientRequestProperties);
using (var reader = await queryTask.ConfigureAwait(false))
{
using (reader)
{
return reader.ToEnumerable<DatasetColumn>().ToArray();
}
}
}
Hoping that I will get an array of DatasetColumn objects. I do in fact get a list of DatasetObjects, the number of objects in the list corresponds to the number of results that I am expecting but all the objects have all the fields set to the default values (nulls for strings and 0 for ints).
What am I doing wrong and how do I get this method to return properly initialized objects? Am I misunderstanding the point of this extension method?
Later edit: I am adding a minimal program to reproduce the issue
using Kusto.Cloud.Platform.Data;
using Kusto.Data;
using Kusto.Data.Net.Client;
using Newtonsoft.Json;
namespace ConsoleApp1
{
class DatasetColumn
{
public string? ColumnName { get; set; }
public int ColumnOrdinal { get; set; }
public string? DataType { get; set; }
public string? ColumnType { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
var sb = new KustoConnectionStringBuilder("https://help.kusto.windows.net/Samples; Fed=true; Accept=true");
using (var client = KustoClientFactory.CreateCslQueryProvider(sb))
{
using (var result = client.ExecuteQuery("StormEvents | getschema"))
{
var cols = result.ToEnumerable<DatasetColumn>().ToArray();
Console.WriteLine(JsonConvert.SerializeObject(cols));
}
}
}
}
}
the underlying implementation for the extension method of the DataReader that your code is using, and that is provided as part of the client library, doesn't currently support properties or private fields.
You could either change the properties in the class DatasetColumn to fields, or write your own implementation.
e.g., replace this:
public int ColumnOrdinal { get; set; }
with this:
public int ColumnOrdinal;

DataContractJsonSerializer not working

I use the DataContractJsonSerializer to convert a JSON string into a class, but always return an empty object.
I tested the string with the' JSON Viewer' extension in Notepad, is valid. Searched for a bug for a long time and compared other examples.
This is my JSON string in shortened form:
{
"error":[],
"result": {
"BCH": {"aclass":"currency","altname":"BCH","decimals":10,"display_decimals":5},
"DASH": {"aclass":"currency","altname":"test"}
}
}
The classes GetAssetInfoResponse and AssetInfo contain properties with DataMember attributes, but the property Result (after Deserialize) does not contain any objects.
[DataContract]
[KnownType(typeof(AssetInfo))]
public class GetAssetInfoResponse
{
[DataMember(Name = "error")]
public List<string> Error { get; set; }
[DataMember(Name = "result")]
public List<Dictionary<string, AssetInfo>> Result { get; set; }
}
[DataContract]
public class AssetInfo
{
/// <summary>
/// Alternate name.
/// </summary>
[DataMember(Name = "altname")]
public string Altname { get; set; }
/// <summary>
/// Asset class.
/// </summary>
[DataMember(Name = "aclass")]
public string Aclass { get; set; }
/// <summary>
/// Scaling decimal places for record keeping.
/// </summary>
[DataMember(Name = "decimals")]
public int Decimals { get; set; }
/// <summary>
/// Scaling decimal places for output display.
/// </summary>
[DataMember(Name = "display_decimals")]
public int DisplayDecimals { get; set; }
}
This is my test code:
var stream = new MemoryStream(Encoding.Unicode.GetBytes(strName))
{
Position = 0
};
var serializer = new DataContractJsonSerializer(typeof(GetAssetInfoResponse));
GetAssetInfoResponse test = (GetAssetInfoResponse)serializer.ReadObject(stream);
Console.ReadLine();
I can't use the Newtonsoft.Json extension because the project should not contain any external dependencies.
Is there another way to transfer JSON strings into classes?
Thank you for your time
You declare Result as a List<Dictionary<string, AssetInfo>> but from the format it looks like a dictionary, not a list of dictionaries (because it starts with {, this is used for objects, or dictionaries, not [ which is used for arrays/lists) . To use this format for dictionaries, you need to configure the UseSimpleDictionaryFormat property
var serializer = new DataContractJsonSerializer(typeof(GetAssetInfoResponse), new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
});
With this setting and this change, it worked:
public Dictionary<string, AssetInfo> Result { get; set; }

Entity Framework 6 won't update record unless I breakpoint my code

Edit: There's no issue here and I don't know whether or not to delete the question... As per this comment, the whole thing was my bad. In the meantime, below are some nice drawings of my database and EntityFramework configuration. And some database tables.
I have the following two tables in my database (among others, these are the relevant ones):
It's possible that the GroupId field in SignalMetaData is null until the GroupMetaData entry is received by my application. With that said, here is my code to update a SignalMetaData record to provide its GroupId field once it's been received. this._entities is my EF object.
/// <summary>
/// Maps a signal to its group in the database
/// </summary>
/// <param name="signalId">The SignalId</param>
/// <param name="groupId">The identifier of the group that SignalId belongs to.</param>
/// <returns></returns>
public bool InsertSignalGroupMapping(Guid signalId, Guid groupId)
{
try
{
var sig = this._entities.SignalMetaDatas
.SingleOrDefault(signal => signal.SignalId == signalId);
if (sig != null)
{
sig.GroupId = groupId;
this._entities.SaveChanges();
}
}
catch (Exception ex)
{
this._logger.Log(LogSeverity.Error, ex);
return false;
}
return true;
}
The this._entities.SaveChanges(); call doesn't seem to do anything when I test my application. I query the database and the record is not updated. It's like the method was never called. If I breakpoint the line with sig.GroupId = groupId; and then step through the SaveChanges() call, it has no problem. The record is updated and everything works fine.
I've tried adding this line between the assignment of GroupId and the SaveChanges() call:
this._entities.Entry(sig).State = EntityState.Modified;
with the same outcome. I've also tried using this call to update the record:
this._entities.SignalMetaDatas.AddOrUpdate(sig);
to no avail. I've added Console writes in the method and gone through without breakpoints and the Console writes appear. The method is being called but it's just doing nothing unless I breakpoint and step through manually.
One other thing I should mention: both signalId and groupId passed to the InsertSignalGroupMapping method are not null or empty guids, verified with Console.Writeline.
I've also tried assigning the GroupMetaData navigation property on the sig object to a fully qualified group object containing the ID passed to InsertSignalGroupMapping with the same outcome.
Per request, here is the model classes for the two objects.
using System;
using System.Collections.Generic;
public partial class GroupMetaData
{
public GroupMetaData()
{
this.GroupHierarchiesParentId = new HashSet<GroupHierarchy>();
this.GroupHierarchiesGroupId = new HashSet<GroupHierarchy>();
this.SignalMetaDatas = new HashSet<SignalMetaData>();
}
public System.Guid GroupId { get; set; }
public string GroupName { get; set; }
public virtual ICollection<GroupHierarchy> GroupHierarchiesParentId { get; set; }
public virtual ICollection<GroupHierarchy> GroupHierarchiesGroupId { get; set; }
public virtual ICollection<SignalMetaData> SignalMetaDatas { get; set; }
}
and
using System;
using System.Collections.Generic;
public partial class SignalMetaData
{
public SignalMetaData()
{
this.SignalHierarchiesSignalId = new HashSet<SignalHierarchy>();
this.SignalHierarchiesParentId = new HashSet<SignalHierarchy>();
this.SignalValues = new HashSet<SignalValue>();
}
public System.Guid SignalId { get; set; }
public string SignalName { get; set; }
public Nullable<System.Guid> GroupId { get; set; }
public virtual GroupMetaData GroupMetaData { get; set; }
public virtual ICollection<SignalHierarchy> SignalHierarchiesSignalId { get; set; }
public virtual ICollection<SignalHierarchy> SignalHierarchiesParentId { get; set; }
public virtual ICollection<SignalValue> SignalValues { get; set; }
}
and the EF Config:
It's possible that you have a watch or something else in your debugging environment that is manipulating the context in a way that causes it to "work".
The standard method is to set the navigation property instead of the ID. Something like:
var sig = this._entities.SignalMetaDatas
.SingleOrDefault(signal => signal.SignalId == signalId);
var group = this._entities.Groups
.SingleOrDefault(g => g.groupId == groupId);
if (sig != null)
{
sig.Group = group;
this._entities.SaveChanges();
}

Sending data to the server with fnServerParams and aoData for jquery DataTable does not work in MVC4

I want to send extra data to serverside (ASP.Net MVC4) for my jquery datatable. There are many examples on how to this client side, but I can't get it to work on the serverside.
Here's the code:
javascript:
$(document).ready(function () {
var oTable = $('#myDataTable').dataTable({
"bServerSide": true,
"sAjaxSource": "SearchPatient/DataHandler",
"fnServerParams": function (aoData) {
alert('in fnServerParams');
aoData.push( { "name": "more_data", "value": "my_value" } );
}
});
});
Note: the alert goes off, so the function itself is working.
My model class:
/// <summary>
/// Class that encapsulates most common parameters sent by DataTables plugin
/// </summary>
public class JQueryDataTableParamModel
{
/// <summary>
/// fnServerparams, this should be an array of objects?
/// </summary>
public object[] aoData { get; set; }
/// <summary>
/// Request sequence number sent by DataTable, same value must be returned in response
/// </summary>
public string sEcho { get; set; }
/// <summary>
/// Text used for filtering
/// </summary>
public string sSearch { get; set; }
/// <summary>
/// Number of records that should be shown in table
/// </summary>
public int iDisplayLength { get; set; }
/// <summary>
/// First record that should be shown(used for paging)
/// </summary>
public int iDisplayStart { get; set; }
/// <summary>
/// Number of columns in table
/// </summary>
public int iColumns { get; set; }
/// <summary>
/// Number of columns that are used in sorting
/// </summary>
public int iSortingCols { get; set; }
/// <summary>
/// Comma separated list of column names
/// </summary>
public string sColumns { get; set; }
/// <summary>
/// Text used for filtering
/// </summary>
public string oSearch { get; set; }
}
and finally my Controller:
public ActionResult DataHandler(JQueryDataTableParamModel param)
{
if (param.aoData != null)
{
// Get first element of aoData. NOT working, always null
string lSearchValue = param.aoData[0].ToString();
// Process search value
// ....
}
return Json(new
{
sEcho = param.sEcho,
iTotalRecords = 97,
iTotalDisplayRecords = 3,
aaData = new List<string[]>() {
new string[] {"1", "IE", "Redmond", "USA", "NL"},
new string[] {"2", "Google", "Mountain View", "USA", "NL"},
new string[] {"3", "Gowi", "Pancevo", "Serbia", "NL"}
}
},
JsonRequestBehavior.AllowGet);
}
Note: the action handler gets hit, so the ajax call to get data is also working and my datatable gets filled with 3 rows..
The problem is: aoData is always null. I expect the first element to hold "my_value".
Any help is much appreciated!
After searching for hours to find the answer finally posted it here. Only to come up with the answer in minutes:
This does the trick:
Add this line serverside in the DataHandler:
var wantedValue = Request["more_data"];
So the value is in the request and not in the model.
Thanks.
The value(s) are indeed in the model, but they are passed as individual fields, not as elements of aoData:
public class JQueryDataTableParamModel {
/// The "more_data" field specified in aoData
public string more_data { get; set; }
public string sEcho { get; set; }
public string sSearch { get; set; }
public int iDisplayLength { get; set; }
public int iDisplayStart { get; set; }
public int iColumns { get; set; }
public int iSortingCols { get; set; }
public string sColumns { get; set; }
public string oSearch { get; set; }
}
Usage:
public ActionResult DataHandler(JQueryDataTableParamModel param) {
/// Reference the field by name, not as a member of aoData
string lSearchValue = param.more_data;
return Json(
new {
sEcho = param.sEcho,
iTotalRecords = 97,
iTotalDisplayRecords = 3,
aaData = new List<string[]>() {
new string[] {"1", "IE", "Redmond", "USA", "NL"},
new string[] {"2", "Google", "Mountain View", "USA", "NL"},
new string[] {"3", "Gowi", "Pancevo", "Serbia", "NL"}
}
},
JsonRequestBehavior.AllowGet
);
}
I will post another answer just to show a way to avoid code duplication.
You can also make your JQueryDataTableParamModel as a base class for others. Datatables will send the custom data in the same object, so you can't make the model bind it directly, unless your C# View Model matches exactly the DataTables sent object.
This can be achieved as #SetFreeByTruth answered, but you could have some code duplication if you want it to be used in a whole project. This table has more_data, what if you have another table with only a property called custom_data? You would need to fill your object with multiple fields or create several datatables view models, each with your custom data.
For your scenario, you could use inheritance. Create a new class like this:
//Inheritance from the model will avoid duplicate code
public class SpecificNewParamModel: JQueryDataTableParamModel
{
public string more_data { get; set; }
}
Using it like this in a controller:
public JsonResult ReportJson(SpecificNewParamModel Model)
{
//code omitted code for clarity
return Json(Return);
}
If you send the DataTables request and inspect the Model, you can see that it's filled with your custom data (more_data) and the usual DataTables data.

Reuse index transformer expressions fails on Id

I'm looking to be able to reuse some of the transform expressions from indexes so I can perform identical transformations in my service layer when the document is already available.
For example, whether it's by a query or by transforming an existing document at the service layer, I want to produce a ViewModel object with this shape:
public class ClientBrief
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
// ellided
}
From this document model:
public class Client
{
public int Id { get; private set; }
public CompleteName Name { get; private set; }
public Dictionary<EmailAddressKey, EmailAddress> Emails { get; private set; }
// ellided
}
public class CompleteName
{
public string Title { get; set; }
public string GivenName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Surname { get; set; }
public string Suffix { get; set; }
public string FullName { get; set; }
}
public enum EmailAddressKey
{
EmailAddress1,
EmailAddress2,
EmailAddress3
}
public class EmailAddress
{
public string Address { get; set; }
public string Name { get; set; }
public string RoutingType { get; set; }
}
I have an expression to transform a full Client document to a ClientBrief view model:
static Expression<Func<IClientSideDatabase, Client, ClientBrief>> ClientBrief = (db, client) =>
new ClientBrief
{
Id = client.Id,
FullName = client.Name.FullName,
Email = client.Emails.Select(x => x.Value.Address).FirstOrDefault()
// ellided
};
This expression is then manipulated using an expression visitor so it can be used as the TransformResults property of an index (Client_Search) which, once it has been generated at application startup, has the following definition in Raven Studio:
Map:
docs.Clients.Select(client => new {
Query = new object[] {
client.Name.FullName,
client.Emails.SelectMany(x => x.Value.Address.Split(new char[] {
'#'
})) // ellided
}
})
(The Query field is analysed.)
Transform:
results.Select(result => new {
result = result,
client = Database.Load(result.Id.ToString())
}).Select(this0 => new {
Id = this0.client.__document_id,
FullName = this0.client.Name.FullName,
Email = DynamicEnumerable.FirstOrDefault(this0.client.Emails.Select(x => x.Value.Address))
})
However, the transformation expression used to create the index can then also be used in the service layer locally when I already have a Client document:
var brief = ClientBrief.Compile().Invoke(null, client);
It allows me to only have to have one piece of code that understands the mapping from Client to ClientBrief, whether that code is running in the database or the client app. It all seems to work ok, except the query results all have an Id of 0.
How can I get the Id property (integer) properly populated in the query?
I've read a number of similar questions here but none of the suggested answers seem to work. (Changing the Ids to strings from integers is not an option.)
I have a hard time following your sample fully, Really the best way to dig in to this would be with a failing self-contained unit test.
Nonetheless, let's see if I can pull out the important bits.
In the transform, you have two areas where you are working with the id:
...
client = Database.Load(result.Id.ToString())
...
Id = this0.client.__document_id,
...
The result.Id in the first line and the Id = in the second line are expected to be integers.
The Database.Load() expects a string document key and that is also what you see in __document_id.
The confusion comes from Raven's documentation, code, and examples all use the terms id and key interchangeably, but this is only true when you use string identifiers. When you use non-string identifiers, such as ints or guids, the id may be 123, but the document key is still clients/123.
So try changing your transform so it translates:
...
client = Database.Load("clients/" + result.Id)
...
Id = int.Parse(this0.client.__document_id.Split("/")[1]),
...
... or whatever the c# equivalent linq form would be.

Categories