Count nulls in properties with one LINQ query - linq

Paste into LINQPad:
void Main()
{
List<Data> list = new List<Data>();
list.Add(new Data());
list.Add(new Data{a="a", b="b"});
list.Add(new Data{a=null, b="b"});
var queryA = from data in list where data.a == null select data;
var queryB = from data in list where data.b == null select data;
var countNulls = new {a = queryA.Count(),b = queryB.Count()};
countNulls.Dump();
}
class Data
{
public string a {get;set;}
public string b {get;set;}
}
Instead of using queryA and queryB is it possible to do this in one query?
Answer:
All queries below generate exactly same SQL, so it's just a preference of coder what to choose.
var countNulls = new
{
a = queryA.Count(),
b = queryB.Count()
};
var countNulls2 = new
{
a = list.Count(d => d.a == null),
b = list.Count(d => d.b == null)
};
var countNulls3 = list.Aggregate(
new { a = 0, b = 0 },
(acc, data) => new
{
a = acc.a + (data.a == null ? 1 : 0),
b = acc.b + (data.b == null ? 1 : 0),
});
Update: apparently (thanks to Evan Stoev) this task can be done 20x faster on EF's DbSet and it creates one SQL query.
var countNulls4 =
(from data in db.Data
group data by 1 into g
select new
{
a = g.Sum(data => data.a == null ? 1 : 0),
b = g.Sum(data => data.b == null ? 1 : 0)
}).First();

For completeness, you can use Aggregate to get the result in a single pass over the input sequence:
var countNulls = list.Aggregate(
new { a = 0, b = 0 },
(acc, data) => new
{
a = acc.a + (data.a == null ? 1 : 0),
b = acc.b + (data.b == null ? 1 : 0),
});
But I'm not sure it would be more efficient compared to 2 separate Count calls due to the need of anonymous object allocation on each step.
UPDATE: It turns out that you are asking for a single SQL query (so list is not actually a List<Data> but DbSet<Data> I guess). In LINQ to Entities, you can use group by constant technique, which combined with replacing Count(condition) with Sum(condition ? 1 : 0) will produce a nice single SQL query pretty similar to what you would write manually:
var countNulls =
(from data in db.Data
group data by 1 into g
select new
{
a = g.Sum(data => data.a == null ? 1 : 0),
b = g.Sum(data => data.b == null ? 1 : 0)
}).First();

You can simply use the Count() overload that takes a predicate:
var countNulls = new
{
a = list.Count(d => d.a == null),
b = list.Count(d => d.b == null)
};
Output is:

As far as I understand, you want to count nulls for two different object properties regardless if the other one is null. In other words:
a=null, b="b"
a="a", b=null
a=null, b=null
would return count for 2 and 2.
In that case you can do it this way in one query:
var queryAB = from data in list where data.a == null || data.b == null select data;

Related

LINQ join issue, returns all null value

