Await results from DataContext.ExecuteQuery - c#

I have a method which is being called by an MVC controller which, in turn, is being called using Ajax in a web application. It used to look like this:
public static IEnumerable<DepartmentViewModel> GetDepartments()
{
DataContext db = new DataContext(ConfigurationHelper.DepartmentsConnectionString);
string sql = "SELECT DISTINCT RTRIM(DEP_CODE) AS [DepartmentCode], RTRIM(DEP_NAME) AS
[DepartmentName] " +
"FROM [departmentinfo].[dbo].[Dep_School_Faculty] " +
"WHERE [DEPARTMENT_IN_USE] = 'Y' AND [VALID] = 'Y' " +
"ORDER BY [DepartmentName]";
IEnumerable<DepartmentViewModel> departments = db.ExecuteQuery<DepartmentViewModel>(sql);
return departments;
}
The SQL query takes a few seconds to execute, which is why it is being called by Ajax after the rest of the web page loads, so the user can be getting on with other stuff while the department list loads. This works great.
Now, though, I need to modify the query slightly and do something with the results of the data on the back end before passing it to the controller, so I updated my method to look like this:
public static async Task<IEnumerable<AcademicAreaViewModel>> AllAcademicAreas()
{
IEnumerable<DepartmentSchoolFacultyModel> deptSchoolFaculty = await GetAllAcademicAreas();
using (DataContext db = new DataContext(ConfigurationHelper.DepartmentsConnectionString))
{
string sql = "SELECT DISTINCT RTRIM(DEP_CODE) AS [DepartmentCode], RTRIM(DEP_NAME) AS [DepartmentName], " +
"RTRIM(SCHOOL_CODE) AS [SchoolCode], RTRIM(SCHOOL_NAME) AS [SchoolName], " +
"RTRIM(FACULTY_CODE) AS [FacultyCode], RTRIM(FACULTY_NAME) AS [FacultyName] " +
"FROM [departmentinfo].[dbo].[Dep_School_Faculty] " +
"WHERE [DEPARTMENT_IN_USE] = 'Y' AND [VALID] = 'Y'";
deptSchoolFaculty = db.ExecuteQuery<DepartmentSchoolFacultyModel>(sql);
};
IEnumerable<AcademicAreaViewModel> departments = deptSchoolFaculty
.GroupBy(d => d.DepartmentCode)
.Select(g => new AcademicAreaViewModel()
{
Name = g.First().DepartmentName,
Code = "Department:" + g.First().DepartmentCode
})
.ToList();
IEnumerable<AcademicAreaViewModel> schools = deptSchoolFaculty
.GroupBy(d => d.SchoolCode)
.Select(g => new AcademicAreaViewModel()
{
Name = g.First().SchoolName,
Code = "School:" + g.First().SchoolCode
})
.ToList();
IEnumerable<AcademicAreaViewModel> academicAreas = departments.Concat(schools);
return academicAreas;
}
The problem I then hit was that the line beginning IEnumerable<AcademicAreaViewModel> departments = deptSchoolFaculty was throwing a null exception error because the deptSchoolFaculty variable hadn't yet been populated with data.
So, I thought, here's a typical use case for asynchronous programming, which just happens to my nemesis (no matter how much I read on the subject or implement it, it just never "clicks" in my head).
I refactored my code like this:
public static async Task<IEnumerable<AcademicAreaViewModel>> AllAcademicAreasAsync()
{
IEnumerable<DepartmentSchoolFacultyModel> deptSchoolFaculty = await GetAllAcademicAreasAsync();
IEnumerable<AcademicAreaViewModel> departments = deptSchoolFaculty
.GroupBy(d => d.DepartmentCode)
.Select(g => new AcademicAreaViewModel()
{
Name = g.First().DepartmentName,
Code = "Department:" + g.First().DepartmentCode
})
.ToList();
IEnumerable<AcademicAreaViewModel> schools = deptSchoolFaculty
.GroupBy(d => d.SchoolCode)
.Select(g => new AcademicAreaViewModel()
{
Name = g.First().SchoolName,
Code = "School:" + g.First().SchoolCode
})
.ToList();
IEnumerable<AcademicAreaViewModel> academicAreas = departments.Concat(schools);
return academicAreas;
}
private static Task<IEnumerable<DepartmentSchoolFacultyModel>> GetAllAcademicAreasAsync()
{
return Task.Run(() =>
{
IEnumerable<DepartmentSchoolFacultyModel> deptSchoolFaculty = new List<DepartmentSchoolFacultyModel>();
using (DataContext db = new DataContext(ConfigurationHelper.DepartmentsConnectionString))
{
string sql = "SELECT DISTINCT RTRIM(DEP_CODE) AS [DepartmentCode], RTRIM(DEP_NAME) AS [DepartmentName] " +
"FROM [departmentinfo].[dbo].[Dep_School_Faculty] " +
"WHERE [DEPARTMENT_IN_USE] = 'Y' AND [VALID] = 'Y' " +
"ORDER BY [DepartmentName]";
return db.ExecuteQuery<DepartmentSchoolFacultyModel>(sql);
}
});
}
Unfortunately, the same line throws the same null exception error, even though I'm using the await keyword to, I hoped, wait for the result of the SQL query before proceeding.
First of all, I don't understand why, in the original version of my code, the method seemed to wait for the SQL query to be complete before returning results (since results were always returned to my page), but now that I'm trying to work with the results in the same method it doesn't.
Secondly, whilst I suspect that the answer to my problem may be to use one of the asynchronous data methods instead of DataContext.ExecuteQuery, I would like to understand why what I've written doesn't work. Also, I much prefer the conciseness of ExecuteQuery for writing the results of the sql query straight into a C# model in one line.

