Convert OrderBy Case statement in LINQ - c#

I'm trying to convert the following SQL from Oracle into a Linq to Entity query.
ORDER by
case when(e.prev_co = 'ABC' and(nvl(co_seniority, '1-jan-2099') < to_date('10-apr-2001')))
then '2001-04-01'
else to_char(nvl(co_seniority, '1-jan-2099'), 'YYYY-MM-DD') end,
nvl(co_seniority, '1-jan-2099'),
birth_dt
I was hoping I could use a function to pass in some parameters and have it return the correct date. I tried creating an new property called SortDate and then created a function on my page that would take in the parameters and return the correct date but that didn't work. I get and exception that says "LINQ to Entities does not recognize the method GetSortDate".
Model
SortByDate = GetSortDate(e.PREV_CO, e.CO_SENIORITY),
Function
public static DateTime GetSortDate(string PreviousCo, DateTime? CoSeniorityDate)
{
//set variable to default date
DateTime sortDate = System.DateTime.Parse("2001-04-01");
//set default date if NULL
if (CoSeniorityDate == null)
{
CoSeniorityDate = System.DateTime.Parse("2099-01-01");
}
if (PreviousCo == "ABC" && (CoSeniorityDate < System.DateTime.Parse("2001-04-10")))
{
sortDate = System.DateTime.Parse("2001-04-01");
}
else
{
sortDate = System.DateTime.Parse(CoSeniorityDate.ToString());
}
return sortDate;
}
Here is my complete EF
using (DataContext db = new DataContext())
{
db.Configuration.AutoDetectChangesEnabled = false; //no changes needed so turn off for performance.
var workStatus = new string[] { "1", "3" };
var company = new string[] { "EX", "SM" };
var eventReason = new string[] { "21", "22", "23" };
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY == null ? DateTime.MaxValue : e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
SortByDate = GetSortDate(e.PREV_CO, e.CO_SENIORITY),
PreviousCo = e.PREV_CO
}).OrderBy(o => o.SortByDate).ThenBy(o => o.SeniorityDate).ThenBy(o => o.BirthDate).ToList();
}
Anyone have a suggestion on how I can convert this OrderBy?
UPDATED QUESTION
At the moment I have my query working correctly by using a secondary SELECT like #Markus showed. The first query just pulls the data and then all the formatting and calling of a method to get the correct SortByDate.
However, my manager would really prefer to do the sorting in the DB versus in memory. He let this one go because there are very few people calling this seniority list and only once a month.
For learning purposes I would like to see if I could get the DB to do all the sorting as #IvanStoev shows below. So, going back that route I’m not able to get the OrderBy to work exactly like it should.
If you look at the original SQL I’m trying to convert it first looks to see if the person had a previous company of “ABC” and if they do then look at the SeniorityDate (set a default date if that’s NULL) and compare it to an acquisition date. If that condition isn’t met then just use their SeniorityDate (set it’s default if NULL). Tricky….I know.
Using the suggested OrderBy in LinqPad and then looking at the returned SQL I can see that the first part of the OrderBy looks at the previous company and then the SeniorityDate and sets a value. Then it looks at the acquisition date. I need to somehow group some conditions to look at first which I don’t know it that’s possible.
SELECT t0.ABV_POSITION_TITLE, t0.BIRTH_DT, t0.CITY_CODE, t0.CO, t0.CO_SENIORITY, t0.EMP, t0.FIRST_NAME, t0.LAST_NAME, t0.PART_TIME_IND, t0.PREV_CO, t0.WORKSTAT
FROM SAP_EMPLOYEE t0
WHERE ((((t0.WORKSTAT IN (:p0, :p1) AND (t0.PERS_SUB_AREA = :p2)) AND SUBSTR(t0.CO, 0 + 1, 2) IN (:p3, :p4)) AND (t0.CO <> :p5)) AND ((t0.EVENT_TYP <> :p6) OR ((t0.EVENT_TYP = :p6) AND NOT t0.EVENT_RSN IN (:p7, :p8, :p9))))
ORDER BY (CASE WHEN ((t0.PREV_CO = :p10) AND (t0.CO_SENIORITY IS NULL)) THEN :p11 WHEN (t0.CO_SENIORITY < :p12) THEN :p13 ELSE COALESCE(t0.CO_SENIORITY, :p11) END), COALESCE(t0.CO_SENIORITY, :p11), t0.BIRTH_DT
-- p0 = [1]
-- p1 = [3]
-- p2 = [200A]
-- p3 = [EX]
-- p4 = [SM]
-- p5 = [EXGS]
-- p6 = [35]
-- p7 = [21]
-- p8 = [22]
-- p9 = [23]
-- p10 = [ABC]
-- p11 = [1/1/2099 12:00:00 AM]
-- p12 = [4/10/2001 12:00:00 AM]
-- p13 = [4/1/2001 12:00:00 AM]
I need to come up with something like
ORDER BY (CASE WHEN ((t0.PREV_CO = :p10) AND (COALESCE(t0.CO_SENIORITY, :p11) < :p12) THEN :p13 ELSE COALESCE(t0.CO_SENIORITY, :p11) END)
Here is the code I used in LinqPad.
void Main()
{
var workStatus = new string[] { "1", "3" };
var company = new string[] { "EX", "SM" };
var eventReason = new string[] { "21", "22", "23" };
var baseDate = new DateTime(2001, 4, 10); // 10-apr-2001
var minDate = new DateTime(2001, 4, 1); // 1-apr-2001
var abcDate = new DateTime(2001, 4, 10); // 10-apr-2001
var maxDate = new DateTime(2099, 1, 1); // 1-jan-2099
var data = (from e in SAP_EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& e.PERS_SUB_AREA == "200A"
&& company.Contains(e.CO.Substring(0, 2))
&& e.CO != "EXGS"
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
orderby e.PREV_CO == "ABC" && e.CO_SENIORITY == null ? maxDate : e.CO_SENIORITY < abcDate ? minDate : e.CO_SENIORITY ?? maxDate,
e.CO_SENIORITY ?? maxDate,
e.BIRTH_DT
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY == null ? maxDate :
e.PREV_CO == "ABC" && e.CO_SENIORITY < twaDate ? maxDate : e.CO_SENIORITY,
LOA = e.WORKSTAT == "1" ? "LOA" : "",
ABC = e.PREV_CO == "ABC" ? "ABC" : "",
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList();
data.Dump();
}

The reason for the exception is that entity framework generates the SQL query when you execute it. In your case, this happens with the call to ToList() at the end. In order to generate the SQL query, entity framework analyzes the query and transforms it into SQL. As entity framework does not know your function, it cannot generate the SQL statements for it.
In order to solve this, you need to first execute the query and do the sort operation in memory on the results. In order to limit the amount of data that is transferred to the client, you should execute the query including the where clause and also tell EF which fields you are interested in to avoid a SELECT * FROM ... that includes all fields of the table.
You could change your query approximately as follows:
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
select new ()
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList().Select(x => new Employee()
{
Co = x.Co,
CityCode = x.CityCode,
EmployeeNumber = x.EmployeeNumber,
LastName = x.LastName,
FirstName = x.FirstName,
Position = x.Position,
EmploymentType = x.EmploymentType,
SeniorityDate = x.SeniorityDate ?? DateTime.MaxValue,
BirthDate = x.BirthDate,
SortByDate = GetSortDate(x.PreviousCo, x.SeniorityDate),
PreviousCo = x.PreviousCo
}).OrderBy(o => o.SortByDate)
.ThenBy(o => o.SeniorityDate)
.ThenBy(o => o.BirthDate).ToList();
This query first filters the data as specified in the where clause and then uses an anonymous type to retrieve only the relevant fields - including the ones that are later used as an input to the GetSortDate method with its original values. After the first ToList the results are present in memory and you can first add a new select that creates the Employee objects including the sort date. These objects are then ordered by sort date and so on.
A small hint for the GetSortDate method: specifying DateTime constants as a string that is parsed is not a good idea as parsing is dependent on the culture of the thread (if no culture is specified).
// Culture dependent
sortDate = System.DateTime.Parse("2001-04-01");
// Better
sortDate = new DateTime(2001, 04, 01);

As you already noticed (the hard way), in LINQ to Entities query you cannot use local methods like in LINQ to Objects. If you want the whole query to be executed in the database, you need to embed the logic inside the query using only the supported constructs.
With that being said, the equivalent of your SQL query should be something like this
var baseDate = new DateTime(2001, 4, 10); // 10-apr-2001
var minDate = new DateTime(2001, 4, 1); // 1-apr-2001
var maxDate = new DateTime(2099, 1, 1); // 1-jan-2099
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
let seniorityDate = e.CO_SENIORITY ?? maxDate
let sortDate =
e.CO_SENIORITY == null ? maxDate :
e.PREV_CO == "ABC" && e.CO_SENIORITY < baseDate ? minDate :
e.CO_SENIORITY
orderby sortDate, seniorityDate, e.BIRTH_DT
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList();
Update: For learning purposes I've updated the answer with using let clauses.
Now regarding the concrete ordering. I could have written the "SortDate" part exactly the way you did it, but I believe my way is a better equivalent. Why?
Here is my "SortDate" interpretation in pseudo code
if (CoSeniorityDate == null)
SortDate = #2099-01-01#
else if (PreviousCo == "ABC" && CoSeniorityDate < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = CoSeniorityDate
And here is your function
if (CoSeniorityDate == null) CoSeniorityDate = #2099-01-01#
if (PreviousCo == "ABC" && CoSeniorityDate < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = CoSeniorityDate
Let CoSeniorityDate == null. Then, according to your logic, let substitute CoSeniorityDate = #2099-01-01#:
if (PreviousCo == "ABC" && #2099-01-01# < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = #2099-01-01#
Since #2099-01-01# < #2001-04-10# is always false, it becomes simple
SortDate = #2099-01-01#
i.e. exactly like the first part of my criteria. In the else part we already know CoSeniorityDate is not null and can just check the other conditions.
Anyway, doing it your way would be like this
let sortDate = e.PREV_CO == "ABC" && seniorityDate < baseDate ? minDate : seniorityDate

Related

Filtering nullable items in the List - LINQ

I have the following List item in order to display. I could visualize the small list follows, that could be hundreds rows. StartDate and also EndDate can be nullable, if EndDate is null, it means the course still open.
CourseId ClassName StartDate EndDate isActiveinDB
-------- --------- --------- ------- ------------
12321 Math 08-25-2017 12-02-2017 Y
32342 Math 08-25-2017 12-02-2017 N
25325 Math 01-25-2018 - Y
If I pass today date (06-06-2018) in the following method, it returns me all courses rather than only the last course (Math 25325) which has not expired and open based on isActiveinDB.
I wonder what is not correct with the following implementation.
public List<Courses> GetClassesByDate( DateTime date, List<Courses> allCourses)
{
List<Courses> courses = allCourses.Where( x => x.StartDate.HasValue ? x.StartDate <= date : true
&& x.EndDate.HasValue ? x.EndDate.Value >= date : true
&& x.isActiveinDB.Equals("Y")).ToList();
return courses;
}
Thanks to #DavidG, implementation is in the following link
Try (if StartDate is nullable, as you said):
List<Courses> courses = allCourses.Where( x =>
(x.StartDate.HasValue ? x.StartDate.Value <= date : true)
&& (x.EndDate.HasValue ? x.EndDate.Value >= date : true)
&& x.isActiveinDB.Equals("Y")).ToList();
You see the () I've added? I thing what you are doing is actually
x.EndDate.HasValue ? x.EndDate.Value : (true && isActiveinDB.Equals("Y"))
You see? The true isn't a single value, but is a subexpression true && isActiveinDB.Equals("Y")
Operators are in an order to be evaluated in a expression. It is explained in https://msdn.microsoft.com/en-us/library/2bxt6kc4.aspx. There's a table that shows operators in the order in a table.
As you can see in the list, conditional expression (?:) is quite low priority. So, I recommend to put parenthesis any time around the conditional expression, then you can avoid such accident as you have.
The following code is NUnit test code for verifying the operators order. Hope Record class is close to your case.
Executing this test, the first assertion passes but the second one fails. It proves the parenthesis makes difference in such expression.
[TestFixture]
public class SyntaxTest
{
public class Record
{
public string Id;
public DateTime? StartDate;
public DateTime? EndDate;
public string isActiveinDB;
}
[TestCase]
public void TestConditionalSyntax()
{
var list = new List<Record>
{
new Record { Id = "0000", StartDate = DateTime.Parse("2018-01-01"), EndDate = DateTime.Parse("2018-06-01"), isActiveinDB = "Y" },
new Record { Id = "0001", StartDate = DateTime.Parse("2018-01-01"), EndDate = DateTime.Parse("2018-09-01"), isActiveinDB = "N" },
new Record { Id = "0002", StartDate = DateTime.Parse("2018-01-01"), EndDate = DateTime.Parse("2018-08-01"), isActiveinDB = "Y" },
new Record { Id = "0003", StartDate = DateTime.Parse("2018-01-01"), EndDate = null, isActiveinDB = "Y" },
new Record { Id = "0004", StartDate = DateTime.Parse("2018-08-01"), EndDate = null, isActiveinDB = "Y" },
new Record { Id = "0005", StartDate = null, EndDate = DateTime.Parse("2018-06-01"), isActiveinDB = "Y" },
new Record { Id = "0006", StartDate = null, EndDate = DateTime.Parse("2018-08-01"), isActiveinDB = "Y" },
};
var date = DateTime.Parse("2018-06-15");
var result1 = list.Where(x => ( x.StartDate.HasValue ? x.StartDate <= date : true )
&& ( x.EndDate.HasValue ? x.EndDate >= date : true )
&& x.isActiveinDB.Equals("Y")).ToList();
Assert.That(result1.Count, Is.EqualTo(3));
var result2 = list.Where(x => x.StartDate.HasValue ? x.StartDate <= date : true
&& x.EndDate.HasValue ? x.EndDate >= date : true
&& x.isActiveinDB.Equals("Y")).ToList();
Assert.That(result2.Count, Is.EqualTo(3));
}
}

Assign value to String Property based on integer Property value in Linq query

I have a linq query, and want to assign string value to a string property, which in dependent on another int property value. I have saved int value in database, and now want to get appropriate string value for the saved integer value. Below is my query..
var data = db.ProjectSetups.Select(c => new ProjectSetup
{
ProjectId = c.ProjectId,
Name = c.Name,
NameArabic = c.NameArabic,
StartDate = c.StartDate,
EndDate = c.EndDate,
Date = c.Date,
StringType = (c.Type.ToInt32() == 1 ? "Development" : "Rental").ToString(),
StringStatus = (c.Status == 1 ? "InProgress" :
c.Status == 2 ? "Completed" :
c.Status == 3 ? "Dividend" : "Closed"),
LandArea = c.LandArea,
SaleAmount = c.SaleAmount
}).ToList();
I got below error
The entity or complex type 'PMISModel.ProjectSetup' cannot be constructed in a LINQ to Entities query
var data = db.ProjectSetups.ToList();
data = data.Select(c => new ProjectSetup
{
ProjectId = c.ProjectId,
Name = c.Name,
NameArabic = c.NameArabic,
StartDate = c.StartDate,
EndDate = c.EndDate,
Date = c.Date,
StringType = (c.Type.ToInt32() == 1 ? "Development" : "Rental").ToString(),
StringStatus = (c.Status == 1 ? "InProgress" :
c.Status == 2 ? "Completed" :
c.Status == 3 ? "Dividend" : "Closed"),
LandArea = c.LandArea,
SaleAmount = c.SaleAmount
}).ToList();

Summarising existing data into newly created record

I am experienced in SQL and less experienced in LINQ and OOP in general, the result being much frustration with LINQ, so please bear with me.
I'm using MVC / Entity Framework as per tags below.
I have two tables. One table called Header is bound to a grid. When I create a record to
be inserted into this Header table, I need to look up some matching related Detail records and summarise and show them in the grid.
I'll limit this to the LINQ aspects for now.
For example I have these detail records:
Date Segment Location Amount1 Amount2
2013-12-01 ABC ZZ 12 2
2013-12-02 ABC ZZ 50 3
2013-12-03 ABC ZZ 2 4
2013-12-01 DEF ZZ 7 5
and I create this header record in my grid:
DateFrom DateTo Segment Location DetailAmount1 DetailAmount2
2013-12-01 2013-12-07 ABC ZZ ( to be populated )
DetailAmount1 should be 64, DetailAmount2 should be 9
So my view calls the Grid_Create action in the controller to get a a viewmodel back with required data (which should have nothing except my summarised detail values and a DB generated key)
This is my controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Grid_Create(
[DataSourceRequest]DataSourceRequest request,
[Bind(Prefix = "models")]IEnumerable<Header_ViewModel> objects,
string Location,
int? Segment_ID,
DateTime? Start_Date,
DateTime? End_Date)
{
using (var MyDB = new DBEntities())
{
// Keep the inserted entitites here.
// Used to return the result later.
var entities = new List<Header_Table>();
if (ModelState.IsValid)
{
foreach (var obj in objects)
{
// PART A: Summarise estimates out of Detail
var est =
(from e in MyDB.Detail_Table
where e.SRC_System == Location
where e.Segment_ID == Segment_ID
where e.Transaction_Date >= Start_Date
where e.Transaction_Date <= End_Date
group e by e.Segment_ID into e
select
new Header_ViewModel
{
Amount1 = e.Sum(x => x.Amount1),
Amount2 = e.Sum(x => x.Amount2),
Amount3 = e.Sum(x => x.Amount3),
Amount4 = e.Sum(x => x.Amount4),
Amount5 = e.Sum(x => x.Amount5),
Amount6 = e.Sum(x => x.Amount6)
})
.FirstOrDefault();
// PART B: If there are no estimates, generate a 0
if (est == null)
{
est = new Header_ViewModel
{
Amount1 = 0,
Amount2 = 0,
Amount3 = 0,
Amount4 = 0,
Amount5 = 0,
Amount6 = 0
};
}
// PART C: Create a new entity
// and set its properties from the posted model
var entity = new Header_Table
{
Transaction_ID = obj.Transaction_ID,
Value1 = obj.Value1,
Value2 = obj.Value2,
Value3 = obj.Value3,
// Summary from detail table
Amount2 = est.Amount2,// obj.Amount2,
Amount3 = est.Amount3, // obj.Amount3,
Amount1 = est.Amount1,// obj.Amount1,
Amount4 = est.Amount4, //obj.Amount4,
Amount5 = est.Amount5, // obj.Amount5,
Amount6 = est.Amount6, // obj.Amount6,
Location = Location,
Segment_ID = Segment_ID,
Start_Date = Start_Date,
End_Date = End_Date,
// assign default values
Updated_By = User.Identity.Name,
Updated_Date = DateTime.Now
};
// Add the entity
MyDB.Header_Table.Add(entity);
// Store the entity for later use
entities.Add(entity);
}
// Insert the entities in the database
MyDB.SaveChanges();
}
// Return the inserted entities. Also return any validation errors.
return Json(
entities.ToDataSourceResult(
request,
ModelState, obj => new Header_ViewModel
{
Transaction_ID = obj.Transaction_ID,
Amount2 = obj.Amount2,
Value1 = obj.Value1,
Amount3 = obj.Amount3,
Amount1 = obj.Amount1
}));
}
}
The questions:
There aren't always detail records to be found. Whats a nice way to default the est object to a single item of zero? Currently in part B I am checking for est==null and loading it up manually. How do I make FirstOrDefault do this for me automatically? (therefore removing part B). I believe I should be able to pass in a type but I can't get the syntax correct, i.e. if I grab everything from new in part B and put it as an argument to FirstOrDefault I get System.Linq.IQueryable<Header_ViewModel> does not contain a definition for 'FirstOrDefault...
When getting the summary in part A, I don't actually need to group by Segment_ID, I just need the total summary for the table. However it appears I have to group by something to get an aggregate in LINQ. I've seen other posts mentioning group by e.GetType() but I get the error LINQ to Entities does not recognize the method 'System.Type GetType()'......
Given that this is a new record, should I be able to populate entity directly out of MyDB.Detail_Table (thereby combining part A and C)? I did try this but got an error every time. Apologies for not posting the exact error, but if someone thinks it's possible I will try again and post errors this time.
Question 1
The easiest solution I can think of is this (using your code as a base):
var est =
(from e in MyDB.Detail_Table
where e.SRC_System == Location
where e.Segment_ID == Segment_ID
where e.Transaction_Date >= Start_Date
where e.Transaction_Date <= End_Date
group e by e.Segment_ID into e
select
new Header_ViewModel
{
Amount1 = e.Sum(x => x.Amount1),
Amount2 = e.Sum(x => x.Amount2),
Amount3 = e.Sum(x => x.Amount3),
Amount4 = e.Sum(x => x.Amount4),
Amount5 = e.Sum(x => x.Amount5),
Amount6 = e.Sum(x => x.Amount6)
})
.FirstOrDefault() ?? new Header_ViewModel();
Question 2
Well, yeah... Not much can be done here, using a constant is possible as explained here. This seems to work:
var est =
MyDB.DetailTableSet.Where(e => e.SRC_System == Location && e.Segment_ID == Segment_ID
&& e.Transaction_Date >= Start_Date && e.Transaction_Date <= End_Date)
.GroupBy(e => 1)
.Select(e => new Header_ViewModel
{
Amount1 = e.Sum(x => x.Amount1),
Amount2 = e.Sum(x => x.Amount2),
})
.SingleOrDefault() ?? new Header_ViewModel();
Question 3
There is no good way to do this. The reason and some work around are explained here, but in your case I would not go that far. This is as far as I would go:
var est =
MyDB.DetailTableSet.Where(e => e.SRC_System == Location && e.Segment_ID == Segment_ID
&& e.Transaction_Date >= Start_Date && e.Transaction_Date <= End_Date)
.GroupBy(e => 1)
.Select(e => new Header_ViewModel
{
Amount1 = e.Sum(x => x.Amount1),
Amount2 = e.Sum(x => x.Amount2),
})
.SingleOrDefault() ?? new Header_ViewModel();
var entity = new HeaderTable()
{
Transaction_ID = obj.Transaction_ID,
Value1 = obj.Value1,
Value2 = obj.Value2,
Value3 = obj.Value3,
// Summary from detail table
Amount1 = est.Amount1,
Amount2 = est.Amount2,
Location = Location,
Segment_ID = Segment_ID,
Start_Date = Start_Date,
End_Date = End_Date,
// assign default values
Updated_By = "Myself",
Updated_Date = DateTime.Now
};
// Add the entity
MyDB.HeaderTableSet.Add(entity);
// Store the entity for later use
entities.Add(entity);

Entity Framework - Results from subquery, speed issue?

I've made a query to get a list of articles, each bound to a "header",
so i retrieve the header plus the related articles and their properties.
The query works, however in its current style it is
Somewhat messy ( in my opinion)
The .ToList() takes way longer than i would expect.
Does anyone see any obvious reason for the speed-issue?
var offerheaders =
from o in dbcontext.F_CAB_OFFER_HEADERS
where
o.OFHD_FK_BUYER == userinfo.orgaTypeSequence
&& o.OFHD_VALID_FROM <= userinfo.selectedDate
&& o.OFHD_VALID_TO >= userinfo.selectedDate
&& o.OFHD_DELETED_YN == 0
&& o.OFHD_DELETED_BY_OWNER_YN == false
&& o.OFHD_OFFER_TYPE == userinfo.offerType
orderby o.OFHD_NO ascending
select o;
var offerlist =
from ofhd in offerheaders
select new {
ofhd = new {
OfferNo = ofhd.OFHD_NO,
OfferSequence = ofhd.OFHD_SEQUENCE,
ValidFrom = ofhd.OFHD_VALID_FROM,
ValidTo = ofhd.OFHD_VALID_TO,
OfferType = ofhd.OFHD_OFFER_TYPE,
Maingroup = new { cdmg_seq = ofhd.F_CAB_CD_MAIN_GROUP_TYPE.CDMG_SEQUENCE, Desc = ofhd.F_CAB_CD_MAIN_GROUP_TYPE.CDMG_DESC },
Supplier = new {
Name = ofhd.F_CAB_GROWER.F_CAB_ORGANISATION.ORGA_NAME,
Pic = ofhd.F_CAB_GROWER.F_CAB_ORGANISATION.ORGA_FK_PICTURE,
Seq = ofhd.F_CAB_GROWER.GROW_SEQUENCE
},
Caption = ofhd.OFHD_CAPTION,
Seperate = ofhd.OFHD_SHOW_SEPARATE_YN,
//ofdts = (from ofdt in dbcontext.F_CAB_OFFER_DETAILS.Where(x => x.OFDT_FK_OFFER_HEADER == ofhd.OFHD_SEQUENCE && x.OFDT_NUM_OF_ITEMS > 0 && x.OFDT_LATEST_DELIVERY_DATE_TIME > compareDateTime && x.OFDT_LATEST_ORDER_DATE_TIME > compareDateTime)
ofdts = from ofdt in dbcontext.F_CAB_OFFER_DETAILS
join props in dbcontext.F_CAB_CAB_PROP on ofdt.OFDT_FK_CAB_CODE equals props.PROP_FK_CABC_SEQ
join cabcode in dbcontext.F_CAB_CD_CAB_CODE on ofdt.OFDT_FK_CAB_CODE equals cabcode.CABC_SEQUENCE
join cabgroup in dbcontext.F_CAB_CD_CAB_GROUP on cabcode.CABC_FK_CAB_GROUP equals cabgroup.CDGR_SEQUENCE
join grouptype in dbcontext.F_CAB_CD_GROUP_TYPE on cabgroup.CDGR_FK_GROUP_TYPE equals grouptype.CDGT_SEQUENCE
join maingrouptype in dbcontext.F_CAB_CD_MAIN_GROUP_TYPE on grouptype.CDGT_FK_MAIN_GROUP equals maingrouptype.CDMG_SEQUENCE
join caca in dbcontext.F_CAB_CAB_CASK_MATRIX on ofdt.OFDT_FK_CACA_SEQ equals caca.CACA_SEQUENCE
join cask in dbcontext.F_CAB_CD_CASK on caca.CACA_FK_CASK equals cask.CDCA_SEQUENCE
join vbncode in dbcontext.F_CAB_CAB_VBN_MATRIX on cabcode.CABC_SEQUENCE equals vbncode.CVMA_FK_CAB_CODE
join grel in dbcontext.F_CAB_GENERAL_RELATIONS on ofdt.OFDT_FK_GREL_SEQ equals grel.GREL_SEQUENCE into greltable
from g_loj in greltable.DefaultIfEmpty()
where
ofdt.OFDT_FK_OFFER_HEADER == ofhd.OFHD_SEQUENCE
&& ofdt.OFDT_NUM_OF_ITEMS > 0
&& props.PROP_FK_CDLA_SEQ == userinfo.lang.CDLA_SEQUENCE
orderby props.PROP_CAB_DESC ascending
select new {
Desc = props.PROP_CAB_DESC,
Group = new { cdgr_seq = cabgroup.CDGR_SEQUENCE, Desc = cabgroup.CDGR_DESC },
Grouptype = new { grouptype.CDGT_SEQUENCE, Desc = grouptype.CDGT_DESC },
Properties = new CABProperties { props = props },
Price = ofdt.OFDT_ITEM_PRICE,
PIC_SEQ = ofdt.OFDT_FK_PICTURE ?? ((cabcode.CABC_FK_PICTURE ?? cabcode.CABC_SEQUENCE)),
PIC_URL = ofdt.OFDT_EXT_PICTURE_REF ?? "",
Seq = ofdt.OFDT_SEQUENCE,
Available = ofdt.OFDT_NUM_OF_ITEMS,
CabCode = ofdt.F_CAB_CD_CAB_CODE.CABC_CAB_CODE,
VBNCode = vbncode.CVMA_FK_VBN_CODE,
Remark = ofdt.OFDT_REMARK,
IsSpecial = ofdt.OFDT_SPECIAL_YN,
Arrived = inTransit ? ofdt.OFDT_ARRIVAL_DATE < DateTime.Now : true,
Cask = new CABCask { cask = cask, caca = caca },
Supplier = g_loj == null ? (ofdt.OFDT_SUPPLIER ?? "") : g_loj.GREL_NAME,
SupplierWeb = g_loj == null ? "" : g_loj.GREL_WEBSITE_URL,
SupplierLogo = g_loj == null ? ofhd.F_CAB_GROWER.F_CAB_ORGANISATION.ORGA_FK_PICTURE : g_loj.GREL_FK_PICT_SEQ,
SupplierSeq = g_loj == null ? -1 : g_loj.GREL_SEQUENCE,
}
}
};
userinfo.mainofferlist = offerlist.ToList();
As Daniel Kelly also mentioned the ToList function is where your query is executed, because these LinqToEntities queries are executed at the point where they are first enumerated, and ToList does that to be able to create a list.
Basically the reason why your querying takes so much time can be separated into two different reasons:
you are using too much projections and I thine (the parts with new {
})
your query has an incredible amount of join clauses
I would recommend to separate your query into subqueries, and run them separately like the first part in
...
select o
use
...
select o).ToList()
by breaking down the main query where you have a lot of subqueries it will be faster and much more readable, so you have less "messiness".
And last but not least you should create mapping for the anonymous objects, and use those classes other than projection that should speed up your query.

Declaring anonymous type as array correctly to keep scope

All I want to do is declare var place correctly so it is still in scope once I get to the foreach loop. I'm assuming I need to declare it before the if statement for connections. Is this a correct assumption and if so how do I declare it? Thanks!
using (var db = new DataClasses1DataContext())
{
if (connections == "Connections")
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && v.WD_Connect >= 1
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
else
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
foreach (var result in place)
....
You can create an array with a single entry whose value you ignore later:
// Note: names *and types* must match the ones you use later on.
var place = new[] { new { locName = "", latitude = 0.0, longitude = 0.0 } };
if (connections = "Connections")
{
// Note: not a variable declaration
place = ...;
}
else
{
place = ...;
}
This works because every use of anonymous types using properties with the same names and types, in the same order, will use the same concrete type.
I think it would be better to make the code only differ in the parts that it needs to though:
var query = v.db.pdx_aparts.Where(v => v.Latitude != null && v.Region == region);
query = connections == "Connections"
? query.Where(v => v.WD_Connect >= 1)
: query.Where(v => v.WD_Connect == null || v.WD_Connect == 0);
var places = query.Select(v => new
{
locName = v.Apartment_complex
.Trim()
.Replace("\"", ""),
latitude = v.Latitude,
longitude = v.Longitude
})
.Distinct()
.ToArray();
Here it's much easier to tell that the only part which depends on the connections value is the section of the query which deals with WD_Connect.
You could convert the if to a ?:.
var place = connections == "Connections" ? monsterQuery1 : monsterQuery2;
I do not think this is a good solution because your queries are too big (unreadable).
It would be much better if you introduced a named class that you use instead of the anonymous type. R# does that for you in a "light bulb menu" refactoring.
you could just use the 1 query since they are pretty much the same, and just add the extra condition in the where clause
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region
&& connections == "Connections"
? v.WD_Connect >= 1
: ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
foreach (var result in place)
....

Categories

Resources