LINQ - SQL Script Changing based on JOIN Order - c#

This is just strange behavior to me.
LINQ transforming my statements creating bizarre execution plans. It's adding sub-queries when I don't ask it to and it's including or excluding joins in this sub-query based on the order of my joins, which leaves me scratching my head. At this point it's impossible for me to optimize this script based on the unpredictable nature of LINQ to Entities.
SETUP
//define global filter
Expression<Func<Contract_SeqExecution, bool>> globalFilter = r => r.ModifiedByFirstName.Contains("w");
...
//extend global filter
Expression<Func<Contract_SeqExecution, bool>> descendantFilter = x => x.client_id == 1 && x.project_status == true && x.parentId != null;
descendantFilter = descendantFilter.And(globalFilter);
//query
IQueryable<Contract_SeqExecution> geDescendantResults = c.queryGroups(this);
//execute
geDescendantResults.AsExpandable().Where(descendantFilter).Dump();
LINQ QUERY
public IQueryable<Contract_SeqExecution> queryGroups(UserQuery context)
{
var result = (from ge in context.group_execution
join aseq in context.automation_sequences on ge.automation_sequence_id equals aseq.id
join modify_u in context.users on aseq.last_modified_by_id equals modify_u.id into modify_uSub
from modify_u in modify_uSub.DefaultIfEmpty()
join p in context.project on aseq.project_id equals p.id
join asstatus in context.automation_sequence_status on ge.run_status_id equals asstatus.id
join es in context.execution_schedule on ge.schedule_id equals es.id
join exe_u in context.users on ge.executed_by_id equals exe_u.id
join create_u in context.users on aseq.created_by_id equals create_u.id
select new Contract_SeqExecution
{
client_id = p.client_id,
project_status = p.status,
ID = ge.id,
Name = aseq.name,
ModifiedByFirstName = modify_u.first_name,
ModifiedByLastName = modify_u.last_name,
FailedInd = asstatus.fail_alert_ind,
parentId = ge.parent_group_exec_id,
patriarchId = ge.patriarch_id
});
return result;
}
If I reorder the joins in the above statement my execution plan changes and LINQ-to-entities decides I want a sub-query....sigh.
Seriously all I did was change the order of the joins in the images below. Drastically different execution plans which will later on hurt performance of the query. What gives? Am I missing something obvious?
Is it picking information up from the entity framework schema? Could it be missing FK definitions or indexes that are driving this crazy behavior? At this point I'm just shooting in the dark. Hopefully someone here can shed some light on this.
Below is the class Contract_SeqExecution. I'm currently running this in a LinqPad C# Program hitting my DAL dll, linq-to-entities.
public class Contract_SeqExecution
{
public int ID { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public int SeqID { get; set; }
public int CaseGroupInd { get; set; }
public string CaseGroupText { get; set; }
public string ExecRatio { get; set; }
public string ModifiedByFirstName { get; set; }
public string ModifiedByLastName { get; set; }
public string Project { get; set; }
public string Uploaded { get; set; }
public string UploadRatio { get; set; }
public bool FailedInd { get; set; }
public bool HoldInd { get; set; }
public int? parentId { get; set; }
public int? patriarchId { get; set; }
public DateTime? SchedRunTime { get; set; }
//other fields
public string Machine {get;set;}
public int is_accepting_changes {get;set;}
public string holding_at_name {get;set;}
public string RunStatus {get;set;}
public string CaseGroupStatus {get; set;}
public string TCStatusColor {get; set;}
public string ExecutedBy {get;set;}
public string CreatedBy {get;set;}
public int? ScheduleID {get;set;}
public bool SeqUploaded {get;set;}
//end other fields
public int? parent_group_exec_id { get; set; }
public int client_id { get; set; }
public bool project_status { get; set; }
public IQueryable<Contract_SeqExecution> queryGroups(UserQuery context)
{
var result = (from ge in context.group_execution
join asstatus in context.automation_sequence_status on ge.run_status_id equals asstatus.id
join es in context.execution_schedule on ge.schedule_id equals es.id
join exe_u in context.users on ge.executed_by_id equals exe_u.id
join aseq in context.automation_sequences on ge.automation_sequence_id equals aseq.id
join p in context.project on aseq.project_id equals p.id
join create_u in context.users on aseq.created_by_id equals create_u.id
join modify_u in context.users on aseq.last_modified_by_id equals modify_u.id into modify_uSub
from modify_u in modify_uSub.DefaultIfEmpty()
select new Contract_SeqExecution
{
client_id = p.client_id,
project_status = p.status,
ID = ge.id,
Name = aseq.name,
ModifiedByFirstName = modify_u.first_name,
ModifiedByLastName = modify_u.last_name,
FailedInd = asstatus.fail_alert_ind,
parentId = ge.parent_group_exec_id,
patriarchId = ge.patriarch_id,
});
return result;
}
}
UPDATE
So I took Andrew's advice and looked into doing this the more "LINQ" way. I like to include FKs in my entities because I feel it's a more natural way to write SQL, habits die hard. I went ahead and rewrote my LINQ expression and the results are now consistent.
Example:
var result = (from ge in context.group_execution
//join asstatus in context.automation_sequence_status on ge.run_status_id equals asstatus.id
//join es in context.execution_schedule on ge.schedule_id equals es.id
//join exe_u in context.users on ge.executed_by_id equals exe_u.id
//join aseq in context.automation_sequences on ge.automation_sequence_id equals aseq.id
//join p in context.project on aseq.project_id equals p.id
//join create_u in context.users on aseq.created_by_id equals create_u.id
//join modify_u in context.users on aseq.last_modified_by_id equals modify_u.id into modify_uSub
//from modify_u in modify_uSub.DefaultIfEmpty()
select new Contract_SeqExecution
{
//client_id = p.client_id,
//project_status = p.status,
ID = ge.id,
Name = ge.automation_sequences.name,
ModifiedByFirstName = ge.automation_sequences.modified_by_user.first_name,
ModifiedByLastName = ge.automation_sequences.modified_by_user.last_name,
//FailedInd = asstatus.fail_alert_ind,
parentId = ge.parent_group_exec_id,
patriarchId = ge.patriarch_id,
});
return result;

Related

SQL-statement with JOIN to LINQ-statement

How would you write the following SQL as a Linq-statement?
SELECT *
FROM Projekt i
LEFT JOIN Projekt j on i.name=j.id
If you have for example these two classes:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
}
public class Address
{
public int ID { get; set; }
public string AddressLine { get; set; }
}
then with Method Syntax(MS) for LEFT OUTER JOIN we'll have this code below:
var JoinedTables = context.Employee.GroupJoin(Address,
emp => emp.AddressId,
add => add.ID,
(emp, add) => new { emp, add })
.SelectMany(x => x.add.DefaultIfEmpty(),
(employee, address) => new { employee, address }))
.ToList();
But with Query Syntax(QS)
which is more understandable for humans
for LEFT OUTER JOIN again all you have to do is:
var JoinedTables = (from emp in Employee
join add in Address
on emp.AddressId equals add.ID
into EmployeeAddressGroup
from address in EmployeeAddressGroup.DefaultIfEmpty()
select new { employee, address }).ToList();