You have problem with disposing DataContext before finishing enumeration of the result. It can be fixed by adding .ToList() call or extending scope for using.
deptSchoolFaculty = db.ExecuteQuery<DepartmentSchoolFacultyModel>(sql).ToList();

Related

Convert method to async

I'm trying to make this method asynchronous.
I found posts related to my question ( you might think this is duplicate) and I'm not sure how to apply them to this method.
Could use some help.
public async Task<IEnumerable<E2307DetailsViewModel>> GetE2307Details(long headerID)
{
// it's slow here; takes a lot of steps when finding the header Id
var E2307Details = await entities.AP_SUPPLIER_2307.AsEnumerable().Where(x => x.AP_2307_HDR_ID == headerID).Select(x => new E2307DetailsViewModel
{
APSupplier2307ID = x.AP_SUPPLIER_2307_ID,
AP2307HeaderID = x.AP_2307_HDR_ID,
UploadFileID = x.UL_ID,
TransactionAPJEID = x.TRANS_APJE_ID,
TransactionDescription = x.TRANS_DESCRIPTION,
TransactionDate = x.TRANS_DATE,
ReferenceNo = x.REFERENCE_NO,
InvoiceNo = x.INVOICE_NO,
ATCCode = x.ATC_CODE,
TaxRate = x.TAX_RATE,
AmtOfTaxWithHeld = x.AMOUNT_OF_TAX_WITHHELD,
ForTheMonthOf = GetUploadFileDetails(x.UL_ID).FOR_THE_MONTH_OF,
IncomePayment = x.AMOUNT_OF_TAX_WITHHELD / (x.TAX_RATE / 100),
MonthNo = GetUploadFileDetails(x.UL_ID).FOR_THE_MONTH_OF_NO.GetValueOrDefault(0),
NatureOfPayment = GetTaxCode().FirstOrDefault(y => y.ATCCode == x.ATC_CODE).NatureOfPayment,
ForTheYearOf = GetUploadFileDetails(x.UL_ID).FOR_THE_YEAR
});
return E2307Details;
}
Edit: I tried replacing 'Where' with 'FirstOrDefaultAsync' but it says
IEnumerable<AP_Supplier_2307> does not contain a definition for
'FirstOrDefaultAsync'
Edit: When I checked this on Debug mode and tried "Step Into", it takes too much time in this code Where(x => x.AP_2307_HDR_ID == headerID) hence why I'm trying to make this method async.
You may need to rewrite the method like this:
public async Task<IEnumerable<E2307DetailsViewModel>> GetE2307Details(long headerID)
{
// it's slow here; takes a lot of steps when finding the header Id
var E2307Details = (await entities.AP_SUPPLIER_2307.Where(x => x.AP_2307_HDR_ID == headerID).ToListAsync())
.Select(x => new E2307DetailsViewModel
{
APSupplier2307ID = x.AP_SUPPLIER_2307_ID,
AP2307HeaderID = x.AP_2307_HDR_ID,
UploadFileID = x.UL_ID,
TransactionAPJEID = x.TRANS_APJE_ID,
TransactionDescription = x.TRANS_DESCRIPTION,
TransactionDate = x.TRANS_DATE,
ReferenceNo = x.REFERENCE_NO,
InvoiceNo = x.INVOICE_NO,
ATCCode = x.ATC_CODE,
TaxRate = x.TAX_RATE,
AmtOfTaxWithHeld = x.AMOUNT_OF_TAX_WITHHELD,
ForTheMonthOf = GetUploadFileDetails(x.UL_ID).FOR_THE_MONTH_OF,
IncomePayment = x.AMOUNT_OF_TAX_WITHHELD / (x.TAX_RATE / 100),
MonthNo = GetUploadFileDetails(x.UL_ID).FOR_THE_MONTH_OF_NO.GetValueOrDefault(0),
NatureOfPayment = GetTaxCode().FirstOrDefault(y => y.ATCCode == x.ATC_CODE).NatureOfPayment,
ForTheYearOf = GetUploadFileDetails(x.UL_ID).FOR_THE_YEAR
});
return E2307Details;
}
Changes:
I removed the AsEnumerable, so that the WHERE is executed in the database
Then added a ToListAsync, that asynchrounously gets all matching records
An extra pair of ( ) around the previous expression, so that the Select works on the List, not on a Task (where it doesn't work)
Remarks
Right now you are calling GetUploadFileDetails(x.UL_ID) three times per record. That can probably be optimized.
You may want to add another .ToList() at the end of that query.

ServiceStack ormLite chaning OrderBy

I am trying to so the following:
var routines = con.Select<Table>(con.From<Table>().OrderBy(p => p.Field1).ThenBy(i => i.Field2));
The above works perfectly. But I want a rather more generic approach and parse a string like sort="field1,field2". I have the following code:
int sortFieldCount = 0;
var itemsq = con.From<Table>();
foreach (var name in orderByField.Split(',')) {
if(sortFieldCount == 0)
itemsq = sortOrderAscending ? itemsq.OrderBy(name) : itemsq.OrderByDescending(name);
else
itemsq = sortOrderAscending ? itemsq.ThenBy(name) : itemsq.ThenByDescending(name);
sortFieldCount++;
}
But the above code seems to overwrite the first OrderBy. Is there a solution to such a problem?
Thanks
Other ways you can perform multiple Order By's with ServiceStack.OrmLite include:
var orderByAnonType = db.Select(db.From<Track>().OrderBy(x => new { x.Album, x.Name }));
var orderByString = db.Select(db.From<Track>().OrderByFields("Album","Name"));
// Use `-` prefix to inverse sort order, e.g. Album Descending
var orderByString = db.Select(db.From<Track>().OrderByFields("-Album","Name"));
var orderByArray = db.Select(db.From<Track>().OrderBy(x => new[]{ "Album","Name" }));
So you could get a flexible OrderBy like AutoQuery's OrderBy with:
var fields = orderByString.Split(',', StringSplitOptions.RemoveEmptyEntries);
q.OrderByFields(fields);
Here's a live example of this you can play around with on gistlyn.com
There is a couple problems in the accepted answer I'd like to address.
First is the possibility of SQL injection attacks. ServiceStack does not fully validate what you pass in to the list of sort columns. While it will detect some of the more obvious attacks, you could still slip in things like calls to stored functions.
The second problem is descending sorts. It's not obvious from the API, but you can pass in "columnName DESC" rather than just "columnName". In fact, this is how it is able to support "Album,Name", it just passes it directly to SQL with the barest amount of validation.
public IList<Employee> SortBy(string lastName, string sortByColumnA, bool isDescendingA, string sortByColumnB, bool isDescendingB)
{
if (!Utilities.EmployeeColumnNames.Contains(sortByColumnA))
throw new ArgumentOutOfRangeException(nameof(sortByColumnA), "Unknown column " + sortByColumnA);
if (!Utilities.EmployeeColumnNames.Contains(sortByColumnB))
throw new ArgumentOutOfRangeException(nameof(sortByColumnB), "Unknown column " + sortByColumnB);
var sortDirectionA = isDescendingA ? " DESC " : "";
var sortDirectionB = isDescendingB ? " DESC " : "";
using (var db = _dbConnectionFactory.OpenDbConnection())
{
return db.Select(db.From<Employee>().Where(x => x.LastName == lastName)
.OrderBy(sortByColumnA + sortDirectionA + "," + sortByColumnB + sortDirectionB)).ToList();
}
}

Appending to the Select part of a query

We have a duplicate part of our LINQ METHOD syntax query. Here is a contrived example.
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = p.qtyOrdered + p.alreadysent,
AWATING = p.qtyOrdered + p.alreadysent
}).ToList();
So we are trying to resolve the duplicate part by putting something in a method and then calling that and getting some sort of result. So something like this....
private IQueryable WhatsLeft()
{
IQueryable<orders> query = _context.Set<orders>();
return query.Select(p => new{p.qtyOrdered + p.alreadysent});
}
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = WhatsLeft(),
AWATING = WhatsLeft()
}).ToList();
Is this at all possible and if so can anyone give me some brief advise on how I would achieve this.
Wouldn't you just simply pass the Order object to the new function directly?
private int Total(Order order)
{
return order.qtyOrdered + order.alreadySent;
}
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = Total(p),
AWATING = Total(p)
}).ToList();
If I understand what you're after correctly. I can't remember off the top of my head how well Linq to sql etc can handle functions, interpreting them into SQL functions. Maybe you could give it a try.
Alternatively, to reduce the complexity of the function (to facilitate L2S conversion) you can make the parameters granular on the function such as:
private int Total(int left, int right)
{
return left + right;
}
Then make the call more like:
var result = query.Select(p => new{
REMAINING = Total(p.qtyOrdered, p.alreadysent),
AWATING = Total(p.qtyOrdered, p.alreadysent)
}).ToList();
UPDATE:
Have you thought about querying the calculation up front?
var result = query.Select(c => c.qtyOrdered + c.alreadysent).Select(p => new {
REMAINING = p,
AWAITING = p
}).ToList();

