Need help to build an EF query with LEFT JOIN - asp.net-mvc

I can't find the correct way to build an EF (4.1) query that will return the same result as this SQL containing a LEFT JOIN:
SELECT
s.id_service,
s.description,
x.id_service as isDisponible
FROM
role.service_disponible s
LEFT JOIN
role.service_disponible_x_ue x
ON s.id_service = x.id_service AND x.id_ue = 1 and flg_actif = '1'
In fact I'm just trying to obtain the complete list of services disponible (ServiceDisponible) adding a field that tell me if service is disponible for a specific entity (filtered with the id_ue) which information come from a many to many related table (ServiceDisponibleXUe).
My model is:
Ideally, I would like this query to return this viewModel object what is basically my serviceDisponible domain with one more field indicating the disponibility of the service.
public ServiceDisponibleViewModel(ServiceDisponible ServiceDisponible, bool isDisponible)
{
this.serviceDisponible = serviceDisponible;
this.isDisponible = isDisponible;
}
What I have so far is this query but the syntax is invalid:
services = context.ServiceDisponible
.Select(a => new ServiceDisponibleViewModel
{
c => new ServiceDisponible
{
id_service = a.id_service,
description = a.description
},
isDisponible = a.ServiceDisponibleXUe
.Any(b => b.flg_actif && b.id_ue == idUe)
}).ToList();

Try this:
ServiceDisponibleViewModel services =
from sd in context.ServiceDisponible
from sdx in context.ServiceDisponibleXUe
.Where(x => x.id_ue == 1 && flg_actif == '1' && x.id_service == sd.id_service)
.DefaultIfEmpty()
select new ServiceDisponibleViewModel(
new ServiceDisponible
{
id_service = sd.id_service,
description = sd.description
},
sdx.id_service
);

