Populate multiple objects from JSON - c#

I have this code:
public class Customer
{
Int32 id { get; set; } = 0;
User user1 { get; set; } = null;
User user2 { get; set; } = null;
}
/* ... */
using (MySqlConnection conn = new MySqlConnection(Costanti.connessione))
{
conn.Open();
MySqlCommand m = new MySqlCommand("
SELECT c1.*, u1.*, u2.*
FROM customers as c1
inner join utenti u1 on u1.customer_id = c1.id
inner join utenti u2 on u2.customer_id = c1.id
", conn);
MySqlDataReader x = m.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(x);
String json_string = Newtonsoft.Json.JsonConvert.SerializeObject(dataTable);
List<Customer> lista = new List<Customer>();
Newtonsoft.Json.JsonConvert.PopulateObject(json_string, lista);
conn.Close();
}
How could I map c1.* fields of select to a generic Customer customer_1, and then u1.* and u2.* into customer_1's properties? Newtonsoft.Json.JsonConvert.PopulateObject doesn't let me do it.
The intermediate json_string looks like:
[
{
"id":1,
"id_user":8,
"name":"manuel",
"id_user1":2,
"name1":"michael"
},
{
"id":2,
"id_user":3,
"name":"friedrich",
"id_user1":6,
"name1":"antony"
}
]
And the result has to be a list composed by:
Customer(with id=1), with User1(8,"manuel") and User2(2,"michael");
Customer(with id=2), with User1(3,"friedrich") and User2(6,"antony").

The main reason your call to PopulateObject() is not working is that your c# data model does not match the schema of your JSON. If I use one of the tools from How to auto-generate a C# class file from a JSON object string to auto-generate a data model from your JSON, I get:
public class RootObject
{
public int id { get; set; }
public int id_user { get; set; }
public string name { get; set; }
public int id_user1 { get; set; }
public string name1 { get; set; }
}
This looks nothing like your Customer class.
The secondary reason that PopulateObject() fails is that Json.NET will only populate public members by default -- and yours are all private.
To fix this, I might suggest skipping the DataTable and json_string representations entirely, and building your list directly from the IDataReader interface that MySqlDataReader implements:
var lista = x
.SelectRows(r =>
// Extract the row data by name into a flat object
new
{
id = Convert.ToInt32(r["id"], NumberFormatInfo.InvariantInfo),
id_user = Convert.ToInt32(r["id_user"], NumberFormatInfo.InvariantInfo),
name = r["name"].ToString(),
id_user1 = Convert.ToInt32(r["id_user1"], NumberFormatInfo.InvariantInfo),
name1 = r["name1"].ToString(),
})
.Select(r =>
// Convert the flat object to a `Customer`.
new Customer
{
id = r.id,
user1 = new User { Id = r.id_user, Name = r.name },
user2 = new User { Id = r.id_user1, Name = r.name1 },
})
.ToList();
Using the extension method:
public static class DataReaderExtensions
{
// Adapted from this answer https://stackoverflow.com/a/1202973
// To https://stackoverflow.com/questions/1202935/convert-rows-from-a-data-reader-into-typed-results
// By https://stackoverflow.com/users/3043/joel-coehoorn
public static IEnumerable<T> SelectRows<T>(this IDataReader reader, Func<IDataRecord, T> select)
{
while (reader.Read())
{
yield return select(reader);
}
}
}
This assumes that your Customer data model now looks like:
public class Customer
{
public Int32 id { get; set; }
public User user1 { get; set; }
public User user2 { get; set; }
}
public class User
{
public Int32 Id { get; set; }
public string Name { get; set; }
}
You could also combine the SelectRows and Select calls into a single method; I separated them for clarity. Skipping the DataTable and JSON representations should be simpler and more performant than your current approach.
Demo fiddle using a mockup data reader here.

Related

Query separate collection in RavenDB Index (WHERE IN)

Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes

How to get a subset of fields from Entity Framework from a join with a field that has the same name as the table

I am learning Entity Framework and Linq at the same time. I can get a table to a grid with this
List<object> boel;
var query = from BillingOrderEntries boe in DM.BillingOrderEntries
select boe;
boel = query.ToList();
dataGridView1.DataSource = boel;
But I am having difficulty trying to get a subset of fields between two tables. I think the join is right because I've looked at several examples.
var boel = new[] { new { DateOfBirth= default(DateTime?), FirstName = "", LastName = "", AccessionField = "", Requisition = "" } }.ToList();
var query = from a in DM.Accessions
join boe in DM.BillingOrderEntries on a.Accession1 equals boe.Accession
orderby boe.LastName
select new { boe.DateOfBirth, boe.FirstName, boe.LastName, a.Accession, a.Requisition }
boel = query.ToList();
dataGridView1.DataSource = boel;
The problem was the Accession table had an Accession column in it. I tried changing the name of the class to AccessionTable, then modifying the property to reflect this in the context class but either this cant be done or I missed something somewhere. I then changed the property in the Accession class to AccessionField. It compiled but won't run:
The specified type member 'AccessionField' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
I can't change the database, but I can change any code that would make this work.
You can use an alias for a field in the select clause as shown below, note this example just mocks the classes you're referencing to mimic your code and demonstrate how to select clause aliasing:
UPDATED
/* Mock class */
public class Accession
{
[Column("Accession")]
public string AccessionField { get; set; }
public string Requisition { get; set; }
}
/* Mock class */
public class Billing
{
public string Accession { get; set; }
public string DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
/* Mock class */
public class DataManager
{
public IEnumerable<Accession> Accessions { get; set; }
public IEnumerable<Billing> BillingOrderEntries { get; set; }
}
public static void Main(string[] args)
{
/* Start: Generate fake data */
DataManager DM = new DataManager();
DM.Accessions = new List<Accession>() { new Accession { AccessionField = "a1", Requisition="r1" }, new Accession { AccessionField = "a2", Requisition = "r2" } };
DM.BillingOrderEntries = new List<Billing>() { new Billing { Accession = "a1", DateOfBirth = "01-01-2016", FirstName = "first1", LastName="last1" }, new Billing { Accession = "a2", DateOfBirth = "02-02-2016", FirstName = "first2", LastName = "last2" } };
/* End: Generate fake data */
var query = from a in DM.Accessions
join boe in DM.BillingOrderEntries on a.AccessionField equals boe.Accession
orderby boe.LastName
select new {
boe.DateOfBirth,
boe.FirstName,
boe.LastName,
a.AccessionField,
a.Requisition
};
var boel = query.ToList();
}

Multiple table join using lambda/linq c# with DTO

This really has me stumped. I have four tables in the database, and unfortunately the person who designed this table didn't create referential constraints. So, there is no navigation properties available.
Four tables are:
CiscoPhoneReport
ApplicationSummary
CSQActivityReport
CallDistributionSummary
The idea is that for each PhoneReportID in CiscoPhoneReport, there is one ApplicationSummary, three CSQActivityReport, and three CallDistributionSummary.
I want the output as below in JSON format:
`[{
"appSummary":{
"startDate":"2015-09-01T00:00:00",
"endDate":"2015-09-30T00:00:00",
"applicationName":"RationalDrugTherapy",
"callsPresented":14504,
"callsAbandoned":1992,
"callsHandled":12512
},
"csqModel":[
{
"startDate":null,
"csqid":"3",
"callsPresented":6271,
"avgQueueTime":"00:00:21",
"callsHandled":0,
"avgAnswerSpeed":"00:00:00",
"avgHandleTime":"00:02:08",
"callsHandledGreaterThan3t":5742,
"callsAbandoned":99,
"avgAbandonTime":"00:02:20",
"maxQueueTime":"00:25:26",
"maxHandleTime":"00:19:33",
"maxAbandonTime":"00:17:50"
},{
"startDate":null,
"csqid":"3",
"callsPresented":6271,
"avgQueueTime":"00:00:21",
"callsHandled":0,
"avgAnswerSpeed":"00:00:00",
"avgHandleTime":"00:02:08",
"callsHandledGreaterThan3t":1728,
"callsAbandoned":99,
"avgAbandonTime":"00:02:20",
"maxQueueTime":"00:25:26",
"maxHandleTime":"00:19:33",
"maxAbandonTime":"00:17:50"
}, {
"startDate":null,
"csqid":"3",
"callsPresented":6271,
"avgQueueTime":"00:00:21",
"callsHandled":0,
"avgAnswerSpeed":"00:00:00",
"avgHandleTime":"00:02:08",
"callsHandledGreaterThan3t":3363,
"callsAbandoned":99,
"avgAbandonTime":"00:02:20",
"maxQueueTime":"00:25:26",
"maxHandleTime":"00:19:33",
"maxAbandonTime":"00:17:50"
}]
}]`
For this, I created DTO:
`public class AppSummary
{
public string PhoneReportID { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string ApplicationName { get; set; }
public int CallsPresented { get; set; }
public int CallsAbandoned { get; set; }
public int CallsHandled { get; set; }
}
`
`public class CSQModel
{
public string StartDate { get; set; }
public string CSQID { get; set; }
public int CallsPresented { get; set; }
public TimeSpan AvgQueueTime { get; set; }
public int CallsHandled { get; set; }
public TimeSpan AvgAnswerSpeed { get; set; }
public TimeSpan AvgHandleTime { get; set; }
public int CallsHandledGreaterThan3t { get; set; }
public int CallsAbandoned { get; set; }
public TimeSpan AvgAbandonTime { get; set; }
public TimeSpan MaxQueueTime { get; set; }
public TimeSpan MaxHandleTime { get; set; }
public TimeSpan MaxAbandonTime { get; set; }
}
`
`public class PhoneReport
{
public AppSummary AppSummary { get; set; }
//Initially, I had it like this
public CSQModel CSQModel { get; set; }
//I renamed the property as LIST to see if I could use it and add data to the list in linq, but I couldn't use the list within select expression in linq.
//public List<CSQModel> CSQModel { get; set; }
}
`
The CSQModel class has needed data from both CSQActivityReport and CallDistributionSummary tables.
I was able to create a linq statement with table joins as below.
var res = from cpr in db.CiscoPhoneReport
join app in db.ApplicationSummary on cpr.PhoneReportID equals app.PhoneReportID into g1
from appGroup in g1.DefaultIfEmpty()
join csq in db.CSQActivityReport on cpr.PhoneReportID equals csq.PhoneReportID into g2
from csqGroup in g2.DefaultIfEmpty()
join call in db.CallDistributionSummary on cpr.PhoneReportID equals call.PhoneReportID into g3
from callGroup in g3.DefaultIfEmpty()
where cpr.PhoneReportID == phoneReportID
select new PhoneReport
{
AppSummary = new AppSummary
{
StartDate = cpr.StartDate,
EndDate = cpr.EndDate,
ApplicationName = appGroup.ApplicationName,
CallsPresented = appGroup.CallsPresented,
CallsAbandoned = appGroup.CallsAbandoned,
CallsHandled = appGroup.CallsHandled
},
CSQModel = new CSQModel
{
CSQID = csqGroup.CSQID.ToString(),
CallsPresented = csqGroup.CallsPresented,
AvgQueueTime = csqGroup.AvgQueueTime,
AvgHandleTime = csqGroup.AvgHandleTime,
CallsHandledGreaterThan3t = callGroup.CallsHandledGreaterThan3t,
CallsAbandoned = csqGroup.CallsAbandoned,
AvgAbandonTime = csqGroup.AvgAbandonTime,
MaxQueueTime = csqGroup.MaxQueueTime,
MaxHandleTime = csqGroup.MaxHandleTime,
MaxAbandonTime = csqGroup.MaxAbandonTime
}
};
`
The result I'm getting is a set of data with 9 rows, which makes sense - as in inner join in SQL. But this is not what I wanted.
How can I obtain the data as in the JSON format above? I couldn't figure it out at all.
I think part of the reason you are seeing 9 records is because the syntax you are using is the one for left outer joins in Linq.
What might work is using subqueries to get the data you want in a format you want it.
For example
var res = from cpr in db.CiscoPhoneReport
join app in db.ApplicationSummary on cpr.PhoneReportID equals app.PhoneReportID
where cpr.PhoneReportID == phoneReportID
select new PhoneReport
{
AppSummary = new AppSummary
{
// Mappings
},
CSQModel = (from model in db.CSQActivityReport
where model.PhoneReportId == phoneReportID
select new CSQModel
{
// Mappings
}).ToList()
}
You were right that you need the CSQModels to be some sort of collection, be it a List or even a basic ICollection of type CSQModel. You can write another sub query for the CallDistributionSummary as needed.
For every master record, you have 3 records each in 2 separate child tables. Any join you do will give you 9 records (even if you go straight to T-SQL) unless you add some more information.
One way to do this in SQL is to join record 1 from table A to record 1 from table B, you would need an indexer to do this. One option in T-SQL is to use the ROW_NUMBER() function on each of the child tables and use that value in the join. But, the ROW_NUMBER() hasn't been extended to LINQ.
If you can't stand getting getting duplicate records, and just doing a Distinct() call for each child result set. Then you could do this as two or 3 separate queries.
NOTE: You can bundle this into 3 result sets in a stored proc.. You can easily get EntityFramework to deserialize each result into your POCO's..
var objCtx = ((IObjectContextAdapter)ctx).ObjectContext;
using (SqlCommand cmd = ctx.Database.Connection.CreateCommand() as SqlCommand)
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "<your proc here>";
var param = cmd.CreateParameter();
param.ParameterName = "#param1";
param.Value = someValue;
cmd.Parameters.Add(param);
await cmd.Connection.OpenAsync();
using (var reader = await cmd.ExecuteReaderAsync())
{
var results = objCtx.Translate<type1Here>(reader).ToList();
reader.NextResult();
var results2 = objCtx.Translate<type2Here>(reader).ToList();
reader.NextResult();
var results3 = objCtx.Translate<type3Here>(reader).ToList();
reader.NextResult();
}
}
You can serialize your object:
DataContractJsonSerializer serializer = new DataContractJsonSerializer(GenericObject.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, GenericObject);
string json = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
return json;

Select which fields to serialize to XML from a Web API call

I have five tables who are used to generate a response for a Web API call. My code will display all field, also those I didn't set, in the XML output.
How do I display only relevant fields without any duplicate fields?
This is my class file:
public class Posts
{
public Suburb suburb { get; set; }
public SubRegion subRegion { get; set; }
public SubRegionDeliveryTime subRegionDeliveryTime { get; set; }
public DeliveryTime deliveryTime { get; set; }
public DeliveryPeriod deliveryPeriod { get; set; }
}
This is my API Controller source code.
public IEnumerable<Posts> Get(int pcode, string SuburbName = "")
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
var Location1 = (from su in db.TLCSuburb.AsEnumerable()
where su.name.Contains(SuburbName) ||
su.postcode == pcode
join Subr in db.TLCSubRegion on
su.SubRegionID equals Subr.SubregionID
join srdt in db.TLCSubRegionDeliveryTime on
Subr.SubregionID equals srdt.SubregionID
join DT in db.TLCDeliveryTime on
srdt.DeliveryTimeId equals DT.DeliveryTimeId
join DP in db.TLCDeliveryPeriod on
DT.DeliveryPeriodID equals DP.DeliveryPeriodID
orderby Subr.SubregionID
select new Posts
{
suburb = new Suburb()
{
name = su.name,
postcode = su.postcode,
AuState = su.AuState,
Latitude = su.Latitude,
Longitude = su.Longitude
},
//deliveryTime = DT.DeliveryDay,
deliveryTime = new DeliveryTime()
{
DeliveryDay = DT.DeliveryDay,
},
deliveryPeriod = new DeliveryPeriod()
{
PeriodType = DP.PeriodType
},
subRegion = new SubRegion()
{
CloseDayId = Subr.CloseDayId,
SubregionName = Subr.SubregionName
}
}).ToList();
string subReName = "";
int poscode;
foreach (var item in Location1)
{
var aus = item.suburb.AuState;
poscode = item.suburb.postcode;
subReName = item.subRegion.SubregionName;
}
var loc = Location1.ToList();
// return null;
return loc.ToList();
}
}
My output should be this format only:
<Posts>
<name>BLUES POINT</name>
<postcode>2060</postcode>
<DeliveryDay>Monday</DeliveryDay>
<PeriodType>weekly</PeriodType>
<AuState>NSW</AuState>
<Latitude>-33.8495688</Latitude>
<Longitude>151.2035053</Longitude>
<SubregionName>Sydney - Nth Shore - Lower</SubregionName>
<CloseDayId>1</CloseDayId>
</Posts>
Introduce a Data Transfer Object so you don't expose your data layer from your API layer. This approach allows you to change the data layer without having to alter your API:
public class LocationDTO
{
public string Name { get; set; }
public string PostCode { get; set; }
// ...
}
// ...
return locations.Select(l => new LocationDTO
{
Name = l.suburb.Name,
PostCode = l.suburb.PostCode,
// ...
}).ToList()
Using attributes on the LocationDTO you can then select which fields get serialized, when and how.
Or you can alter the serializer. WebAPI uses DataContractSerializer for XML, which emits null and default values by default. If you use XmlSerializer, the API will omit empty and default fields.

Mapping Dapper Query to a Collection of Objects (which itself has a couple of Collections)

I want to execute a single Query (or Stored Proc with multiple resultsets). I know how to do Multi-mapping using Dapper, but I can't sort how to map the two collections onto the same parent. Basically, given this Object definition...
class ParentObject
{
string Name { get; set; }
ICollection<ChildObjectOne> ChildSetOne {get;set;}
ICollection<ChildObjectTwo> ChildSetTwo { get; set; }
}
class ChildObjectOne
{
string Name { get; set; }
}
class ChildObjectTwo
{
int id { get; set; }
string LocationName { get; set; }
}
I want to be able to run a Dapper query that somehow yields:
IQueryable<ParentObject> result = cnn.Query(
// Some really awesome dapper syntax goes here
);
Not sure if you DON'T want to use MultiMapping but here's how it would work for your case. As far as I know and read on SO, is not not possible to map a deep nested object graph with a simple Query.
static void Main(string[] args)
{
var sqlParent = "SELECT parentId as Id FROM ParentTable WHERE parentId=1;";
var sqlChildOneSet = "SELECT Name FROM ChildOneTable;"; // Add an appropriate WHERE
var sqlChildTwoSet = "SELECT Id, LocationName FROM ChildTwoTable;"; // Add an appropriate WHERE
var conn = GetConnection() // whatever you're getting connections with
using (conn)
{
conn.Open();
using (var multi = conn.QueryMultiple(sqlParent + sqlChildOneSet + sqlChildTwoSet))
{
var parent = multi.Read<ParentObject>().First();
parent.ChildSetOne = multi.Read<ChildOne>().ToList();
parent.ChildSetTwo = multi.Read<ChildTwo>().ToList();
}
}
}
Similar questions for nested objects and dapper :
https://stackoverflow.com/search?q=nested+objects+%2B+dapper
It is possible to materialize an object with one-to-many relationships using the IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map); method in this case. However you need to make a few changes to the entities in order to have enough information to do so.
Here are a few SO threads with similar questions.
How do I map lists of nested objects with Dapper
Extension function to make it cleaner
Dapper.Net by example - Mapping Relationships
public class ParentObject
{
public ParentObject()
{
ChildSetOne = new List<ChildObjectOne>();
ChildSetTwo = new List<ChildObjectTwo>();
}
// 1) Although its possible to do this without this Id property, For sanity it is advisable.
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildObjectOne> ChildSetOne {get; private set;}
public ICollection<ChildObjectTwo> ChildSetTwo { get; private set; }
}
public class ChildObjectOne
{
// 2a) Need a ParentId
public int ParentId { get; set; }
public string Name { get; set; }
}
public class ChildObjectTwo
{
// 2b) This ParentId is not required but again for sanity it is advisable to include it.
public int ParentId { get; set; }
public int id { get; set; }
public string LocationName { get; set; }
}
public class Repository
{
public IEnumerable<ParentObject> Get()
{
string sql =
#"SELECT
p.Id,
p.Name,
o.Name,
o.ParentId,
t.Id,
t.LocationName,
t.ParentId
FROM
Parent p
LEFT JOIN ChildOne o on o.ParentId = p.Id
LEFT JOIN ChildTwo t on t.ParentId = p.Id
WHERE
p.Name LIKE '%Something%'";
var lookup = new Dictionary<int, ParentObject>();
using (var connection = CreateConnection())
{
connection.Query<ParentObject, ChildObjectOne, ChildObjectTwo, ParentObject>(
sql, (parent, childOne, childTwo) =>
{
ParentObject activeParent;
if (!lookup.TryGetValue(childOne.ParentId, out activeParent))
{
activeParent = parent;
lookup.add(activeParent.Id, activeParent);
}
//TODO: if you need to check for duplicates or null do so here
activeParent.ChildSetOne.Add(childOne);
//TODO: if you need to check for duplicates or null do so here
activeParent.ChildSetTwo.Add(childTwo);
});
}
return lookup.Values;
}
}

Categories