Concatenate string in Linq query

I have a db call that returns me an object. I use linq to cast that object how I want it
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList();
I now want to add the line rental to the tariff name show that it shows like:
myTariff - 12.99
when I try and do this though I can make this change ok:
Text = c.TariffName + " - ",
but when I try and add the line rental I get problems that linq won't recognise the ToString(). I need it to look like:
Text = c.TariffName + " - " + c.LineRental.ToString(),
I understand that linq won't recognise the ToString() method from reading LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression but how do I change this given I can't set it as a string prior to the linq query?
Convert the query result to a list first then use select to make the toString work.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count);
var list = result.ToList().Select(c => new
{
Text = c.TariffName + " - " + c.LineRental.ToString(),
Key = c.TariffId,
Price = c.LineRental
});
What is happening linq trying to execute your select statement on the database query level, and it does not know how to transform your .Select lambda to select part of sql statement.
Easiest thing you can do is first query required fields, call .ToList() on query to execute it and perform projection after that.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList().Select(c=>new {
Text = string.Format("{0} - {1}", c.Text, c.Price),
Key=Key,
Price=Price
});

LINQ to Entities create dynamic field

I am trying to create a dynamic field using LINQ to Entities w/ EF5. Based on certain conditions (which I've encapsulated in a function) the dynamic field will be populated with different values.
I referenced the post here but when I run the code below, I get the follow error:
LINQ to Entities does not recognize the method 'System.String FormatName()' method, and this method cannot be translated into a store expression.
public static IEnumerable<dynamic> SelectAllCustomers()
{
using (var db = new DatabaseContext())
{
var query = db.Customer.Select(c => new
{
c.ID,
FullNameLastFirstMiddle = FormatName(c.First_Name, c.Middle_Name, c.Last_Name),
}
);
return query.ToList();
}
}
private static string FormatName(string first, string middle, string last)
{
//format name
if (!String.IsNullOrWhiteSpace(first))
{
if (!String.IsNullOrWhiteSpace(middle))
return last + ", " + first + " " + middle;
else
return last + ", " + first;
}
else
{
if (!String.IsNullOrWhiteSpace(middle))
return last + ", " + middle;
else
return last;
}
}
Any ideas on how best to dynamically build a field with sofisticated logic using LINQ to Entities?
String formatting like that doesn't really need to be translated to SQL and performed on the database side in the first place. Instead just query the database for the information that you need and then perform the string manipulation on the application side using LINQ to objects:
var query = db.Customer.Select(c => new
{
c.ID,
c.First_Name,
c.Middle_Name,
c.Last_Name,
})
.AsEnumerable()
.Select(c => new
{
c.ID,
FullNameLastFirstMiddle =
FormatName(c.First_Name, c.Middle_Name, c.Last_Name),
});

Categories

Resources