I am new in EF and LINQ and I am facing a strange issue. When I check null value in my select new block, all values from child table is coming null. Below is the LINQ query.
My Linq Code
var linqResult = from pd in entities.tblpackagedetails
join ps in entities.tblpackageselecteds
on pd.PackageDetailsID equals ps.PackageDetailsID
into tabJoin
from tj in tabJoin.Where(ps => ps.UserID == userID
&& ps.IsActive == true).DefaultIfEmpty()
select new
{
IsComplete = (tj == null) ? false : tj.IsComplete,
IsActive = (tj == null) ? false : tj.IsActive,
UserID = (tj == null) ? 0 : tj.UserID,
IsMandatory = pd.IsMandatory,
PackageSelectedID = (tj == null) ? 0 : tj.PackageSelectedID,
IsSelected = (tj == null ? false : tj.IsSelected),
pd.Amount,
pd.Code,
pd.Description,
pd.Points,
pd.PackageDetailsID
};
foreach (var result in linqResult)
{
packagesSelected.Add(new PackageDetailDataModel()
{
Amount = result.Amount,
Code = result.Code,
Description = result.Description,
IsComplete = result.IsComplete,
IsMandatory = result.IsMandatory,
PackageDetailsID = result.PackageDetailsID,
PackageSelectedID = result.PackageSelectedID,
Points = result.Points,
IsActive = result.IsActive,
UserID = result.UserID,
IsSelected = result.IsSelected
});
}
SQL generated by Visualizer
SELECT
`Extent1`.`PackageDetailsID`,
`Extent2`.`IsComplete`,
`Extent2`.`IsActive`,
`Extent2`.`UserID`,
`Extent1`.`IsMandatory`,
`Extent2`.`PackageSelectedID`,
`Extent2`.`IsSelected`,
`Extent1`.`Amount`,
`Extent1`.`Code`,
`Extent1`.`Description`,
`Extent1`.`Points`
FROM `tblpackagedetails` AS `Extent1`
LEFT OUTER JOIN `tblpackageselected` AS `Extent2`
ON (`Extent1`.`PackageDetailsID` = `Extent2`.`PackageDetailsID`)
AND ((`Extent2`.`UserID` = #linq_0) AND (1 = `Extent2`.`IsActive`))
When I ran above sql in MySQL workbench I got below output (repalcing #linq_0 with userID).
My Parent Table Structure
Child Table Structure
Output I want
But the values for IsComplete, IsActive, UserID, PackageSelectedID and IsSelected null as a result condition checking in select new block assign false or 0.
If I remove null checking, I get value for first 3 rows and in fourth iteration I get below exception.
The cast to value type 'Boolean' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type
Please help... :(
Working code block
packagesSelected = new List<PackageDetailDataModel>();
var linqResult = from pd in entities.tblpackagedetails
join ps in entities.tblpackageselecteds
on pd.PackageDetailsID equals ps.PackageDetailsID
into tabJoin
from tj in tabJoin.Where(ps => ps.UserID == userID
&& ps.IsActive == true).DefaultIfEmpty()
select new
{
IsComplete = (bool?)tj.IsComplete,
IsActive = (bool?)tj.IsActive,
UserID = (int?)tj.UserID,
IsMandatory = pd.IsMandatory,
PackageSelectedID = (int?)tj.PackageSelectedID,
IsSelected = (bool?)tj.IsSelected,
pd.Amount,
pd.Code,
pd.Description,
pd.Points,
pd.PackageDetailsID
};
foreach (var result in linqResult)
{
packagesSelected.Add(new PackageDetailDataModel()
{
Amount = result.Amount,
Code = result.Code,
Description = result.Description,
IsComplete = (result.IsComplete ?? false),
IsMandatory = result.IsMandatory,
PackageDetailsID = result.PackageDetailsID,
PackageSelectedID = (result.PackageSelectedID ?? 0),
Points = result.Points,
IsActive = (result.IsActive ?? false),
UserID = (result.UserID ?? 0),
IsSelected = (result.IsSelected ?? false)
});
}
Thanks to 2Kay :)
When tj is null, EF consieders all properties of tj as null. It's ok, but when EF trying to materialize them into value-types it fails. So the solution is to use nullable types..
Try this query:
var linqResult = from pd in entities.tblpackagedetails
join ps in entities.tblpackageselecteds
on pd.PackageDetailsID equals ps.PackageDetailsID
into tabJoin
from tj in tabJoin.Where(ps => ps.UserID == userID
&& ps.IsActive == true).DefaultIfEmpty()
select new
{
IsComplete = (bool?) tj.IsComplete,
IsActive = (bool?) tj.IsActive,
UserID = (int?) tj.UserID,
IsMandatory = pd.IsMandatory,
PackageSelectedID = (int?) tj.PackageSelectedID,
IsSelected = (bool?) tj.IsSelected,
pd.Amount,
pd.Code,
pd.Description,
pd.Points,
pd.PackageDetailsID
};

Return the result of joining two tables in Silverlight 4.0

Main Code:
DomainServiceAccountManager d = new DomainServiceAccountManager();
EntityQuery<ListBuy> q = d.GetListMemberBuyQuery();
LoadOperation<ListBuy> l = d.Load(q);
DGListBuy.ItemsSource = l.Entities;
The code:
public IQueryable<ListBuy> GetListMemberBuy()
{
var membuy =
from mem in this.ObjectContext.Members
from b in this.ObjectContext.Buys.Where(b => b.ID_member == mem.ID)
.OrderByDescending(b => b.ID)
.DefaultIfEmpty()
select new { b.ID, mem.Name, b.Money, b.Tarikh };
return membuy;
}
I get the following message:
Cannot implicitly convert type 'System.Linq.IQueryable<AnonymousType#1>' to 'System.Linq.IQueryable<AccountManager.Web.ListBuy>'. An explicit conversion exists (are you missing a cast?)
You method GetListMemberBuy actually returns an IQueryable of anonymous type instead of an IQueryable of type ListBuy. These are not identical, hence the (compile time?) error.
I can only assume that ListBuy also exists in the database, but if so, then you can remove the anonymous type.
public IQueryable<ListBuy> GetListMemberBuy()
{
var membuy =
from mem in this.ObjectContext.Members
from b in this.ObjectContext.Buys.Where(b => b.ID_member == mem.ID)
.OrderByDescending(b => b.ID)
.DefaultIfEmpty()
select new ListBuy() { ID = b.ID, Name = mem.Name, Money = b.Money, Tarikh = b.Tarikh }; // <-- new ListBuy() !!
return membuy;
}
If ListBuy does not exist in the database, then you cannot return an IQueryable. Maybe this will work.
public IEnumerable<ListBuy> GetListMemberBuy()
{
var membuy =
from mem in this.ObjectContext.Members
from b in this.ObjectContext.Buys.Where(b => b.ID_member == mem.ID)
.OrderByDescending(b => b.ID)
.DefaultIfEmpty()
select new { b.ID, mem.Name, b.Money, b.Tarikh };
return membuy
.AsEnumerable()
.Select(b => new ListBuy() {
ID = b.ID, Name = b.Name, Money = b.Money, Tarikh = b.Tarikh
});
}
All this is a bit speculation since you didn't include the ListBuy class definition in your question.

Linq: Nested queries are better than joins, but what if you use 2 nested queries?

In her book Entity Framework Julie Lerman recommends using nested queries in preference to joins (scroll back a couple of pages).
In her example see populates 1 field this way, but what id you want to populate 2?
I have an example here where I would prefer to populate the Forename and Surname with the same nested query rather than 2 separate ones. I just need to know the correct syntax to do this.
public static List<RequestInfo> GetRequests(int _employeeId)
{
using (SHPContainerEntities db = new SHPContainerEntities())
{
return db.AnnualLeaveBookeds
.Where(x => x.NextApproverId == _employeeId ||
(x.ApproverId == _employeeId && x.ApprovalDate.HasValue == false))
.Select(y => new RequestInfo
{
AnnualLeaveDate = y.AnnualLeaveDate,
Forename = (
from e in db.Employees
where e.EmployeeId == y.EmployeeId
select e.Forename).FirstOrDefault(),
Surname = (
from e in db.Employees
where e.EmployeeId == y.EmployeeId
select e.Surname).FirstOrDefault(),
RequestDate = y.RequestDate,
CancelRequestDate = y.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
})
.OrderBy(x => x.AnnualLeaveDate)
.ToList();
}
}
There's nothing wrong with your query, but you can write it in a way that is much simpler, without the nested queries:
public static List<RequestInfo> GetRequests(int employeeId)
{
using (SHPContainerEntities db = new SHPContainerEntities())
{
return (
from x in db.AnnualLeaveBookeds
where x.NextApproverId == employeeId ||
(x.ApproverId == employeeId && x.ApprovalDate == null)
orderby x.AnnualLeaveDate
select new RequestInfo
{
AnnualLeaveDate = x.AnnualLeaveDate,
Forename = x.Employee.Forename,
Surname = x.Employee.Surname,
RequestDate = x.RequestDate,
CancelRequestDate = x.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
}).ToList();
}
}
See how I just removed your from e in db.Employees where ... select e.Forename) and simply replaced it with x.Employee.Forename. When your database contains the correct foreign key relationships, the EF designer will successfully generate a model that contain an Employee property on the AnnualLeaveBooked entity. Writing the query like this makes it much more readable.
I hope this helps.
try this
using (SHPContainerEntities db = new SHPContainerEntities())
{
return db.AnnualLeaveBookeds
.Where(x => x.NextApproverId == _employeeId ||
(x.ApproverId == _employeeId && x.ApprovalDate.HasValue == false))
.Select(y =>
{
var emp = db.Emplyees.Where(e => e.EmployeeId == y.EmployeeId);
return new RequestInfo
{
AnnualLeaveDate = y.AnnualLeaveDate,
Forename = emp.Forename,
Surname = emp.Surname,
RequestDate = y.RequestDate,
CancelRequestDate = y.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
};
).OrderBy(x => x.AnnualLeaveDate).ToList();
}

How to make this LINQ To entity method work when it has Nullable LEFT JOIN

Here is the code snippet, actually the whole method. This method works f,ine when NULLAblE Foreign Key Refernces has value. When the value is not there, then this method does not work. My idea is to get all the records even if the references column is NULL. Here is the code :
public List<PostedJob> GetPostedJobs(int startingIndex, int maximumRows)
{
using (var records = new CommonEvent())
{
var resultSet =
from r in records.ProjectPosts
join rr in records.Categories on r.Category_FK equals rr.ID
join al in records.ApplyForLimits on r.ApplyForLimit_FK
equals al.Id
//from uImage in
// records.UploadedFiles
// .Where(uu=>uu.Id == r.UploadedFileInfo_FK
// || r.UploadedFileInfo_FK == null).DefaultIfEmpty()
join a in records.UploadedFiles on r.UploadedFileInfo_FK
equals a.Id into something
from uImage in something.DefaultIfEmpty()
orderby r.PostId
select new Models.PostedJob
{
ApplyForLimitName = al.Name,
ProjectTitle = r.ProjectTitle,
ProjectDescription = r.ProjectDescription,
ProjectSummaryDescription = r.ProjectSummaryDescription,
SkillsRequirements = r.SkillsRequirements,
CategoryName = rr.CategoryName,
UploadedFileID = (int) r.UploadedFileInfo_FK,
UploadedFileInformation = uImage == null ?
new Models.UploadedFile
{
fileContents = new byte [] { (byte) 0},
FileExtension = string.Empty,
FileName = string.Empty,
FileSize = 0,
UploadedDate = DateTime.Now
}
:
new Models.UploadedFile
{
fileContents = uImage.FileContents,
FileExtension = uImage.FileExtension,
FileName = uImage.FileName,
FileSize = uImage.FileSize,
UploadedDate = DateTime.Now
}
};
return resultSet.Skip(startingIndex).Take(maximumRows).ToList();
}
Thank you for any suggestions or ideas on how to proceed . I am using .NET 4.0
Can you not use the Associations generated for you?
var a = records
.ProjectPosts
.Select(
projectPost =>
new Models.PostedJob()
{
ProjectTitle = projectPost.ProjectTitle,
CategoryName = projectPost.Category.CategoryName,
});
Something along those lines?
EDIT: And just add Null checks when the FK may fail
example:
CategoryName = projectPost.Category == null ? String.Empty : projectPost.Category.CategoryName,

LINQ Union with Constant Values

Very primitive question but I am stuck (I guess being newbie). I have a function which is supposed to send me the list of companies : ALSO, I want the caller to be able to specify a top element for the drop-down list as well.. (say for "None"). I have following piece of code, how I will append the Top Element with the returning SelectList?
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new
{
c.Id,
c.Company
};
return new SelectList( cs.AsEnumerable(), "Id", "Comapny" );
}
Thanks!
This should work for you:
List<Corporate> corporates =
(from c in entityDataContext.Corporates
where (c.Approved == onlyApproved || onlyApproved == false)
select c).ToList();
corporates.Add(new Corporate { Id = -1, Company = "None" });
return new SelectList(corporates.AsEnumerable(), "Id", "Comapny");
This method has always worked for me.
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new SelectListItem {
Value = c.Id,
Text = c.Company
};
var list = cs.ToList();
list.Insert(0, TopElement);
var selectList = new SelectList( list, "Value", "Text" );
selectList.SelectedValue = TopElement.Value;
return selectList;
}
Update forgot the lesson I learned when I did this. You have to output the LINQ as SelectListItem.
cs.ToList().Insert(0, new { TopElement.ID, TopElement.Company });
You could convert it to a list as indicated or you could union the IQueryable result with a constant array of one element (and even sort it):
static void Main(string[] args)
{
var sampleData = new[] {
new { Id = 1, Company = "Acme", Approved = true },
new { Id = 2, Company = "Blah", Approved = true }
};
bool onlyApproved = true;
var cs = from c in sampleData
where (c.Approved == onlyApproved || onlyApproved == false)
select new
{
c.Id,
c.Company
};
cs = cs.Union(new [] {new { Id = -1, Company = "None" }}).OrderBy(c => c.Id);
foreach (var c in cs)
{
Console.WriteLine(String.Format("Id = {0}; Company = {1}", c.Id, c.Company));
}
Console.ReadKey();
}

Resources