I need to create a LEFT OUTER JOIN in linq lambda syntax. The SQL I am trying to create a linq equivalent of is:
SELECT DISTINCT
p.PartNum AS PartNum, p.ShortChar01 AS SkuType,
vv.VendorID AS VendorCode,
p.PartDescription AS Description, p.Company AS Company
FROM
Part p WITH (NOLOCK)
INNER JOIN
PartPlant pp ON p.Company = pp.Company AND p.PartNum = pp.PartNum
LEFT OUTER JOIN
Vendor vv On pp.VendorNum = vv.VendorNum
WHERE
p.RefCategory = #refCategory
So as you can see its a fairly simple query joining a few tables. The issue is that it could happen that there is no vendor but we still want the rest of the information hence the left outer join.
My current attempt to recreate this is:
_uow.PartService
.Get()
.Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v });
I am aware that the select isn't returning the same fields at the moment. I have ignored that for now as I am trying to ensure i am getting the correct values first.
The SQL query returns 41 records with 1 record having a null vendor. The linq query returns 40 records obviously not returning the one with the null vendor. I have tried using GroupJoin() and DefaultIfEmpty() but I cannot get it to work.
Any help would be greatly appreciated.
From the comment and links from user2321864, I managed to get it working as follows:
_uow.PartService.Get().Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v })
.SelectMany(y => y.V.DefaultIfEmpty(),
(x, y) => new { PPRC = x.PPRC, Vendor = y })
.Select(r => new Level2Parts()
{
CompanyCode = r.PPRC.Part.Company,
Description = r.PPRC.Part.PartDescription,
PartNum = r.PPRC.Part.PartNum,
SkuType = r.PPRC.Part.ShortChar01,
VendorCode = r.Vendor.VendorID
})
.Distinct();
Related
I was trying to convert a SQL Server query with multiple table joins and multiple conditions in a join to LINQ. I am getting an error while trying to use multiple conditions in join using &&.
Below is my SQL Server query.
SELECT j.ID,
j.OnTime AS 'Alarm Appear Time',
j.OffTime AS 'Alarm Disappear Time',
j.OnNoticeTime AS 'Alarm Ack Time',
j.OffNoticeTime AS 'Alarm Close Time',
j.InstanceID AS 'Asset ID',
i.RemarkPath AS 'Asset',
j.PropertyID AS 'Message ID',
p.Remark AS 'Alarm Message',
a.X4AlarmGroup_ID AS 'Priority ID',
g.Name AS 'Priority Group'
FROM [XAMRuntimeX4].[dbo].[X4Journals] j
JOIN [XAMRuntimeX4].[dbo].[V_X4InstanceRemarks] i
ON j.InstanceID = i.Instance_ID
JOIN [XAMRuntimeX4].[dbo].[V_AutomationControlPropertyRemarks] p
ON j.PropertyID = p.Property_ID
JOIN [XAMControlX4].[dbo].[X4AlarmConfigs] a
ON j.InstanceID = a.X4Instance_ID
AND j.PropertyID = a.X4Property_ID
JOIN [XAMControlX4].[dbo].[X4AlarmGroups] g
ON a.X4AlarmGroup_ID = g.ID
WHERE i.Language = 'iv'
AND p.LANGUAGE = 'iv'
ORDER BY j.OnTime DESC;
Below given is my attempt to convert it into LINQ
var finalAlarm = xAMRuntimeX4Context.X4Journals
.Join(xAMControlX4Context.X4InstanceRemarks,
j => j.InstanceID, i => i.Instance_ID, (j, i) => new { j, i })
.Join(xAMControlX4Context.AutomationControlPropertyRemarks,
ppc => ppc.j.PropertyID, p => p.Property_ID, (ppc, p) => new { ppc, p })
.Join(xAMControlX4Context.X4AlarmConfigs,
(ppc2 => ppc2.ppc.j.InstanceID, a => a.X4Instance_ID) &&
(ppc2 => ppc2.ppc.j.PropertyID, a => a.X4Property_ID),
(ppc2, a) => new { ppc2, a });
Remarks
Your LINQ attempt is missing the X4AlarmGroups.
To join X4Journals and X4AlarmConfigs on two columns I used ValueTuples.
I also unwrapped the intermediate anonymous types.
var finalAlarm = xAMRuntimeX4Context.X4Journals
.Join(xAMControlX4Context.X4InstanceRemarks,
j => j.InstanceID, i => i.Instance_ID, (j, i) => new { j, i })
.Join(xAMControlX4Context.AutomationControlPropertyRemarks,
ppc => ppc.j.PropertyID, p => p.Property_ID, (ppc, p) => new { ppc.j, ppc.i, p })
.Join(xAMControlX4Context.X4AlarmConfigs,
ppc2 => new { k1 = ppc2.j.InstanceID, k2 = ppc2.j.PropertyID },
a => new { k1 = a.X4Instance_ID, k2 = a.X4Property_ID },
(ppc2, a) => new { ppc2.j, ppc2.i, ppc2.p, a })
.Join(xAMControlX4Context.X4AlarmGroups,
ppc3 => ppc3.a.X4AlarmGroup_ID, g => g.ID, (ppc3, g) => new { ppc3.j, ppc3.i, ppc3.p, ppc3.a, g })
.Where(ppc4 => ppc4.i.Language == "iv" && ppc4.p.Language == "iv")
.OrderBy(ppc4 => ppc4.j.OnTime)
.Select(x => new {
x.j.ID,
AlarmAppearTime = x.j.OnTime,
AlarmDisappearTime = x.j.OffTime,
AlarmAckTime = x.j.OnNoticeTime,
AlarmCloseTime = x.j.OffNoticeTime,
AssetID = x.j.InstanceID,
Asset = x.i.RemarkPath,
MessageID = x.j.PropertyID,
AlarmMessage = x.p.Remark,
PriorityID = x.a.X4AlarmGroup_ID,
PriorityGroup = x.g.Name
});
See How to do joins in LINQ on multiple fields in single join.
You cannot use the && operator to select multiple columns in a Linq method syntax join. Instead you need to code your outer and inner key selectors to each return both key values together in a new anonymous object.
In your case, something like:
.Join(xAMControlX4Context.X4AlarmConfigs,
ppc2 => new { ppc2.ppc.j.InstanceID, ppc2.ppc.j.PropertyID },
a => new { a.X4Instance_ID, a.X4Property_ID },
(ppc2, a) => new { ppc2, a })
In Linq To SQL, thinking you have proper relations at database level, you seldom need joins. Instead you use navigational properties. In your case, it is not clear if those relations are 1-to-1, 1-to-many, many-to-1 or many-to-many. It would matter just for getting the Remarkpath, Remark like properties. Looking from your SQL, as it stands, we have to consider that they may lead to multiple rows, thus it translates to LinqToSQL as:
var finalAlarm = from j in xAMRuntimeX4Context.X4Journals
from i in j.X4InstanceRemarks.Where(x => x.Langauge == "iv")
from p in j.AutomationControlPropertyRemarks.Where(x => x.Langauge == "iv")
from a in j.X4AlarmConfigs
from g in a.X4AlarmGroups
select new
{
ID = j.ID,
AlarmAppearTime = j.OnTime,
AlarmDisappearTime = j.OffTime,
AlarmAckTime = j.OnNoticeTime,
AlarmCloseTime = j.OffNoticeTime,
AssetID = j.InstanceID,
Asset = i.RemarkPath,
MessageId = j.PropertyId,
AlarmMessage = p.Remark,
PriorityID = a.X4AlarmGroup_ID,
PriorityGroup = g.Name
};
Note: You can use LinqPad to write this easier. Connect to your database, and check the schema. It would show you the navigations, 1-to-many etc relations.
Wondering why LINQ doesn't have a Left Join method. I've been trying to figure this out with myriad examples on SO, but no such luck. The other examples show simple examples with one join. If I group the joins then I only get references to the TradeCountries table in the select statement.
Being new to LINQ, I could've had this done 4 hours ago with a simple SELECT statement, but here I'm am trying to figure out why the LeftJoin method was left out of LINQ.
What does the line with "LeftJoin" need to be changed to make this work?
/*
* GetTop5Distributors
#param int array of series IDs
*/
public List<TopDistributors> Get5TopDistributors(IEnumerable<int> seriesIds)
{
_context = new MySQLDatabaseContext();
var result = _context.TradesTrades
.Join(_context.TradesSeries, tt => tt.SeriesId, ts => ts.Id, (tt, ts) => new { tt, ts })
.Join(_context.TradesTradeDistributors, tsd => tsd.tt.Id, ttd => ttd.TradeId,
(tsd, ttd) => new { tsd, ttd })
.Join(_context.TradesOrganisations, tsdto => tsdto.ttd.DistributorId, to => to.Id,
(tsdto, to) => new { tsdto, to })
.LeftJoin(_context.TradesCountries, tsdc => tsdc.to.CountryId, tc => tc.Id,
(tsdc, tc) => new {tsdc, tc})
.Where(x => seriesIds.Contains(x.tsdc.tsdto.tsd.tt.SeriesId))
.Where(x => x.tsdc.tsdto.tsd.tt.FirstPartyId == null)
.Where(x => x.tsdc.tsdto.tsd.tt.Status != "closed")
.Where(x => x.tsdc.tsdto.tsd.tt.Status != "cancelled")
.GroupBy(n => new { n.tsdc.tsdto.tsd.tt.SeriesId, n.tsdc.tsdto.ttd.DistributorId })
.Select(g =>
new TopDistributors
{
SeriesId = g.Key.SeriesId,
DistributorName = g.Select(i => i.tsdc.to.Name).Distinct().First(),
IsinNickname = g.Select(i => i.tsdc.tsdto.tsd.ts.Nickname).Distinct().First(),
CountryName = g.Select(i => i.tc.Name).Distinct().First(),
CommissionTotal = Math.Ceiling(g.Sum(i => i.tsdc.tsdto.ttd.Commission))
}
)
.OrderByDescending(x => x.CommissionTotal)
.Take(5)
.ToList();
return result;
}
Here's the rather simple select statement that is taking orders or magnitude too long to convert to LINQ.
SELECT
trades_trades.series_id,
trades_organisations.`name`,
trades_series.nickname,
trades_countries.name as Country_Name,
SUM(trades_trade_distributors.commission) as Commission_Total
FROM
trades_trades
JOIN trades_series
ON trades_series.id = trades_trades.series_id
JOIN trades_trade_distributors
ON trades_trades.id = trades_trade_distributors.trade_id
JOIN trades_organisations
ON trades_trade_distributors.distributor_id = trades_organisations.id
LEFT JOIN trades_countries
ON trades_organisations.country_id = trades_countries.id
WHERE trades_trades.series_id IN (
17,
18)
AND trades_trades.first_party_id IS NULL
AND trades_trades.status <> 'closed'
AND trades_trades.status <> 'cancelled'
GROUP BY trades_trades.series_id, trades_trade_distributors.distributor_id
ORDER BY Commission_Total DESC
Following my recipe, here is a more or less straightforward translation of the SQL to LINQ. I moved the where to be near what it constrains, and used let to create a convenient name for the Sum, as LINQ doesn't allow you to forward reference anonymous object members.
var ans = from tt in trades_trades
where new[] { 17, 18 }.Contains(tt.series_id) && tt.first_party_id == null &&
tt.status != "closed" && tt.status != "cancelled"
join ts in trades_series on tt.series_id equals ts.id
join ttd in trades_trade_distributors on tt.id equals ttd.trade_id
join to in trades_orginizations on ttd.distributor_id equals to.id
join tc in trades_countries on to.country_id equals tc.id into tcj
from tc in tcj.DefaultIfEmpty() // GroupJoin -> left join
group new { tt, ts, ttd, to, tc } by new { tt.series_id, ttd.distributor_id } into tradeg
let Commission_Total = tradeg.Sum(trade => trade.ttd.commission)
orderby Commission_Total descending
select new {
tradeg.Key.series_id,
tradeg.First().to.name,
tradeg.First().ts.nickname,
Country_Name = tradeg.First().tc == null ? null : tradeg.First().tc.name,
Commission_Total
};
I am joining 4 tables (Productos, ProductosFotos, Marca, Categoria) to find the product by CategoriaId. The ProductoFotos table contains 3 images but I only need to show 1 of them. What do I need to do and not repeat the result for each image.
Here is my code:
var productoInCategoriaInDb = _productoRepository.GetAll()
.Where(x => x.Publicado == true && x.CategoriaId == id)
.GroupJoin(
_productoFotoRepository.GetAll(), <-- here take only one image
p => p.ProductoId,
pf => pf.ProductoId,
(p, pf) => new { p, pf })
.SelectMany(
p => p.pf.DefaultIfEmpty(),
(p, pf) => new { p.p, pf })
.GroupJoin(
_marcaRepository.GetAll(),
ppf => ppf.p.MarcaId,
pm => pm.MarcaId,
(ppf, pm) => new { ppf, pm })
.SelectMany(
ppfpm => ppfpm.pm.DefaultIfEmpty(),
(ppfpm, pm) => new { ppfpm.ppf, pm })
.GroupJoin(
_categoriaRepository.GetAll(),
ppfpm => ppfpm.ppf.p.CategoriaId,
pc => pc.CategoriaId,
(ppfpm, pc) => new { ppfpm, pc })
.SelectMany(
ppfpmpc => ppfpmpc.pc.DefaultIfEmpty(),
(ppfpmpc, pc) => new ProductoBuscarViewModel
{
Producto = ppfpmpc.ppfpm.ppf.p,
ProductoFoto = ppfpmpc.ppfpm.ppf.pf,
Marca = ppfpmpc.ppfpm.pm,
Categoria = pc
}
);
Update
Relational contraints diagram
This will work:
var productoInCategoriaInDb = from prod in _productoRepository.GetAll()
join imag in _productoFotoRepository.GetAll()
on p.ProductoId equals imag.ProductoId into imags
from imag in imags.DefaultIfEmpty()
join marca in _marcaRepository.GetAll()
on prod.MarcaId equals marca.MarcaId into marcas
from marca in marcas.DefaultIfEmpty()
join cat in _categoriaRepository.GetAll()
on cat.CategoriaId equals prod.CategoriaId into cats
from cat in cats.DefaultIfEmpty()
group new { prod, imag, marca, cat } by new { P = prod, M = marca, C = cat } into prodGroup
select new ProductoBuscarViewModel(){
Producto = prodGroup.Key.P
ProductoFoto = prodGroup.FirstOfDefault().imag
Marca = prodGroup.Key.M
Categoria = prodGroup.Key.C
}
This will generate a subselect with top(1) to get the first image of all with the same product. I proved it in LinqPad with similar entities. I usually put a bit field in the ProductoFoto entity to set the 'default' image of a Proveedor. Then filter the _productoFotoRepository.GetAll() to get only the default image. With that you dont need to call to a group by and the query will execute faster.
I'm trying to convert my sql query to linq, i confused about sum and grouping,
this is my query
SELECT
produk.supplier,
SUM(transaksi.jumlah_transaksi),
SUM(transaksi.nominal_transaksi),
operasional.nominal
FROM
transaksi INNER JOIN produk ON transaksi.id_produk = produk.id_produk
LEFT JOIN
(SELECT
operasional.id_supplier,
SUM(nominal) AS nominal
FROM
operasional) operasional
ON operasional.id_supplier = produk.id_supplier
GROUP BY produk.supplier
output should be
like this
Progress
i am just trying with linq query like this without grouping
var result = from t in db.transaksi
join p in db.produk on t.id_produk equals p.id_produk
from op in
(
from o in db.operasional
select new
{
id_supplier = o.id_supplier,
nominal = o.nominal
}
).Where(o => o.id_supplier == p.id_supplier).DefaultIfEmpty()
select new
{
nama_supplier = p.supplier,
jumlah_transaksi = t.jumlah_transaksi,
nominal_transaksi = t.nominal_transaksi,
biaya_operasional = op.nominal
};
and result query from my linq still like this
SELECT
`p`.`supplier`,
`t`.`jumlah_transaksi`,
`t`.`nominal_transaksi`,
`t1`.`nominal`
FROM
`transaksi` `t`
INNER JOIN `produk` `p`
ON `t`.`id_produk` = `p`.`id_produk`
LEFT JOIN `operasional` `t1`
ON `t1`.`id_supplier` = `p`.`id_supplier`
Solved
and this is my full linq
var result = from t in db.transaksi
join p in db.produk on t.id_produk equals p.id_produk
from op in
(
from o in db.operasional
group o by o.id_supplier into g
select new
{
id_supplier = g.First().id_supplier,
nominal = g.Sum(o => o.nominal)
}
).Where(o => o.id_supplier == p.id_supplier).DefaultIfEmpty()
select new
{
nama_supplier = p.supplier,
jumlah_transaksi = t.jumlah_transaksi,
nominal_transaksi = t.nominal_transaksi,
biaya_operasional = op.nominal
};
var grouped = result
.GroupBy(x => x.nama_supplier)
.Select(x => new
{
nama_supplier = x.Key,
jumlah_transaksi = x.Sum(s => s.jumlah_transaksi),
nominal_transaksi = x.Sum(s => s.nominal_transaksi),
biaya_operasional = x.Select(s => s.biaya_operasional).First()
});
Try to use GroupBy (in following code result is your query from code above):
var grouped = result
.GroupBy(x => x.nama_supplier)
.Select(x => new {
nama_supplier = x.Key,
sum1 = x.Sum(s => s.jumlah_transaksi),
sum1 = x.Sum(s => s.nominal_transaksi),
nominal = x.Select(s => s.biaya_operasional).First()
})
Code is not checked so use it just as idea.
I have a query like below. I want to group my values by "RapId"
Result must be come like this:
RaporId 1, List of UserId 15,24,23
RaporId 2, List of UserId 18,45,57
var sorgu = (from ra in Model1
join me in Model2
on ra.RapId equals me.RapId
select new
{
RapId = ra.RapId,
UserId= ra.RaportorId,
})
.GroupBy(x=>x.RapId )
.SelectMany(x => x)
.ToList();
var results = sorgu.GroupBy(p => p.RapId , p => p.UserId,
(key, g) => new { RapId = key, UserId= g.ToList() });
I get an error like this
> Error 39 Cannot convert lambda expression to type
> 'System.Collections.Generic.IEqualityComparer<AnonymousType#1>'
> because it is not a delegate type
What's wrong with this query?
Compiler thinks you are trying to use this overload: But you are passing a lambda expressions instead of IEqualityComparer.I think you just need to remove p => p.UserId :
var results = sorgu.GroupBy(p => p.RapId,
(key, g) => new { RapId = key, UserId= g.ToList() });