I am stuck with the error "value cannot be null. parameter name row" when I use Left join in Linq for DataSet.
Following is the data and the linq query.
DataRowCollection - items (Columns - Item_id, SKU, Quantity)
DataRowCollection - promotions (Columns - Item_id, Promotion_Id)
DataRowCollection - components (Columns - Component_id, Promotion_id)
DataRowCollection - amounts (Columns - Component_id, Amount_Text, currency)
var q =
from Item in items
join promotion in promotions
on Item.Field<int>("Item_id") equals promotion.Field<int?>("Item_id") into promo
from promotion in promo.DefaultIfEmpty()
join disccomponent in components
on promotion.Field<int>("Promotion_Id") equals disccomponent.Field<int?>("Promotion_Id")
join discamounts in amounts
on disccomponent.Field<int>("Component_id") equals discamounts.Field<int?>("Component_id")
where disccomponent.Field<string>("Type") == "Principal"
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamounts.Field<string>("Amount_Text"),
DiscountCurrency = discamounts.Field<string>("currency")
};
I need to get the following:
SKU, Qty and Discount details for items which have discount
SKU, Qty for items which do not have any discount
The code worked without left outer join when all items had discount, but if any item does not have any discount it skips that item and hence I had to use the left outer join.
Any help is highly appreciated. Please let me know if any clarification is needed.
Thanks in advance.
I think you are trying to access properties on a null discamounts. Perhaps your select new should look something like this...
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamounts == null ? "" : discamounts.Field<string>("Amount_Text"),
DiscountCurrency = discamounts == null ? "" : discamounts.Field<string>("currency")
};
Give your join a name by using into and use DefaultIfEmpty on it. Something like this (look at the last join and from expression in the statement)
var q =
from Item in items
join promotion in promotions
on Item.Field<int>("Item_id") equals promotion.Field<int?>("Item_id") into promo
from promotion in promo.DefaultIfEmpty()
join disccomponent in components
on promotion.Field<int>("Promotion_Id") equals disccomponent.Field<int?>("Promotion_Id")
join discamounts in amounts
on disccomponent.Field<int>("Component_id") equals discamounts.Field<int?>("Component_id") into DiscamountJoin
from discamount in DiscamountJoin.DefaultIfEmpty()
where disccomponent.Field<string>("Type") == "Principal"
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamount.Field<string>("Amount_Text"),
DiscountCurrency = discamount.Field<string>("currency")
};
Thanks everyone for your help. I made this work by splitting it into 2 different linq queries below.
The first query gets the promotion data in a var
var promoData =
from promotion in promotions
join component in components
on promotion.Field<int>("Promotion_Id") equals component.Field<int?>("Promotion_Id")
join amount in amounts
on component.Field<int>("Component_id") equals amount.Field<int?>("Component_id")
where component.Field<string>("Type") == "Principal"
select new
{
Item_id = promotion.Field<int?>("Item_id"),
Promotion_id = promotion.Field<int>("Promotion_Id"),
DiscountAmount = amount == null ? "" : amount.Field<string>("Amount_Text"),
DiscountCurrency = amount == null ? "" : amount.Field<string>("currency")
};
The second query uses the promotions var to simplify the query and get the resuls
var q =
from Item in items
join promotion in promoData
on Item.Field<int>("Item_id") equals promotion.Item_id into promo
from promoJoin in promo.DefaultIfEmpty()
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = promoJoin != null ? promoJoin.DiscountAmount : "0",
DiscountCurrency = promoJoin != null ? promoJoin.DiscountCurrency : ""
};
This approach works perfectly and I get all the items whether they have promotions or not. I am still thinking whether doing the same was possible in a single query :)
For now marking this as an answer, if anyone comes up with some other better way will mark that.
Related
I have a code that selecting from table and joining multiple tables and joining dbContext.Database.sqlQuery from view in sql server.
But it gives me this error
Unable to create a constant value of type
'ITManagement.Models.Employee'. Only primitive types or enumeration
types are supported in this context.
My code
public JsonResult getEmployeeAsset(EmployeeController employee)
{
var employeeID = Request.QueryString["employeeID"];
var devices = (from asset in db.Devices
where asset.EmployeeID == employeeID
join brand in db.DeviceBrands on asset.Brand equals brand.ID
join model in db.DeviceModels on asset.Model equals model.ID
join type in db.DeviceTypes on asset.DeviceType equals type.ID
join room in db.Rooms on asset.FullRoomCode equals room.FullCode
//if device has last employee
join lsEmp in db.Database.SqlQuery<LDAPUsers>("SELECT * FROM V_LDAP_Users") on asset.LastEmployeeID equals lsEmp.employeeID into lstEmp
join sysUser in db.AspNetUsers on asset.sscUser equals sysUser.Id
from lastEmployee in lstEmp.DefaultIfEmpty()
select new
{
deviceID = asset.ID,
SerialNumber = asset.SerialNumber,
Type = type.Type,
BrandName = brand.BrandName,
ModelName = model.ModelName,
MaccCode = asset.MaccCode,
PONumber = asset.PONumber,
WarrantyDate = asset.WarrantyDate.ToString(),
MacAddress = asset.MacAddress,
WIFIMacAddress = asset.WIFIMacAddress,
PCName = asset.PCName,
LastEmployee = asset.LastEmployeeID + "-" + lastEmployee.employeeName,
Shared = asset.Shared == 1 ? "True" : "False",
Location = room.RoomName,
RecordedBy = sysUser.Name,
requestID = (from request in db.StoreRequests where request.DeviceID == asset.ID && request.State == 1 && request.VoucherType == "ASD" orderby request.ID select request.ID).FirstOrDefault()
}).DefaultIfEmpty();
return Json(new { assets = devices == null ? null : devices }, JsonRequestBehavior.AllowGet);
}
Your help please, thanks.
First of all, have you tried nested queries by commenting them out?
for example;
//join lsEmp in db.Database.SqlQuery<LDAPUsers>("SELECT * FROM V_LDAP_Users") on asset.LastEmployeeID equals lsEmp.employeeID into lstEmp
//requestID = (from request in db.StoreRequests where request.DeviceID == asset.ID && request.State == 1 && request.VoucherType == "ASD" orderby request.ID select request.ID).FirstOrDefault()
If there is no problem in these two, you can quickly find out which one is causing the problem by commenting the fields.
Tip: Also, more than 3 joins will affect your query performance. Try to split your queries as much as possible.
I have several tables, the main one is called DefectRecord, others are called DefectArea, DefectLevel...etc and the one called DefectAttachment. And this problem is about joining DefectRecord with other tables to get a ViewModel for further use. What the hard part I am facing is about the DefectAttachment table.
DefectRecord has a 1-to-many relation with DefectAttachment. While there may be NO attachment at all for one defect record, there may be multiple attachments.
Logically I tried to perform a left join among DefectRecord & DefectAttachment, but there is one more requiredment:
If there is multiple attachments, select ONLY the oldest one(i.e. the
one with oldest CreatedDate field value)
I am stuck at this requirement, how can I perform this with LINQ-to-Entities? Below is the code of what I have now:
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join a in attachmentQuery on dr.DefectRecordID equals a.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
select new DefectRecordViewModel
{
DefectRecordCode = dr.Code,
DefectAttachmentContent = g == null ? null : g.FileContent,
LookupFilterName = ft.FilterName,
}).ToList();
The *Query variable are the IQueryable object which get the full list of corresponding table.
Group your results by the Code and FilterName and then for the content take that of the item in the group that has the oldest date
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join d in attachmentQuery on dr.DefectRecordID equals d.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
group g by new { dr.Code, ft.FilterName } into gg
select new DefectRecordViewModel
{
DefectRecordCode = gg.Key.Code,
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault() == null? null: gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault().FileContent,
LookupFilterName = gg.Key.FilterName,
}).ToList();
If using C# 6.0 or higher then you can do:
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime)
.FirstOrDefault()?.FileContent,
This is my sql query, how can I generate this query into LINQ:
SELECT
TimeTable.StartTime,
sum(Booking.Quantity) as Total
FROM PromotionSlot
RIGHT JOIN TimeTable
ON PromotionSlot.StartHour = TimeTable.StartTime
LEFT JOIN Booking
ON PromotionSlot.ID = Booking.PromotionSlotID
GROUP BY TimeTable.StartTime
Result:
|StartTime |Total|
---------------------
9 NULL
10 NULL
11 2
12 2
13 NULL
14 NULL
15 NULL
16 NULL
17 NULL
18 NULL
19 NULL
20 NULL
21 NULL
This is what I attempted, I'm not sure that the structure of the linq is correct with my SQL query. But I faced error about The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type. Please guide me, thank you all in advance!
var bookingdata =
(from ps in dc.PromotionSlots
join t in dc.TimeTables on ps.StartHour equals t.StartTime into Ts
join bo in dc.Bookings on ps.ID equals bo.PromotionSlotID into Bs
from time in Ts.DefaultIfEmpty()
from book in Bs.DefaultIfEmpty()
group new {time,book} by time.StartTime into NewGroup
select new dataView
{
StartTime = NewGroup.Key,
numBookings = NewGroup.Select(a => a.book!=null? a.book.Quantity: 0).Sum()
}).ToList();
Here is my dataView model
public class dataView
{
public int? StartTime { get; set; }
public int? numBookings { get; set; }
}
UPDATED:
changed StartTime in my dataView model to int? and this is the result by using console.log()
Format: #:console.log("* " + "#item.StartTime" + ":" + "#item.numBookings");
* :0
* 10:0
* 11:2
* 12:2
I found out the reason why the above console.log() will appear this result. I tried to change my SQL Query RIGHT JOIN TimeTable to LEFT JOIN TimeTable. The return result totally the same like the output from my LINQ.
I think the problem you're hitting is that in ad-hoc query results, any column value can be null (eg., the Total column in your results example has mixed values, int and null). But C# is not so lenient, an a null value cannot be stuffed into a integer value.
I think that in your dataView type, the numBookings property is probably set to int, and an int type in C# is not nullable.
I believe if you change it to a nullable int, like:
public int? numBookings { get; set; }
then that may fix your error. If not that, then the following might work:
numBookings = NewGroup.Select(a => a.book!=null ? (a.book.Quantity ?? 0): 0).Sum() ?? 0
But I am not sure about this last one.
UPDATE
What if you update your LINQ query to use LEFT JOIN for both joins, like so:
var bookingdata = (
from t in dc.TimeTables
join ps in dc.PromotionSlots on t.StartTime equals ps.StartHour into Ts
from time in Ts.DefaultIfEmpty()
join bo in dc.Bookings on time.ID equals bo.PromotionSlotID into Bs
from book in Bs.DefaultIfEmpty()
group new {time, book} by time.StartTime into NewGroup
select new dataView
{
StartTime = NewGroup.Key,
numBookings = NewGroup.Select(a => a.book!=null? a.book.Quantity: 0).Sum()
}
).ToList();
Does this make the results more consistent with the SQL query?
I'm trying to do a left join, not an inner join in a linq query. I have found answers related to using DefaultIfEmpty() however I can't seem to make it work. The following is the linq query:
from a in dc.Table1
join e in dc.Table2 on a.Table1_id equals e.Table2_id
where a.Table1_id == id
orderby a.sort descending
group e by new
{
a.Field1,
a.Field2
} into ga
select new MyObject
{
field1= ga.Key.Field1,
field2= ga.Key.Field2,
manySubObjects = (from g in ga select new SubObject{
fielda= g.fielda,
fieldb= g.fieldb
}).ToList()
}).ToList();
The query only gives me the rows from table 1 that have a corresponding record in table 2. I would like every record in table 1 populated into MyObject and a list of 0-n corresponding records listed in manySubObjects for each MyObject.
UPDATE:
I tried the answer to the question that is a "possible duplicate", mentioned below. I now have the following code that does give me one record for each item in Table1 even if there is no Table2 record.
from a in dc.Table1
join e in dc.Table2 on a.Table1_id equals e.Table2_id into j1
from j2 in j1.DefaultIfEmpty()
where a.Table1_id == id
orderby a.sort descending
group j2 by new
{
a.Field1,
a.Field2
} into ga
select new MyObject
{
field1= ga.Key.Field1,
field2= ga.Key.Field2,
manySubObjects = (from g in ga select new SubObject{
fielda= g.fielda,
fieldb= g.fieldb
}).ToList()
}).ToList();
However, with this code, when there is no record in table2 I get "manySubObject" as a list with one "SubObject" in it with all null values for the properties of "SubObject". What I really want is "manySubObjects" to be null if there is no values in table2.
In reply to your update, to create the null listing, you can do a ternary in your assignment of manySubObjects.
select new MyObject
{
field1= ga.Key.Field1,
field2= ga.Key.Field2,
manySubObjects =
(from g in ga select g).FirstOrDefaut() == null ? null :
(from g in ga select new SubObject {
fielda= g.fielda,
fieldb= g.fieldb
}).ToList()
}).ToList();
Here is a dotnetfiddle that tries to do what you're attempting. https://dotnetfiddle.net/kGJVjE
Here is a subsequent dotnetfiddle based on your comments. https://dotnetfiddle.net/h2xd9O
In reply to your comments, the above works with Linq to Objects but NOT with Linq to SQL. Linq to SQL will complain that it, "Could not translate expression ... into SQL and could not treat as a local expression." That's because Linq cannot translate the custom new SubObject constructor into SQL. To do that, you have to write more code to support translation into SQL. See Custom Method in LINQ to SQL query and this article.
I think we've sufficiently answered your original question about left joins. Consider asking a new question about using custom methods/constructors in Linq to SQL queries.
I think the desired Result that you want can be given by using GroupJoin()
The code Below will produce a structure like so
Field1, Field2, List < SubObject > null if empty
Sample code
var query = dc.Table1.Where(x => Table1_id == id).OrderBy(x => x.sort)
.GroupJoin(dc.Table2, (table1 => table1.Table1_id), (table2 => table2.Table2_id),
(table1, table2) => new MyObject
{
field1 = table1.Field1,
field2 = table1.Field2,
manySubObjects = (table2.Count() > 0)
? (from t in table2 select new SubObject { fielda = t.fielda, fieldb = t.fieldb}).ToList()
: null
}).ToList();
Dotnetfiddle link
UPDATE
From your comment I saw this
ga.Select(g = > new SubObject(){fielda = g.fielda, fieldb = g.fieldb})
I think it should be (depends on how "ga" is built)
ga.Select(g => new SubObject {fielda = g.fielda, fieldb = g.fieldb})
Please update your question with the whole query, it will help solve the issue.
** UPDATE BIS **
sentEmails = //ga.Count() < 1 ? null :
//(from g in ga select g).FirstOrDefault() == null ? null :
(from g in ga select new Email{
email_to = g.email_to,
email_from = g.email_from,
email_cc = g.email_cc,
email_bcc = g.email_bcc,
email_subject = g.email_subject,
email_body = g.email_body }).ToList()
Should be:
sentEmails = //ga.Count() < 1 ? null :
((from g in ga select g).FirstOrDefault() == null) ? null :
(from g in ga select new Email{
email_to = g.email_to,
email_from = g.email_from,
email_cc = g.email_cc,
email_bcc = g.email_bcc,
email_subject = g.email_subject,
email_body = g.email_body }).ToList()
Checks if the group has a First, if it doesn't the group doesn't have any records so the Action.Name for a Time Stamp has no emails to send. If the First isn't null the loop throw the group elements and create a list of Email,
var results =
(
// Use from, from like so for the left join:
from a in dc.Table1
from e in dc.Table2
// Join condition goes here
.Where(a.Id == e.Id)
// This is for the left join
.DefaultIfEmpty()
// Non-join conditions here
where a.Id == id
// Then group
group by new
{
a.Field1,
a.Field2
}
).Select(g =>
// Sort items within groups
g.OrderBy(item => item.sortField)
// Project required data only from each item
.Select(item => new
{
item.FieldA,
item.FieldB
}))
// Bring into memory
.ToList();
Then project in-memory to your non-EF-model type.
I just want the apartment complex count along with the other values. Only adding the count breaks the code. The error I get is "Sequence operators not supported for type 'System.String'." I have also tried changing apartCount to an int with no luck. Any help would be appreciated
using (var db = new DataClasses2DataContext())
{
var zips = (from s in db.ZipCodeServiceAvailabilities
join b in db.ZipCodeBoundaries on s.ZipCode equals b.ZipCode
join a in db.pdx_apart_views on s.ZipCode equals a.Zip_Code
where (s.IsServiced == 1 && b.Ordering % 10 == 0)
orderby b.ZipCode
select new
{
zipCode = b.ZipCode.Trim(),
latitude = b.Latitude,
longitude = b.Longitude,
apartCount = a.Apartment_complex.Count()
}).ToArray();
}
I think you miss group by clause in your query.
or you can use corolated sub query in select clause. please explain your question more