Having SQL as example often makes one jump to a join in linq. But using navigation properties produces much more succinct syntax:
from sd in context.ServiceDisponible
from sdx in sd.ServiceDisponibleXUes.Where(x => x.id_ue == 1
&& x.flg_actif == "1")
.DefaultIfEmpty()
select new
{ sd.id_service,
sd.description,
isDisponible = sdx.id_service
};
(I couldn't help using the plural form of ServiceDisponibleXUe which imo is more clear).

Related

Selecting list for drop down ASP.NET MVC

In my database, I have a table called Employee and it has columns EmpNames and EmpId which same EmpId created User table with user levels. I want to get a list of empNames and id's to who are user level equal to the 4.
This is how I got empname list for a drop down list
List<M_Employee> EmpList = db.CreateEmployee.Where(x => x.Status == true).ToList();
List<SelectListItem> EmpDropDown = EmpList.Select(x => new SelectListItem { Text = x.EmpName, Value = x.Id.ToString() }).ToList();
Same way I have tried to query the user level = 4 and tried to join emp table with user table to get the emp names who assigned user levels to 4 but it didn't work.
Here is my code for that
List<int> TopEmp = db.Master_Users.ToList().Where(r => r.EmpId == int.Parse(db.CreateEmployee.Where(x=> x.Id))).ToList().
Can you help me on this?
Firstly, you need to understand how ToList works.
When you call ToList it means that Entity framework will execute the sql statement constructed at that point and retrieve the results into memory.
You generally want to construct your entire query first and then have that query get all the data you want from the database in the format of an object you want by using .Select(x => x.whatever).ToList(). Otherwise you'll be making multiple calls to the database to get bits of data here and there and then joining them or working with them unnecessarily in memory which is slower than having the database do it.
So your first query where you get the select list items can be rewritten like this:
List<SelectListItem> EmpDropDown = db.CreateEmployee
.Where(x => x.Status == true)
.Select(x => new SelectListItem { Text = x.EmpName, Value = x.Id.ToString() })
.ToList()
And from what you've described you should be able to rewrite the 2nd query like this:
List<int> TopEmp = (from u in db.Master_Users
join e in db.CreateEmployee on u.EmpId equals e.Id
where u.Level == 4
select e.Id
).ToList();
This is using a different query syntax but allows to specify the key to join on easily as I don't know how your foreign keys and navigation properties are setup.
I can't see you dbcontext, maybe it is possible to use include too, but for the start try this
List<SelectListItem> EmpDroDown = (from emp in db.CreateEmployee
join usr in db.Master_Users on emp.Id equals usr.EmpId
where emp.Status == true && usr.UserLevel==4
select new SelectListItem { Text = em.EmpName,
Value = emp.Id.ToString() }).ToList();

A better way to combine two Linq Queries into one in MVC5 Controller

in my MVC Application using EntityFramework I have a self referencing table: customers with customerParentID. For children with no emails I want to be able to channel correspondence to parents Email. This is the solution I have that works but I am hitting database 3 times and I want to combine the last 2 into one.
Controller Linq Query
useParentEmail is a bool which is true for children with no Email
Id is obtained by a parameter
var b = db.Lists.SingleOrDefault(a => a.LatestTxnId == Id);
var customerWithNoEmail = db.Customers.SingleOrDefault(a => a.CustomerID == b.CustomerID && a.useParentEmail);
if (customerWithNoEmail != null)
{
var customerParent = db.Customers.SingleOrDefault(a => a.CustomerID == customerWithNoEmail.ParentCustomerID);
customerParentEmail = customerParent.Email;
}
This query hits database twice is there a better way I can do this to hit database once? Any help will be appreciated.
Assuming you are using Entity Framework with the correct navigation properties in the model so that joins work automagically, something like this could work (I've used LinqPad and linq to objects for ease of example). To be honest although I used to be a heavy user of Entity Framework I haven't used it for a few years so I could be a little rusty.
var customers = new[]
{
new { CustomerID = 1, useParentEmail = true, email = "", ParentCustomerID = (int?)2 },
new { CustomerID = 2, useParentEmail = false, email = "parent#example.org", ParentCustomerID = (int?)null },
new { CustomerID = 3, useParentEmail = false, email = "child#example.org", ParentCustomerID = (int?)null }
};
int id = 1;
var emails = (from c in customers
join p in customers on c.ParentCustomerID equals p.CustomerID into childrenParents
from cp in childrenParents.DefaultIfEmpty()
where c.CustomerID == id
select new
{
c.CustomerID,
email = c.useParentEmail == true ? cp.email : c.email
}).SingleOrDefault();
emails.Dump();

Joining of two queries and returning in list format

I am developing a MVC application.
I am using the two queries to fetch the record, and I want to get the common records from these queries .
I want to return the data set in list
Like this
return Json(poList, JsonRequestBehavior.AllowGet);
My two queries are..
var poList = (from po in db.PurchaseOrders
where po.CompanyId == companyId && po.PartyId == partyId && (po.IsDeleted == false || po.IsDeleted == null)
select po into newPO
select new
{
Name = newPO.PONo,
Id = newPO.Id
});
//.ToList().OrderBy(e => e.Name);
var poList2 = (db.Employees.Where(x => x.Id == EmpID)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Employees)
.Distinct()
.SelectMany(x => x.PurchaseOrders)
.Select(po => new { Name = po.PONo, Id = po.Id }));
var finalPO = from PO in poList.ToList().Union(poList2).ToList() select PO);
The reason you can't union them is that the two lists return different objects.
The first list returns an anonymous type with members Name and Id. If, instead, you just wanted to return the purchase orders in query one, then you could simply use the following:
var poList = (
from po in db.PurchaseOrders
where po.CompanyId == companyId &&
po.PartyId == partyId &&
(po.IsDeleted == false || po.IsDeleted == null)
select po
);
You may need to append .ToList() to the query above in order to use the Union(...) method. Then, you should be able to union the two sequences together (assuming poList2 is also a sequence of db.PurhaseOrders objects.
Conversely, instead of changing query for poList above, you could change the query behind poList2 to the following to achieve the same effect, but different results:
var poList2 = (db.Employees.Where(x => x.Id == EmpID)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Employees)
.Distinct()
.SelectMany(x => x.PurchaseOrders)
.Select(po => new { Name = po.PONo, Id = po.Id }));
Personally, I think the first one is more clear (unless there are many fields on the PO object and you only need the two as shown).
UPDATE: I see the original post was edited so that both queries now return the same object (or shape of object). However, the poster is still trying to combine the results incorrectly. The poster is using yet another LINQ query in an attempt to use the Union(...) method. This is completely unnecessary. Might as well write out the code for him/her:
var finalPO = poList.Union(poList2).ToList(); // ToList() only necessary if you need a list back
That should do it.
Really, the two books I mentioned in my comments below will get you a long way in understanding .NET and LINQ: APress - Pro C# and the .NET Framework 4.0; O'Reilly - C# 5 in a Nutshell. There are also many books on LINQ alone--but without a solid grasp of .NET (and C#, F#, or VB), you can't hope to understand or use LINQ.
I dont not think you need the ToList() in the intermediate results, just use the union and do the ToList in the final result, like:
var finalPO = poList.Union(poList2).ToList()
First, create a ViewModel like this:
public class PurchaseOrderViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then, use it in your code like this:
var poList1 = (from po in db.PurchaseOrders
where po.CompanyId == companyId && po.PartyId == partyId
&& (po.IsDeleted == false || po.IsDeleted == null)
select po into newPO
select new PurchaseOrderViewModel
{
Name = newPO.PONo,
Id = newPO.Id
}).ToList();
var poList2 = (db.Employees.Where(x => x.Id == EmpID)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Employees)
.Distinct()
.SelectMany(x => x.PurchaseOrders)
.Select(po => new PurchaseOrderViewModel
{
Name = po.PONo,
Id = po.Id
})).ToList();
var finalList = poList1.Union(poList2);