Sql query combining results

I have the following:
void Main()
{
// I need to get the below query, but with the newest comment date as well of each post.
// Gives me all the posts by the member
var member_id = 1139;
var query = (from posts in Ctm_Forum_Posts
join category in Ctm_Forum_Categories on posts.FK_Categori_ID equals category.Id
where posts.Archieved == false
&& posts.Deleted == false
&& posts.FK_Member_ID == member_id
select new ForumPostModel(){
Id = posts.Id,
BodyText = posts.BodyText,
Summary = posts.Summary,
Title = posts.Title,
Created = posts.Created,
Updated = posts.Updated,
CategoryName = category.Title,
});
// this gives the newest comment date (registration_timestamp) from a specific post with id = 1
var NewestCommentDate = (from comments in Ctm_Comments
join posts in Ctm_Forum_Posts on comments.Page_ID equals posts.Id
where posts.Id == 1 && comments.Deleted == false
orderby comments.Reqistration_timestamp descending
select comments.Reqistration_timestamp).FirstOrDefault();
}
// Model of the result.
public class ForumPostModel{
public int Id { get; set; }
public string Title { get; set; }
public string BodyText { get; set; }
public string Summary { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public string CategoryName { get; set; }
public DateTime LatestCommentTime { get; set; }
}
Right now the two queries work, but my problem is the performance sucks,, so im trying to combine the results into a single query, so my load time will be much less.
I tried searching about unions, but have been unable to figure it out.

Hierarchical select in Entity Framework with lambda expression

I have a relational dataset as dummy. I want to take the data as hierarchical (Role > SubRoles > Permissions) then I will convert to JSON but I get an exception:
Error CS0266
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Collections.Generic.List'. An explicit conversion exists (are you missing a cast?)
Thanks for your answer.
Model classes:
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public List<SubRole> SubRoles { get; set; }
}
public class SubRole
{
public int Id { get; set; }
public string Name { get; set; }
public string EndPoint { get; set; }
public List<Permission> Permissions { get; set; }
}
public class RoleSubRole
{
public int Id { get; set; }
public int RoleId { get; set; }
public int SubRoleId { get; set; }
}
public class Permission
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SubRolePermission
{
public int Id { get; set; }
public int SubRoleId { get; set; }
public int PermiisonId { get; set; }
}
public class RoleModel
{
public Role Role { get; set; }
}
Program class:
public static void Main(string[] args)
{
RoleModel roleModel = new RoleModel()
{
Role =
(from u in DataSet.Users
join r in DataSet.Roles on u.RoleId equals r.Id
where u.Id == 1
select new Role
{
Name = r.Name,
SubRoles =
(from rsb in DataSet.RoleSubRoles
join sr in DataSet.SubRoles on rsb.SubRoleId equals sr.Id
where r.Id == rsb.RoleId
select new SubRole
{
Name = sr.Name,
EndPoint = sr.EndPoint,
Permissions =
(from srp in DataSet.SubRolePermissions
join p in DataSet.Permissions on srp.PermiisonId equals p.Id
where srp.SubRoleId == sr.Id
select new Permission
{
Name = p.Name
})
})
})
};
}
You should use
Permissions =
(from srp in DataSet.SubRolePermissions
join p in DataSet.Permissions on srp.PermiisonId equals p.Id
where srp.SubRoleId == sr.Id
select new Permission
{
Name = p.Name
}).ToList()
The Permissions is a List<T>, while the query returns an IEnumerable<T>. Same is the case with SubRoles.You need to convert to List<T>, which could be done using the ToList() method
Complete Query
Role =
(from u in DataSet.Users
join r in DataSet.Roles on u.RoleId equals r.Id
where u.Id == 1
select new Role
{
Name = r.Name,
SubRoles =
(from rsb in DataSet.RoleSubRoles
join sr in DataSet.SubRoles on rsb.SubRoleId equals sr.Id
where r.Id == rsb.RoleId
select new SubRole
{
Name = sr.Name,
EndPoint = sr.EndPoint,
Permissions =
(from srp in DataSet.SubRolePermissions
join p in DataSet.Permissions on srp.PermiisonId equals p.Id
where srp.SubRoleId == sr.Id
select new Permission
{
Name = p.Name
}).ToList()
}).ToList()
}).First()
};
Also note that the Role represents a single entity, while the query returns a collection. You need to make a choice on which entity from the collection needs to be stored. For the sample code above, I have used First()

