I need to join all data according to GroupIDs, which these GroupIDs are owned by EmpNo
public IEnumerable<EmployeeWithEmail> GetAllEmployeesWithEmail(int EmpNo)
{
using (var context = new SQL_TA_SCOREBOARDEntities1())
{
return (from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
//need code to join all data according to EmpNo's GroupIDs
group new
{
ea.EmpNo,
subjoin.Role,
vh.EmailAddress,
vh.LNameByFName,
ea.Active
} by vh.LNameByFName into grp
let item = grp.FirstOrDefault()
orderby item.Role ascending
select new EmployeeWithEmail
{
EmpNum = item.EmpNo ?? 0,
Role = item.Role,
EmailAddress = item.EmailAddress,
LNameByFname = item.LNameByFName,
Active2 = item.Active ?? false
}).ToList();
}
}
I guess I'm trying to filter twice and join common data, but there are actually two filters, which I don't know how to control.
So my output would be like:
EmpNo ---> __ 01 | 01 | 01 | 01
GroupID ---> __10 | 10 | 20 | 20
Data ---> _________Apple | Apple | Orange | Orange
I can filter EmpNo 01 and GroupID 10 but What if the EmpNo belongs to two groups?
Sorry for not finding the right terminologies.
Thanks in advance.
Based on your comment, the SQL you are trying to generate should be (I've simplified slightly)
SELECT EmployeeAccess.EmpNo, View_SystemAdminMembers.LNameByFName, View_SystemAdminMembers.GroupName,
View_SystemAdminMembers.Role, View_SystemAdminMembers.Active, View_SystemAdminMembers.EmpNo,
View_SystemAdminMembers.RoleID
FROM EmployeeAccess
INNER JOIN View_SystemAdminMembers ON EmployeeAccess.GroupID = View_SystemAdminMembers.GroupID
WHERE (EmployeeAccess.EmpNo = '01')
This is pretty different from what you show in your question:
from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
so I'm not sure if my answer is going to help or not, but to get the SQL you specify, I think you would want to do:
var query =
from ea in context.EmployeeAccess
join vsam in context.View_SystemAdminMembers on ea.GroupID equals vsam.GroupID
where ea.EmpNo == "01"
select new
{
ea.EmpNo, vsam.LNameByFName, vsam.GroupName, vsam.Role, vsam.Active, vsam.EmpNo, vsam.RoleID
};
Using fluent syntax (not query syntax) it would look a bit like:
var query =
context.EmployeeAccess
.Join(context.View_SystemAdminMembers, allEA => allEA.GroupID, allVSAM => allVSAM.GroupID, (ea, vsam) => new {ea, vsam})
.Where(combined => combined.ea.EmpNo == "01")
.Select(combined => combined.ea.EmpNo, combined.vsam.LNameByFName, combined.vsam.GroupName, combined.vsam.Role, combined.vsam.Active, combined.vsam.EmpNo, combined.vsam.RoleID);
(although I admit--I normally return the whole entity like
.Select(combined => combined.ea) or something like that so I'm not 100% certain on that last line...)
Note that in both cases, "var query" is going to be an IQueryable which means you will still need to add a ToList or equivalent to get your results. Before you do that, though, you'll want to apply Tim Burkhart's answer to make any modifications you want on it (like GroupBy or whatever). As he noted, one of the cool things about IQueryable is that you don't have to do it all in one statement; you can take query just as I have defined it above and then add something like
query = query.Where(c => c.LNameByFName.Contains("A"))
or whatever.
One more note--your return value is entirely made up of items from View_SystemAdminMembers with the exception of EmployeeAccess.EmpNo, but since you are filtering on that, you should already know what it is. It may be easier to return just a View_SystemAdminMember object, rather than creating a new type. That's up to you.
I'm not really following what you're asking for. Perhaps it can be rephrased or given more context or formatted differently?
But I thought I would make some suggestions that might help you get to where you are wanting to go.
1 - The function name implies that the person will provide an email and receive multiple Employee objects with that email. But it appears to accept an employee id and return a list of EmployeeWithEmail. Consider renaming the function to match what it is doing. Also consider returning IEnumerable<Employee> or IEnumerable<IEmployeeEmailView>
2 - This function is doing a lot. (And it takes more than a few seconds to figure out what it is doing). In cases like this, I would start off simple. Don't do the grouping or sorting or anything. Have some function return the results of this:
from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
in the form of something like IEnumerable<Employee>
public class Employee {
public int Id { get; set; }
public string Role { get; set; }
public string EmailAddress { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
From there, things are easier to deal with. You're not having to work with LINQ-to-SQL. You can just do...
IEnumerable<Employee> employees = <your renamed function>();
var employeesGroupedByName = employees.GroupBy(e => e.Name);
Hopefully that makes things easier for you. It doesn't solve your answer, but I think it might make things less complicated/easier to work with.
So written out fully, it could be something like:
public class EmployeeRepository {
public IEnumerable<Employee> GetAll() {
// This function won't compile. I don't know the syntax for this type of LINQ
using (var context = new SQL_TA_SCOREBOARDEntities1()) {
return (from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
}
}
public IEnumerable<Employee> GetAllEmployeesWithEmployeeId(int employeeId) {
return GetAll().Where(e => e.Id == employeeId).ToList();
}
public IEnumerable<Employee> SomeOtherFunctionThatDoesWhatYouWantToDoFromThisPost() {
// You could also create a class that extends IEnumerable<Employee> to
// encapsulate the logic so that the repository doesn't have a million functions
}
}
That's all I've got for you. I could give a more complete answer if the problem was spelled out a little bit better, but hopefully this gets you on the right track.
Related
I'm trying to create a linq query that can be able to search through an array of objects. This is what I can do on the SQL
select * from Compliance c
join ComplianceDetail cd on cd.ComplianceID = c.ComplianceID
join ComplianceDetailAnswer cda on cd.ComplianceDetailID = cda.ComplianceDetailID
where cda.ComplianceQuestionValue like '%test%'
As you can see, the heirarchy is one Compliance to many ComplianceDetail and one ComplianceDetail to many ComplianceDetailAnswer. I want to search on the ComplianceDetailAnswer table but I have no idea how to do it in linq.
Here's my original code but I'm searching on the Compliance table itself
compliances = await _contextProvider.Context.Compliances
.Where(c.IncidentReportID.ToString().Contains(searchText) || searchText == null)
.ToListAsync();
This is just searching on the Compliance table, but I want to be able to search on its child table ComplianceDetailAnswer
You can compose it in LINQ easily. Here is an example using query syntax:
var result = from c in Compliance
join cd in ComplianceDetail on c.ComplianceID equals cd.ComplianceID
join cda in ComplianceDetailAnswer on cd.ComplianceDetailID equals cda.ComplianceDetailID
where cda.ComplianceQuestionValue.Contains("test")
select new { c, cd, cda };
If you need to call Skip and Take simply wrap your expression to parentheses and the you can chain other LINQ methods
var result = (from c in Compliance
join cd in ComplianceDetail on c.ComplianceID equals cd.ComplianceID
join cda in ComplianceDetailAnswer on cd.ComplianceDetailID equals cda.ComplianceDetailID
where cda.ComplianceQuestionValue.Contains("test")
select new { c, cd, cda }).Skip(1).Take(100);
Here's another answer using System.Linq. Each IEnumerable<T> supports Take() and Skip().
As long as you do not iterate the result - in my case compliancesWithQuestionsLikeTest the query will not be executed, since it's just an IQueryable object.
Adding Take() and Skip() should therefore generate an SQL statement that has something like TAKE FIRST 50 ROWS ONLY and thus give you a performance benefit.
class Compliance
{
public List<ComplianceDetail> ComplianceDetails { get; set; }
}
class ComplianceDetail
{
public List<ComplianceDetailAnswer> ComplianceDetailAnswers { get; set; }
}
class ComplianceDetailAnswer
{
public string Question { get; set; }
}
static void Main(string[] args)
{
var obj = new Compliance();
var queryList = new List<Compliance> { obj };
var compliancesWithQuestionsLikeTest = queryList.Where(compliance => compliance.ComplianceDetails
.Any(complianceDetail => complianceDetail.ComplianceDetailAnswers
.Any(answer => answer.Question.Contains("test"))));
}
In this sample queryList basically is your _contextProvider.Context.Compliances.
EDIT:
Please note that this query will return the Compliance objects in an unmodified manner. You will get only Compliances containing a question with test. The Compliance objects however also contain all other question related to them in the database. The objects are not manipulated!
As you can see in Tomas Chabada's answer he creates new objects only containing questions with "test" in select new { c, cd, cda }.
I have a table which contains columns among others like Id as Primary Key and LastMeterReadingId (Foreign Key which references to same table) - something like Parent Meter Reading.
I would like to get all rows which are not already used like Parent. I would like to avoid situation when meter reading is parent for more than one meter reading.
I know how to join to same table, but I have no idea how to choose only those records which aren't parent already. That's how query looks like without condition statement.
return (from m in uow.MeterReadingReadWriteRepository.Query()
join parent in uow.MeterReadingReadWriteRepository.Query() on m.Id equals parent.LastMeterReadingId
select new MeterReadingDto()
{
(...)
}).ToList();
Do you have any idea how to achieve it in efficient way?
Regards.
I would like to get all rows which are not already used like Parent
In other words, you want all rows that have no children. Note that the variable name parent in your query is misleading - when you do a join b on a.Id equals b.ParentId, a is the parent and b is the child.
Anyway, there are at least 3 ways to achieve your goal, IMO being equivalent from nowadays database query optimizers point of view (i.e. should be equally efficient):
(1) Using !Any(...) which is equivalent to SQL NOT EXISTS(...):
from m in uow.MeterReadingReadWriteRepository.Query()
where !uow.MeterReadingReadWriteRepository.Query().Any(child => m.Id == child.LastMeterReadingId)
select ...
(2) Using group join:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
where !children.Any()
select ...
(3) Using left outer antijoin:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
from child in children.DefaultIfEmpty()
where child == null
select ...
If this is EF (LINQ to Entities), the first two are translated to one and the same SQL NOT EXISTS based query. While the last is translated to the "traditional" SQL LEFT JOIN ... WHERE right.PK IS NULL based query.
You could just add
where !(from child in uow.MeterReadingReadWriteRepository.Query() where child.Id == m.LastMeterReadingId select child).Any()
Not sure how intelligently this would be optimised though. It would also be better to factor out uow.MeterReadingReadWriteRepository.Query().
Do you not have a Child relationship/collection in your Meter Reading entity from the foreign key constraint? - this would make the query much more straightforward.
var readings = uow.MeterReadingReadWriteRepository.Query();
var parents = readings
.Join(readings, child => child.Id, parent => parent.LastMeterReadingId,
(child, parent) => new {parent.Id})
.Distinct()
.ToDictionary(a => a.Id);
var result = (from m in readings
where !parents.Contains(m.Id)
select new
{
Id = m.Id
}).ToList();
Thanks #Ben Jackson
public class MeterReading : EntityBase
{
public long PropertyId { get; set; }
public long? LastMeterReadingId { get; set; }
public long? PaymentId { get; set; }
public Property Property { get; set; }
public MeterReading LastReading { get; set; }
public Payment Payment { get; set; }
}
That's how most value properties looks like. Maybe should I use T-SQL query with JOIN to CTE which mentioned before condition statement? I'll try your solution ASAP.
I have two List objects. I want a count of how many items between the two list match. Now I could loop through with a counter as I come across matches....but that would be kinda lame.
However this one is stretching my LINQ knowledge. I believe what I want to do is Join, discern (where), group, and project the count. I came to this approach by reading similar SO questions and LINQ documentation.
However if this is flawed by all means it doesn't have to be this way. I just want the count of matching elements.
So my "master" object is:
public class Master
{
public StringModel OldText { get; private set; }
public StringModel NewText { get; private set; }
public Master()
{
OldText = new StringModel();
NewText = new StringModel();
}
}
StringModel is:
public class StringModel
{
public List<strPiece> Lines { get; private set; }
public StringModel()
{
Lines = new List<strPiece>();
}
}
My LINQ thus far is:
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
where oldLine.Type == "ABC"
group oldLine by --as you can see I kinda break down here.
Any help finishing would be appreciated. Also if I need to post more code just let me know.
Thank You
Sounds like a good use for intersect
var infoQuery =
(from cust in db.Customers
select cust.Country)
.Intersect
(from emp in db.Employees
select emp.Country)
;
Just perform a GroupJoin rather than a Join.
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
into newMatches
where oldLine.Type == "ABC"
select oldLine;
var matches = unchangedJoin.Count();
I too would recommend Intersect. If you go that way, using the fluent syntax makes more sense.
int matches = Master.OldText.Lines
.Where(line => line.Type == "ABC")
.Intersect(Master.NewText.Lines)
.Count();
My problem solving like this such a code;
string permalink = "computers/hp/computers"; //example for similarity
List<string> aliases = permalink.Split('/').ToList();
Category cat = db.Categories.SingleOrDefault(c => c.Alias == aliases.First());
aliases.Remove(aliases.First());
foreach (string alias in aliases)
{
cat = cat.Categories.SingleOrDefault(c => c.Alias == alias);
}
return cat;
But this is sent many query..
How do I make one time?
If I understand what you want, you can use the Enumerable.Aggregate method. You will have to start with a 'root' category, that encompasses all of db.Categories. That's pretty easy to mock up though. Try this:
var aliases = permalink.Split('/');
var category = new Category // start with a 'root' category
{
Id = 0,
Categories = db.Categories
};
var cat = aliases.Aggregate(category, (c, a) => c.Categories.SingleOrDefault(x => x.Alias == a));
Firstly, if the category table is small it is sometimes better to just grab the whole table and do the selection in memory (perhaps using p.w.s.g's answer).
If the table is large, then a Stored procedure would probably be better than Linq.
But, if you really want to do it in Linq, then I think the only way is to repeatedly add a join to same table.
The following is assuming that your relationship is between fields called ParentID and Id. I have also changed your string permalink to better illustrate the order.
You first need a little helper class
public class Info
{
public Category category;
public int? recordID;
}
then your main code
string permalink ="computers1/hp/computers2";
var aliases = permalink.Split('/');
var query = dc.Categories.Where(r=>r.Alias == aliases[aliases.Length-1])
.Select(r=> new Info { category = r, recordID = r.ParentID});
for(int i = aliases.Length -2 ; i >= 0; i--)
{
string alias = aliases[i];
query = query.Join(dc.Categories ,
a => a.recordID , b => b.Id , (a,b) => new { a , b} )
.Where(r=>r.b.Alias == alias)
.Select(r=> new Info { category = r.a.category, recordID = r.b.ParentID});
}
return query.SingleOrDefault().category;
As you can see the lambda syntax of join is (IMHO) horrendous and I usually try to avoid it, but I can't think of anyway of avoiding it here.
Since I can't test it, it could be totally wrong (maybe I've mixed up the ID, ParentID or my a's and b's ), so it is important to test this and to test how it performs.
I think the sql produced should be something like
SELECT * from Categories AS t0
INNER JOIN Categories AS t1 ON t0.ParentID = t1.id
INNER JOIN Categories AS t2 ON t1.ParentID = t2.id
WHERE t2.Alias = 'computers1'
AND t1.Alias = 'hp'
AND t0.Alias = 'computers2'
The more sections or aliases, then the more joins there are.
Now that you've see all that, you probably want to avoid using this method -)
I'll probably just add to your confusion :), but let me just throw an idea...
Let me just say it that this doesn't work (exactly per your specs) - and it's not the solution but might help you simplify things a bit.
var query =
(from cat in db.Categories
where cat.Alias == "mainAalias"
from subcat in cat.Categories
where aliases.Contains(subcat.Alias)
orderby subcat.Alias descending
select subcat);
query.FirstOrDefault(); // or something
This should produce one relatively simple query
(e.g. SELECT...FROM...JOIN...WHERE... AND...IN...ORDERBY...).
e.g. if you give it 'cat1', 'cat2'...'cat6' - out of cat1 - to cat100 - it gives 'cat6'...'cat1' (I mean the aliases)
However it has a major 'flaw' with the 'sorting' - your specs require a sort that is the order of 'aliases' as they come - which is a bit unfortunate for queries. If you could somehow enforce, or define an order, that could be translated to SQL this (or similar) might work.
I'm assuming - that your 'aliases' are pre-sorted in an ascending
order - for this query to work. Which they are not, and I'm aware of
that.
But I think that your idea is not clearly defined here (and why all of us are having problems) - think through, and optimize - simplify your requirements - and let your C# tier help e.g. by pre-sorting.
You could also try some form of 'grouping' per cat.Alias etc. - but I think the same 'sorting problem' persists.
Im getting a "The method 'Join' is not supported" error... Funny thing is that i simply converted the 1st LINQ into the 2nd version and it doesnt work...
What i wanted to have was LINQ version #3, but it also doesnt work...
This works
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on s["bh_contract"] equals b["bh_contract"]
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
This doesn't
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"] }
equals new { contractid = b["bh_contract"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
Also, this doesn't, which is a composite join and what i really aim for
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"], serviceid = s["serviceid"] }
equals new { contractid = b["bh_contract"], serviceid = s["serviceid"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
I tried early binding and still doesnt work...
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { foo = s.bh_contract.Id }
equals new { foo = b.bh_Contract.Id }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
stil not working
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { s.bh_contract.Id, s.ServiceId }
equals new { b.bh_Contract.Id, ServiceId = b.bh_Service }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
But im simply trying to do the example(s) here How to do joins in LINQ on multiple fields in single join
What am i missing?
Thanks in advance
While I'm not entirely sure which CRM you're using, I think you're misunderstanding something.
In order for a LINQ query to work, there needs to be a LINQ provider for the underlying data source -- the bit of code responsible for translating chain of e.g. Join, Where, operator usage, etc, etc, into the query API of the data source. This might be SQL, some custom query language, or some chain of methods.
Two LINQ providers (such as, one for LINQ to DataSet and some custom provider you've written yourself) don't have to support the same methods and other code. The precise subset of LINQ methods (and/or other embedded statements) a LINQ provider supports is dependent on its implementation.
Looking at it like that, it's not that surprising that the LINQ provider you're using doesn't seem to comprehend the standard syntax for joins using multiple fields, or doesn't seem to comprehend the usage of anonymous types at all.
My advice is to search the documentation of the supplied LINQ provider to see which query operations it supports (perhaps there is a note about this specific mode of query not being supported). Failing that, you'll have to resort to some sort of other query -- one not involving an equijoin. Perhaps your best option is to perform the joins separately, and then intersect the two result groups. It really depends on the specifics of the case.
Have you looked at the MSDN samples. There are some multiple-column join examples there:
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var list_join = (from a in svcContext.AccountSet
join c in svcContext.ContactSet
on a.PrimaryContactId.Id equals c.ContactId
where a.Name == "Contoso Ltd" && <<--- multiple join here
a.Address1_Name == "Contoso Pharmaceuticals"
select a).ToList();
foreach (var c in list_join)
{
System.Console.WriteLine("Account " + list_join[0].Name
+ " and it's primary contact "
+ list_join[0].PrimaryContactId.Id);
}
}
This other thread might be relevant