I'm currently using Entity Framework for my db access but want to have a look at Dapper. I have classes like this:
public class Course{
public string Title{get;set;}
public IList<Location> Locations {get;set;}
...
}
public class Location{
public string Name {get;set;}
...
}
So one course can be taught at several locations. Entity Framework does the mapping for me so my Course object is populated with a list of locations. How would I go about this with Dapper, is it even possible or do I have to do it in several query steps?
Alternatively, you can use one query with a lookup:
var lookup = new Dictionary<int, Course>();
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course))
lookup.Add(c.Id, course = c);
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(l); /* Add locations to course */
return course;
}).AsQueryable();
var resultList = lookup.Values;
See here https://www.tritac.com/blog/dappernet-by-example/
Dapper is not a full blown ORM it does not handle magic generation of queries and such.
For your particular example the following would probably work:
Grab the courses:
var courses = cnn.Query<Course>("select * from Courses where Category = 1 Order by CreationDate");
Grab the relevant mapping:
var mappings = cnn.Query<CourseLocation>(
"select * from CourseLocations where CourseId in #Ids",
new {Ids = courses.Select(c => c.Id).Distinct()});
Grab the relevant locations
var locations = cnn.Query<Location>(
"select * from Locations where Id in #Ids",
new {Ids = mappings.Select(m => m.LocationId).Distinct()}
);
Map it all up
Leaving this to the reader, you create a few maps and iterate through your courses populating with the locations.
Caveat the in trick will work if you have less than 2100 lookups (Sql Server), if you have more you probably want to amend the query to select * from CourseLocations where CourseId in (select Id from Courses ... ) if that is the case you may as well yank all the results in one go using QueryMultiple
No need for lookup Dictionary
var coursesWithLocations =
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (course, location) => {
course.Locations = course.Locations ?? new List<Location>();
course.Locations.Add(location);
return course;
}).AsQueryable();
I know I'm really late to this, but there is another option. You can use QueryMultiple here. Something like this:
var results = cnn.QueryMultiple(#"
SELECT *
FROM Courses
WHERE Category = 1
ORDER BY CreationDate
;
SELECT A.*
,B.CourseId
FROM Locations A
INNER JOIN CourseLocations B
ON A.LocationId = B.LocationId
INNER JOIN Course C
ON B.CourseId = B.CourseId
AND C.Category = 1
");
var courses = results.Read<Course>();
var locations = results.Read<Location>(); //(Location will have that extra CourseId on it for the next part)
foreach (var course in courses) {
course.Locations = locations.Where(a => a.CourseId == course.CourseId).ToList();
}
Sorry to be late to the party (like always). For me, it's easier to use a Dictionary, like Jeroen K did, in terms of performance and readability. Also, to avoid header multiplication across locations, I use Distinct() to remove potential dups:
string query = #"SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id";
using (SqlConnection conn = DB.getConnection())
{
conn.Open();
var courseDictionary = new Dictionary<Guid, Course>();
var list = conn.Query<Course, Location, Course>(
query,
(course, location) =>
{
if (!courseDictionary.TryGetValue(course.Id, out Course courseEntry))
{
courseEntry = course;
courseEntry.Locations = courseEntry.Locations ?? new List<Location>();
courseDictionary.Add(courseEntry.Id, courseEntry);
}
courseEntry.Locations.Add(location);
return courseEntry;
},
splitOn: "Id")
.Distinct()
.ToList();
return list;
}
Something is missing. If you do not specify each field from Locations in the SQL query, the object Location cannot be filled. Take a look:
var lookup = new Dictionary<int, Course>()
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.Name, l.otherField, l.secondField
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course)) {
lookup.Add(c.Id, course = c);
}
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(a);
return course;
},
).AsQueryable();
var resultList = lookup.Values;
Using l.* in the query, I had the list of locations but without data.
Not sure if anybody needs it, but I have dynamic version of it without Model for quick & flexible coding.
var lookup = new Dictionary<int, dynamic>();
conn.Query<dynamic, dynamic, dynamic>(#"
SELECT A.*, B.*
FROM Client A
INNER JOIN Instance B ON A.ClientID = B.ClientID
", (A, B) => {
// If dict has no key, allocate new obj
// with another level of array
if (!lookup.ContainsKey(A.ClientID)) {
lookup[A.ClientID] = new {
ClientID = A.ClientID,
ClientName = A.Name,
Instances = new List<dynamic>()
};
}
// Add each instance
lookup[A.ClientID].Instances.Add(new {
InstanceName = B.Name,
BaseURL = B.BaseURL,
WebAppPath = B.WebAppPath
});
return lookup[A.ClientID];
}, splitOn: "ClientID,InstanceID").AsQueryable();
var resultList = lookup.Values;
return resultList;
There is another approach using the JSON result. Even though the accepted answer and others are well explained, I just thought about an another approach to get the result.
Create a stored procedure or a select qry to return the result in json format. then Deserialize the the result object to required class format. please go through the sample code.
using (var db = connection.OpenConnection())
{
var results = await db.QueryAsync("your_sp_name",..);
var result = results.FirstOrDefault();
string Json = result?.your_result_json_row;
if (!string.IsNullOrEmpty(Json))
{
List<Course> Courses= JsonConvert.DeserializeObject<List<Course>>(Json);
}
//map to your custom class and dto then return the result
}
This is an another thought process. Please review the same.
I am trying to display a JSON using a JsonResult method in MVC, I am using Entity Framework but my issue is that the PostMan is displaying a server error:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I am using a query which involves 3 different tables, however one of those tables might retrieve more that 3 different rows.
This is my code:
[HttpGet]
[AllowAnonymous]
public JsonResult RetreiveResume(int User)
{
using (var context = new DexusEntities())
{
var collection = (from p in context.CND_PersonalData join
pd in context.CND_ProfessionalData on p.UserId equals pd.UserId join
ex in context.CND_ExperiencesData on p.UserId equals ex.UserId select p).ToList();
return Json(collection, JsonRequestBehavior.AllowGet);
}
}
What's wrong with my code?
Thanks in advance.
Move return line after using. You are disposing context just after trying to return results. You can check this link for more information: What happens if i return before the end of using statement? Will the dispose be called?
[HttpGet]
[AllowAnonymous]
public JsonResult RetreiveResume(int User)
{
var collection = new CND_PersonalData();
using (var context = new DexusEntities())
{
context.Configuration.LazyLoadingEnabled = false;
collection = (from p in context.CND_PersonalData join
pd in context.CND_ProfessionalData on p.UserId equals pd.UserId join
ex in context.CND_ExperiencesData on p.UserId equals ex.UserId select p).ToList();
}
return Json(collection, JsonRequestBehavior.AllowGet);
}
could you please try to put the return after the brackets like the following :
using (var context = new DexusEntities())
{
var collection = (from p in context.CND_PersonalData join
pd in context.CND_ProfessionalData on p.UserId equals pd.UserId join
ex in context.CND_ExperiencesData on p.UserId equals ex.UserId select p).ToList();
return Json(collection, JsonRequestBehavior.AllowGet);
}
In my stored procedure, I have two select statement based on two result set i would like to get the details using linq with c# code.
Below is my Stored Procedure :
Create Proc SP_GetResult
As
Begin
Select Id,Name from EmployeeTable;
Select ContactNo,DateOfBirth from Customer;
End
I tried below code to call Stored Procedure using Linq :
Public void SelectValues()
{
using (Entities1 entity = new Entities1())
{
var list = entity.SP_GetResult
foreach (var test in list)
{
var test12123 = test;
}
}
}
I can get only EmployeeTable details. But, I can't get Customer table details.
How to do it ?
Any idea ?
It's achievable, there is a great explanation in MSDN . Check this article, it shows two ways to achieve it:
https://msdn.microsoft.com/en-us/data/jj691402.aspx
For Your requirements use approach 2 (Multiple Result Sets with Configured in EDMX).
Once You do this You can use those results with Linq without a problem.For example
Public void SelectValues()
{
using (Entities1 entity = new Entities1())
{
var Employees = entity.SP_GetResult;
var Customers = Employees.GetNextResult<Customer>();
// do your stuff
}
}
To 'join' those 2 collection You can use a Tuple Collection
Tuple<EmployeeTable, Customer>
Since the above approach works only for .NET 4.5 and higher, you can use the first approach from the article. It also suits you. You can use linq there. Look at my example:
public List<EmployeeTable> GetEmployees()
{
using(var ctx = new myEntities())
{
var cmd = ctx.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[SP_GetResult]";
var reader = cmd.ExecuteReader();
//reader.NextResult(); <- uncomment this to get second result(Customer)
var employees = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<EmployeeTable>(reader, "EmployeeTable", MergeOption.AppendOnly);
return employees;
}
Now You can use linq like:
var boss = GetEmployees().FirstOrDefault(x => x.Name == "BossName");
Alternative:
Actually to do such simple queries You don't need to have it in one sp. You don't even need store procedures for this. Having only EF You can get it like this:
using (var ctx = new myEntities())
{
var employyes = from x in ctx.EmployeeTable select new
{
id = x.Id,
name = x.Name
}
}
I'm trying to do a LINQ statement using three database tables for my third dropdownlist. Below are my codes but I get an error (for my third dropdownlist) when I choose a cluster in the second dropdownlist.
**//SECTORS**
public JsonResult GetSectors()
{
using (SAMPDBEntities context = new SAMPDBEntities())
{
var ret = context.SECLIBs
.Select(x => new { x.seccd, x.unitacro }).ToList();
return Json(ret, JsonRequestBehavior.AllowGet);
}
}
**//CLUSTERS**
public JsonResult GetCluster(string seccd)
{
using (SAMPDBEntities context = new SAMPDBEntities())
{
var ret = context.CLUSLIBs
.Where(x => x.seccd.Contains(seccd))
.Select(x => new { x.cluscd, x.unitdesc }).ToList();
return Json(ret, JsonRequestBehavior.AllowGet);
}
}
**//EMPLOYEES**
public JsonResult GetEmployee(string cluscd)
{
using (SAMPDBEntities context = new SAMPDBEntities())
{
var ret = context.UNILIBs
.Where(a => a.cluscd.Contains(cluscd))
.Include(x => x.PPSAs.Select(y => y.EMPFILE.empno))
.ToList();
return Json(ret, JsonRequestBehavior.AllowGet);
}
}
Here's my error:
A specified Include path is not valid. The EntityType 'SAMPDBModel.EMPFILE' does not declare a navigation property with the
name 'empno'.
and here's the SQL query (for my third dropdownlist):
SELECT DISTINCT e.empno, e.lname, e.fname, e.mname, c.cluscd
FROM SECLIB a
INNER JOIN CLUSLIB b
ON a.seccd = b.seccd
INNER JOIN UNILIB c
ON b.cluscd = c.cluscd
INNER JOIN PPSA d
ON c.unitcode = d.unitcd
INNER JOIN EMPFILE e
ON d.empno = e.empno
WHERE e.empstat = 1 AND c.cluscd = #cluscd
I need to do a cascading dropdownlist and I need to show the list of employees based on the selected sector and cluster. How can I do that using multiple tables? Please help me. Thanks in advance!
This should be an issue of not specifying the correct respective name that generated from EDMX. Please Can you check the "EMPFILE" Class that generated from Entity Framework It should have similar name with different case sensitive word.
When querying in SQL it does not bother with case sensitivity. But C# is case sensitive language.
And its better if you can post the "EMPFILE" class and database table here.
I have the following extract of code and I get the above error. This is a really simple query and of course works perfectly in SQL. What am I missing?
public IEnumerable<PAYSHIST> GetPayrollCriteria(string COID, IEnumerable<ASITE> sites)
{
var recs = from p in _entities.PAYSHISTs
join a in sites on p.SITE_CODE equals (a.SALES_ACC + a.SITE_NUMBER.ToString("000"))
select p;
return recs;
}
You cant join an in memory collection with a database table. Try writing the query as follows instead using Contains:
var recs = from p in _entities.PAYSHISTs
where sites.Select(a => a.SALES_ACC + a.SITE_NUMBER.ToString("000"))
.Contains(p.SITE_CODE)
select p;