Could not translate expression [duplicate]

This question already has answers here:
How to use ToShortDateString in linq lambda expression?
(4 answers)
Closed 6 years ago.
I am getting the following error: "Could not translate expression..." when I try to return a list of enquiry entities, converted to a custom class.
I am very new to using link in this way, in the past I would use stored procedure in SQL and just import them as methods but I am trying to convert these.
My method that returns the list is:
public static List<EnquiryData> GetAllEnquiries()
{
var GridData = from a in Global.AcepakSalesPortal.Enquiries
join Cust in Global.AcepakSalesPortal.Customers
on a.CustomerID equals Cust.CustomerID into CustGroup
from b in CustGroup.DefaultIfEmpty()
join Pros in Global.AcepakSalesPortal.Prospects
on a.ProspectID equals Pros.ProspectID into ProsGroup
from c in ProsGroup.DefaultIfEmpty()
join Users in Global.AcepakSalesPortal.Users
on a.ResponsiblePartyID equals Users.UserID into UserGroup
from d in UserGroup.DefaultIfEmpty()
join Qt in Global.AcepakSalesPortal.Quotes
on a.QuoteID equals Qt.QuoteID into QuoteGroup
from e in QuoteGroup.DefaultIfEmpty()
join Usr in Global.AcepakSalesPortal.Users
on e.CreatedBy equals Usr.UserID into UsrGroup
from f in UsrGroup.DefaultIfEmpty()
join EnqCat in Global.AcepakSalesPortal.EnquiryCategories
on a.EnquiryCategoriesID equals EnqCat.EnquiryCatID into CatGroup
from g in CatGroup.DefaultIfEmpty()
join Clsd in Global.AcepakSalesPortal.Users
on a.ClosedBy equals Clsd.UserID into ClsdGroup
from h in ClsdGroup.DefaultIfEmpty()
orderby a.Created descending
select new EnquiryData
{
EnquiryID = a.EnquiryID,
ResponsiblePartyID = a.ResponsiblePartyID,
EnquiryNo = "ENQ" + a.EnquiryID.ToString().PadLeft(7, '0'),
EType = a.CustomerID.HasValue ? "C" : "P",
EnqCat = g.Code + " - " + g.Category,
ContactPerson = a.ProspectID.HasValue ? c.ContactPerson : "NOT INTEGRATED YET",
ContactNumber = a.ProspectID.HasValue ? c.ContactNum : "NOT INTEGRATED YET",
ContactEmail = a.ProspectID.HasValue ? c.ContactEmail : "NOT INTEGRATED YET",
Company = a.CustomerID.HasValue ? b.Name : c.CompanyName,
Description = a.Description,
AssignedTo = d.Name,
AddressBy = a.AddressBy,
EnquiryDate = a.Created,
EStatus = a.Closed.HasValue ? "Closed" : a.QuoteID.HasValue ? "Quoted" : "Open",
QuotedOn = a.QuoteID.HasValue ? e.Created.ToShortDateString() : "N/A",
QuotedBy = a.QuoteID.HasValue ? f.Name : "N/A",
QuoteNum = a.QuoteID.HasValue ? e.QuoteID.ToString().PadLeft(7, '0') : "N/A",
ClosedOn = a.Closed.HasValue ? a.Closed.Value.ToShortDateString() : "N/A",
ClosedBy = a.Closed.HasValue ? h.Name : "N/A",
Reason = a.Closed.HasValue ? a.ClosedReason : "N/A"
};
return GridData.ToList();
}
And the custom class is:
public class EnquiryData
{
public int EnquiryID { get; set; }
public int ResponsiblePartyID { get; set; }
public string EnquiryNo { get; set; }
public string EType { get; set; }
public string EnqCat { get; set; }
public string ContactPerson { get; set; }
public string ContactNumber { get; set; }
public string ContactEmail { get; set; }
public string Company { get; set; }
public string Description { get; set; }
public string AssignedTo { get; set; }
public DateTime AddressBy { get; set; }
public DateTime EnquiryDate { get; set; }
public string EStatus { get; set; }
public string QuotedOn { get; set; }
public string QuotedBy { get; set; }
public string QuoteNum { get; set; }
public string ClosedOn { get; set; }
public string ClosedBy { get; set; }
public string Reason { get; set; }
}
My question is 2 fold
1. Is there a better ways to join tables together in Linq than I am doing above?
2. What could be causing the error, I dont mind figuring it out but not sure how to even approach this.
EDIT: This is most definitely not a duplicate of the mentioned question. The only similarity between the 2 is the use of shortdatestring, however the error message I receive is completely different to that of the other question.
You have used lot of c# methods which are not known by the SQL.Hence you can retrieve all the columns as shown below and then do your custom mapping on the memory as you wish.
var GridData = (from a in Global.AcepakSalesPortal.Enquiries
join Cust in Global.AcepakSalesPortal.Customers
on a.CustomerID equals Cust.CustomerID into CustGroup
from b in CustGroup.DefaultIfEmpty()
join Pros in Global.AcepakSalesPortal.Prospects
on a.ProspectID equals Pros.ProspectID into ProsGroup
from c in ProsGroup.DefaultIfEmpty()
join Users in Global.AcepakSalesPortal.Users
on a.ResponsiblePartyID equals Users.UserID into UserGroup
from d in UserGroup.DefaultIfEmpty()
join Qt in Global.AcepakSalesPortal.Quotes
on a.QuoteID equals Qt.QuoteID into QuoteGroup
from e in QuoteGroup.DefaultIfEmpty()
join Usr in Global.AcepakSalesPortal.Users
on e.CreatedBy equals Usr.UserID into UsrGroup
from f in UsrGroup.DefaultIfEmpty()
join EnqCat in Global.AcepakSalesPortal.EnquiryCategories
on a.EnquiryCategoriesID equals EnqCat.EnquiryCatID into CatGroup
from g in CatGroup.DefaultIfEmpty()
join Clsd in Global.AcepakSalesPortal.Users
on a.ClosedBy equals Clsd.UserID into ClsdGroup
from h in ClsdGroup.DefaultIfEmpty()
orderby a.Created descending
select a).ToList()
After that do your custom class mapping here :
var list= GridData.Select(a=>new EnquiryData{EnquiryID = a.EnquiryID,.... })
1.When these query executes, linq complier needs to transform this linq queries to SQL query.Most of the methods implementing IQuerable inferface, can be convertable. but C# methods like ToString() , ToShortDateString() could not be able to convert by Linq complier. so you get 'Could not translate expression..'.
You could try:
var GridData = from a in Global.AcepakSalesPortal.Enquiries
join Cust in Global.AcepakSalesPortal.Customers on a.CustomerID equals Cust.CustomerID
join Pros in Global.AcepakSalesPortal.Prospects on a.ProspectID equals Pros.ProspectID
join Users in Global.AcepakSalesPortal.Users on a.ResponsiblePartyID equals Users.UserID
etc..

