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;
Related
I have a dataset returning two tables with stored procedure; the first one is in a one-to-one relationship with inner join.
My second table is the items table marked with the id column in the first table.
Table1.ID is kept in Def_SpecificationID column
Stored procedure code:
SELECT Spec.ID as SpecID,
Spec.Def_Specification_GroupID,
Spec.SpecTitle,
Spec.HasItem,
Spec.IsActive,
SpecGroup.SpecGroup
FROM Def_Specification Spec
INNER JOIN Def_Specification_Group SpecGroup ON Spec.Def_Specification_GroupID = SpecGroup.ID
WHERE Spec.IsActive = 1
SELECT ID, Def_SpecificationID, SpecificationTitle
FROM Def_Specification_Items
table structure
What I want to do here is to add data to the elements of the first table according to the Def Specification ID in the second table.
Entity
public class DefSpecificationAndGroupAndItems
{
public DefSpecificationAndGroupAndItems()
{
this.DefSpecificationItems = new();
}
public int ID { get; set; }
public int Def_Specification_GroupID { get; set; }
public string SpecTitle { get; set; }
public int HasItem { get; set; }
public bool IsActive { get; set; }
public string SpecGroup { get; set; }
public List<DefSpecificationItems> DefSpecificationItems { get; set; }
}
Dapper code
await connection.QueryAsync<DefSpecificationAndGroupAndItems,DefSpecificationItems, DefSpecificationAndGroupAndItems>(
"SP_DEF_SPECIFICATION_GET_ALL_BY_ACTIVE",
(spec,items)=> {
DefSpecificationAndGroupAndItems result=new();
result.DefSpecificationItems.Add(items);
return result;
},
new { Active = active },
commandType: CommandType.StoredProcedure,
splitOn: "Def_SpecificationID,SpecID"
);
This code worked for me but is there a dapper version of it
var specification = new List<DefSpecificationAndGroupAndItems>();
using (var multi = await connection.QueryMultipleAsync("SP_DEF_SPECIFICATION_GET_ALL_BY_ACTIVE", new { Active = active },
commandType:CommandType.StoredProcedure))
{
var specs =(await multi.ReadAsync<DefSpecificationAndGroupAndItems>()).ToList();
var specItems = (await multi.ReadAsync<DefSpecificationItems>()).ToList();
foreach (var item in specs)
{
item.DefSpecificationItems = specItems.Where(i => i.Def_SpecificationID == item.ID).ToList();
}
specification = specs;
}
How can I map tables returned from dataset in dapper?
You can just use:
SELECT *
FROM Def_Specification_Items
WHERE Def_SpecificationId = #DefSpecificationId;
#DefSpecificationId will be parameter passed to Dapper:
specs.DefSpecificationItems = await connection
.QueryAsync<List<DefSpecificationItems>>(sql, new
{
DefSpecificationId = specs.Id
});
specs is DefSpecificationAndGroupAndItems here
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.
I have Medals class, I call a service that return all Medal class fields except for the two fields ArabicName and ISOCode; Which I have to bring them from another table class "CountrysTBLs" , I made this join code:
The Medal class:
public class Medals {
public int bronze_count { get; set; }
public string country_name { get; set; }
public int gold_count { get; set; }
public string id { get; set; }
public int place { get; set; }
public int silver_count { get; set; }
public int total_count { get; set; }
public string ArabicName { get; set; } // this field not return by service
public string ISOCode { get; set; } // this field not return by service
}
The code:
var cntrs = db.CountrysTBLs.ToList();
List<Medals> objs = call_Service_Of_Medals_Without_ISOCode();
IEnumerable<Medals> query = from obj in objs
join cntry in cntrs on obj.country_name equals '%' + cntry.CommonName + '%'
select new Medals
{
ArabicName = cntry.ArabicName,
ISOCode = cntry.ISOCode,
country_name = obj.country_name,
place = obj.place,
gold_count = obj.gold_count,
silver_count = obj.silver_count,
bronze_count = obj.bronze_count,
total_count = obj.total_count
};
I get no result?!
How to fix that? Is there is any way to bring the two fields (ISOCode, ArabicName) without even use the inner join, and in same time best performance?
You want something like this to achieve LIKE functionality
List<Medals> medals = new List<Medals>();
var list = medals.Select(x => new
{
obj = x,
country = countries.FirstOrDefault(c => c.CommonName.Contains(x.country_name))
});
or something like this (if you want to just enrich each medal)
foreach (var medal in medals)
{
var country = countries.FirstOrDefault(c => c.CommonName.Contains(x.country_name));
medal.ISOCode = country.ISOCode;
medal.ArabicName = country.ArabicName;
}
Do note that this is not as performant as a Dictionary<string,Coutnry> of countries where the key is the country name, but as you need a LIKE comparison you would need to bring in a custom data structure such as Lucene index for fast lookups. But check first, if the lists are small enough, it probably won't be a problem. Otherwise, why not make the Medal.Country_Name and Country.Name the same? So you can do quick Dictionary (hashtable) lookups
I have two views in my model.
I basically need to do an INNER JOIN on them based on three columns:
dataSource
ShowID
EpisodeID
The first thing I don't know how to do is add the SQL "AND" operator to the LINQ expression.
The second thing is, I don't know how to SELECT the JOINED table.
Can someone give me a hand?
var query = (from s in db.TVData_VW_ShowList
from z in db.TVData_VW_Schedule
where s.dataSource = z.dataSource
&& s.ShowID = z.ShowID
&& s.EpisodeId = z.EpisodeId select ...
You can use anonymous types to your advantage here, both to join across multiple columns, and to project into a new type containing data from both sides of the join. Here's a working example using Linq to objects:
namespace LinqExample
{
class Program
{
static void Main()
{
var Shows = new List<ShowData> { new ShowData { dataSource = "foo", EpisodeID = "foo", ShowID = "foo", SomeShowProperty = "showFoo" }};
var Schedules = new List<ScheduleData> { new ScheduleData { dataSource = "foo", EpisodeID = "foo", ShowID = "foo", SomeScheduleProperty = "scheduleFoo" } };
var results =
from show in Shows
join schedule in Schedules
on new { show.dataSource, show.ShowID, show.EpisodeID }
equals new { schedule.dataSource, schedule.ShowID, schedule.EpisodeID }
select new { show.SomeShowProperty, schedule.SomeScheduleProperty };
foreach (var result in results)
{
Console.WriteLine(result.SomeShowProperty + result.SomeScheduleProperty); //prints "showFoo scheduleFoo"
}
Console.ReadKey();
}
}
public class ShowData
{
public string dataSource { get; set; }
public string ShowID { get; set; }
public string EpisodeID { get; set; }
public string SomeShowProperty { get; set; }
}
public class ScheduleData
{
public string dataSource { get; set; }
public string ShowID { get; set; }
public string EpisodeID { get; set; }
public string SomeScheduleProperty { get; set; }
}
}
So to join you can use the join keyword then use on to specify the conditions. && (the logical and operator in C#) will be translated to the SQL AND keyword.
Also, in EF they have what are known as "implicit joins" meaning if I have TableA with a foreign key to TableB, call it fKey.
Doing where TableA.fKey == TableB.pKey will cause the provider to put a join there. To select you simply need to do;
select new { prop1 = TableA.Prop1, prop2 = TableB.Prop1 }
this will create a new anonymous which selects values from both tables.
Below is a more complete example of the join syntax. I think it uses all of the things you asked about;
var result = from a in TableA
join b in TableB on a.fKey equals b.pKey && b.Status equals 1
select new { a.Prop1, a.Prop2, b.Prop1 };
First you need to create an auxiliar class that contains the columns of both views, something like:
public class viewItem
{
public int ShowID { get; set; }
public int EpisodeID { get; set; }
public int dataSource { get; set; }
...
}
then your linq query would be:
var query = (from s in db.TVData_VW_ShowList
join z in db.TVData_VW_Schedule
on s.dataSource equals z.dataSource
where s.ShowID == z.ShowID
&& s.EpisodeID == z.EpisodeID
select new viewItem {
ShowID = s.ShowID,
EpisodeID = s.EpisodeID,
dataSource = s.dataSource,
...
}
With a fair bit of embarrassment, I wish to ask / determine the best way to map a Linq-To-SQL query to a POCO. I am not in a position to use EF at the moment, so sticking with Linq-To-SQL.
As of now, I have a simple class as follows:
public class DiaryEvent
{
public Int64 ID { get; set; }
public string ResourceCalendarID { get; set; }
public string EventTitle { get; set; }
public string Location { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Description { get; set; }
public string ResourceColour { get; set; }
public bool AllDay { get; set; }
}
My Linq-to-SQL (LTS) query is as follows:
DataClassesDataContext db = new DataClassesDataContext();
var qry =
from b in db.bookings
select b
Because the database table bookings isn't/can't be created exactly as my DiaryEvent class, I am currently iterating over b like:
List<DiaryEvent> EventList = new List<DiaryEvent>();
foreach (booking item in qry)
{
EventList.Add(new DiaryEvent
{
AllDay = false,
Description = item.bookingDescription,
ID = item.bookingID,
StartDate = (DateTime)item.startDate,
EndDate = (DateTime)item.endDate,
EventTitle = item.bookingName,
Location = item.bookingLocation,
ResourceCalendarID = item.resourceID.ToString(),
ResourceColour = item.bookingColour.Trim()
});
}
This maps my data correctly, however I am not sure if this is the fastest/most correct way to achieve this?
I would be grateful for any feedback as to confirming if the above technique is acceptable, or if there are ways to improve upon that?
P.S: In fairness, there isn't going to be massive amounts of data to retrieve.. maybe a few hundred rows in any given circumstance.
Thanks in advance!
Unless I've misunderstood something, you could simplify your code to something like this:
var EventList = (from b in db.bookings
select new DiaryEvent
{
AllDay = false,
Description = b.bookingDescription,
ID = b.bookingID,
StartDate = (DateTime)b.startDate,
EndDate = (DateTime)b.endDate,
EventTitle = b.bookingName,
Location = b.bookingLocation,
ResourceCalendarID = b.resourceID.ToString(),
ResourceColour = b.bookingColour.Trim()
}).ToList();
The result is virtually the same but it saves you having to use the foreach.