load navigation properties with filter for Entity Framework 4.3

Few days back I put a question regarding mapping two classes Message and MessageStatusHistory using EF. The mapping is going fine but I am facing some problems with the navigation property StatusHistory in class Message that relates it to MessageStatusHistory objects. I am loading the messages for one user only and want to the statuses pertaining to that user only. Like I would want to show if the user has marked message as read/not-read and when. If I use default loading mechanism like following it loads all the history related to the message irrespective of the user:
IDbSet<Message> dbs = _repo.DbSet;
dbs.Include("StatusHistory").Where(x=>x.MessageIdentifier == msgIdentifier);
To filter history for one user only I tried following trick:
IDbSet<Message> dbs = _repo.DbSet;
var q = from m in dbs.Include("StatusHistory")
where m.MessageIdentifier == msgIdentifier
select new Message
{
MessageIdentifier = m.MessageIdentifier,
/*OTHER PROPERTIES*/
StatusHistory = m.StatusHistory
.Where(x => x.UserId == userId).ToList()
};
return q.ToList();//THROWING ERROR ON THIS LINE
I am getting the error:
The entity or complex type 'MyLib.Biz.Message' cannot be constructed in a LINQ
to Entities query.
I have tried by commenting StatusHistory = m.StatusHistory.Where(x => x.UserId == userId).ToList() also but it has not helped.
Please help me in getting Messages with filtered StatusHistory.
EDIT:- above is resolved with this code:
var q = from m in _repository.DBSet.Include("Histories")
where m.MessageIdentifier == id
select new {
m.Id,/*OTHER PROPERTIES*/
Histories = m.Histories.Where(x =>
x.SenderId == userId).ToList()
};
var lst = q.ToList();
return lst.Select(m => new Message{
Id = m.Id, MessageIdentifier = m.MessageIdentifier,
MessageText = m.MessageText, Replies = m.Replies,
ReplyTo = m.ReplyTo, Histories = m.Histories, SenderId =
m.SenderId, SenderName = m.SenderName, CreatedOn = m.CreatedOn
}).ToList();
But if I try to include replies to the message with:
from m in _repository.DBSet.Include("Replies").Include("Histories")
I am getting error on converting query to List with q.ToList() for Histories = m.Histories.Where(x=> x.SenderId == userId).ToList().
About your EDIT part: You cannot use ToList() in a projection, just leave it an IEnumerable<T> and convert to a List<T> when you construct the Message. You also don't need to create two list objects, you can switch from the LINQ to Entities query to LINQ to Objects (the second Select) by using AsEnumerable():
var list = (from m in _repository.DBSet
where m.MessageIdentifier == id
select new {
// ...
Histories = m.Histories.Where(x => x.SenderId == userId)
})
.AsEnumerable() // database query is executed here
.Select(m => new Message {
// ...
Histories = m.Histories.ToList(),
// ...
}).ToList();
return list;
Be aware that Include has no effect when you use a projection with select. You need to make the properties that you want to include part of the projection - as you already did with select new { Histories.....

How to specify a condition in an Entity Framework join?

I have a Blogs table related to BlogComments table with a FK.
I need to get, through Linq, all the BlogComments items that match a certain flag
If i do:
db.Blogs.Where(b => b.BlogComments.Where(bc=>bc.Where(bc.Flag1==true));
I get "Cannot implicity convert type IEnumerable to bool"
Which is the best way to solve this problem?
Because this expression:
b.BlogComments.Where(...)
returns an IEnumerable (of BlogComments), but you are then passing it into this method:
db.Blogs.Where(...)
which expects a function that returns a bool, not an IEnumerable.
You probably need something like this:
var blogId = 5;
db.BlogComments.Where(bc => bc.BlogId == blogId && bc.Flag1 == true)
If you need to select comments from multiple blogs, then you could try using Contains:
var blogIds = new [] {1,2,3,4,5};
db.BlogComments.Where(bc => blogIds.Contains(bc.BlogId) && bc.Flag1 == true)
If you want to place criteria on the set of blogs, as well as the comments, then you could do this in one query using a join:
var query = from b in db.Blogs
join c in db.BlogComments on c.Blog equals b
where b.SomeField == "some value"
&& c.Flag1 == true
select c;
You could write it in LINQ form.
var blogs = from b in db.Blogs
join c in db.BlogComments
on b.BlogId equals c.BlogId
where c.Flag1
select b;
If you have a composite key you can write
on new { A = b.BlogKey1, B = b.BlogKey2 }
equals new { A = c.CommentKey1, B = c.CommentKey2 }
If it were me, I would just have another DbSet in your DbContext.
DbSet<BlogComment> BlogComments
and just search through there without going through Blogs.
db.BlogComments.Where(bc => bc.Flag1 == true);
If anyone knows if there's anything wrong in doing so, then I'm all ears :)

Resources