Linq query with first or default join

I have the following data model:
public class Course
{
public int CourseId { get; set; }
public int StateId { get; set; }
}
public class CompletedCourse
{
public int CompletedCourseId { get; set; }
public int UserId { get; set; }
public Course Course { get; set; }
public string LicenseNumber { get; set; }
}
public class License
{
public int LicenseId { get; set; }
public int UserId { get; set; }
public int StateId { get; set; }
public string LicenseNumber { get; set; }
}
I'm trying to come up with an IQueryable for CompletedCourses and I would like to populate CompletedCourse.LicenseNumber with the LicenseNumber property of the FirstOrDefault() selection from my Licenses table where UserId and StateId match the completed course records.
Here is my query, but I don't think this will handle duplicate licenses correctly:
var entries =
(from course in context.CompletedCourses
join license in context.Licenses on course.UserId equals license.UserId
where license.StateId == course.Course.StateId
select course)
.Include(x => x.Agent)
.Include(x => x.Course.State);
Is this something that can be done in a single query? Thanks in advance.
Here is how you can do that:
var entries =
(from course in context.CompletedCourses
join license in context.Licenses
on new { course.UserId, course.Course.StateId }
equals new { license.UserId, license.StateId }
into licenses
let licenseNumber = licenses.Select(license => license.LicenseNumber).FirstOrDefault()
select new { course, licenseNumber });
But please note that with this type of projection you cannot have Includes in your query (you can, but they will not be in effect).
The EF generated query I'm getting from the above is:
SELECT
[Extent1].[CompletedCourseId] AS [CompletedCourseId],
[Extent1].[UserId] AS [UserId],
[Extent1].[LicenseNumber] AS [LicenseNumber],
[Extent1].[Course_CourseId] AS [Course_CourseId],
(SELECT TOP (1)
[Extent2].[LicenseNumber] AS [LicenseNumber]
FROM [dbo].[Licenses] AS [Extent2]
INNER JOIN [dbo].[Courses] AS [Extent3] ON [Extent3].[StateId] = [Extent2].[StateId]
WHERE ([Extent1].[Course_CourseId] = [Extent3].[CourseId]) AND ([Extent1].[UserId] = [Extent2].[UserId])) AS [C1]
FROM [dbo].[CompletedCourses] AS [Extent1]
It can be noticed that EF effectively ignores the join, so the same result can be obtained by simple natural query:
var entries =
(from course in db.CompletedCourses
let licenseNumber =
(from license in db.Licenses
where license.UserId == course.UserId && license.StateId == course.Course.StateId
select license.LicenseNumber).FirstOrDefault()
select new { course, licenseNumber });
#IvanStoev's answer was very helpful in joining on anonymous types, but ultimately I couldn't use it because I needed Includes. Here is the solution I went with that results in two DB queries instead of one which is fine for my situation.
var entries = context.CompletedCourses
.Include(x => x.Agent)
.Include(x => x.Course);
var courses = entries.ToList();
var courseIds = entries.Select(x => x.CompletedCourseId);
var licenses =
(from course in entries
join license in context.Licenses
on new { course.AgentId, course.Course.StateId }
equals new { AgentId = license.UserId, license.StateId }
where courseIds.Contains(course.CompletedCourseId)
select license);
foreach (var course in courses)
{
var license = agentLicenses.FirstOrDefault(x => x.UserId == course.AgentId &&
x.StateId == course.Course.StateId);
if (license != null)
{
course.LicenseNumber = license.LicenseNumber;
}
}
return courses;

Categories

Resources