C# LINQ join with conditional where clause on two different data sets - c#

I have two collections that I am comparing data against. I can join the two collections by ids. I need to have a where clause that returns a list of data where certain items in collection B were not found in collection A
public class Contract
{
public string ContractId { get; set; }
public IList InvoiceList { get; set; }
public class Invoice
{
public int InvoiceNumber { get; set; }
}
}
public class PaymentRequest
{
public IList Contracts { get; set; }
public class ContractList
{
public string ContractId { get; set; }
public IList Invoices { get; set; }
}
public class InvoiceList
{
public int InvoiceNumber { get; set; }
}
}
I have the following so far but can't quite get the where clause down.
var query = (
from request in (
from contract in paymentRequest.Contracts
from invoice in contract.Invoices
select new { contract, invoice })
join valid in (
from contract in validContracts
select new { contract })
on new { request.contract.ContractId } equals new { valid.contract.ContractId }
where !(
// get a list of invoice numbers in the request data set that are not in the valid data set
)
select "Contract Id: " + request.contract.ContractId +
", Invoice Number: " + request.invoice.InvoiceNumber
).ToList();
Any help is appreciated.

var collectionAIds = new HashSet<int>(collectionA.Select(colAItem => colAItem.Id));
var itemsInCollectionBNotInCollectionA = collectionB.Where(colBItem =>
!collectionAIds.Contains(colBItem.Id));
basically, we want to get the Ids in collection A, and then select all items from collection B, not in A's list of ids.
A HashSet is optional. it just avoids repetitive O(n) lookups, if you don't use that variable.
p.s. i am assuming int, as the type of id.. use the data type of id for the Hashset.

Using contains gets you less performance than something that reminds a regular Sql query. This will be better
var result =
from itm1 in Coll1
from itm2 in Coll2.Where(x => x.Id == itm1.Id).DefaultIfEmpty()
where itm2 == null
select itm1;
This will give you all items in Coll1 that don't exist in Coll2. And this is gonna be faster than using Contains any time

It seems to me that your query should look like this:
var query =
(
from request in
(
from contract in paymentRequest.Contracts
from invoice in contract.Invoices
select new { contract, invoice }
)
join valid in
(
from contract in validContracts
select new { contract }
) on new { request.contract.ContractId } equals new { valid.contract.ContractId } into gvs
where
gvs
.SelectMany(gv => gv.contract.Invoices)
.Select(i => i.InvoiceNumber)
.All(n => n != request.invoice.InvoiceNumber)
select "Contract Id: " + request.contract.ContractId +
", Invoice Number: " + request.invoice.InvoiceNumber
).ToList();

Related

Return non-aggregates along with aggregates in Linq select

I am trying to convert the basic SQL query into Linq.
The aggregates are returning fine, but the non-aggregates are not.
SeriesId, Amount, and NumberOfTrades are perfect.
Code and Isin aren't causing errors, but they're returning the type rather than the data.
The query I'm trying to convert:
SELECT
trans_series.id,
trans_series.number,
trans_series.isin,
SUM( trans_trades.amount ),
COUNT( trans.series_id )
FROM
trans_trades
INNER JOIN trans_series ON trans_series.id = trans_trades.series_id
WHERE
trans_trades.series_id IN (
17,
18)
AND trans_trades.first_party_id IS NULL
AND trans_trades.status <> 'closed'
AND trans_trades.status <> 'cancelled'
GROUP BY trans_trades.series_id
The method:
public List<TotalByIsinViewModel> GetIssuerSeries()
{
_context = new MySQLDatabaseContext();
var result = (from ts in _context.TradesSeries
join tts in _context.TradesTrades
on ts.Id equals tts.SeriesId
where myInClause.Contains(tts.SeriesId)
group new { ts, tts } by new { tts.SeriesId } into g
select new TotalByIsinViewModel
{
SeriesId = g.Key.SeriesId,
Code = g.Select(i => i.tts.Number).Distinct().ToString(),
Isin = g.Select(i => i.ts.Isin).Distinct().ToString(),
Amount = (decimal?)g.Sum(pt => pt.tts.Amount),
NumberOfTrades = g.Count()
}).ToList();
return result;
}
The view model:
public class TotalByIsinViewModel
{
public int SeriesId { get; set; }
public string Code { get; set; }
public string Isin { get; set; }
public decimal? Amount { get; set; }
public int NumberOfTrades { get; set; }
}
I'm expecting the actual distinct varchar values from the "number" and "isin" columns, but I'm getting type data returned in my Razor page's cs Onget.
What you need is:
Code = g.First().tts.Number,
Isin = g.First()ts.Isin,
And the reason is in g.Select(i => i.tts.Number) you select a list of values.
Maybe you have only one value in list but C# still sees a list.
And ToString method for arrays is inherited from Object class. So it prints type name.
Fixed this problem by changing the "Isin" and "Code" to this:
Code = g.Select(i => i.ts.Number).Distinct().First(),
Isin = g.Select(i => i.ts.Isin).Distinct().First(),

C# LINQ join - use range variable in secondary join

I am trying to use some linq magic to query over two different data sets to compare an inner property on one data set to see if that value is greater than a property in the other data set. I've generalized the class layouts and removed some properties to hopefully make it clearer.
public class Contract
{
public string ContractId { get; set; }
public IList<Invoice> InvoiceList { get; set; }
public class Invoice
{
public int InvoiceNumber { get; set; }
public decimal CurrentDueAmount { get; set; }
}
}
public class PaymentRequest
{
public IList<ContractList> Contracts { get; set; }
public class ContractList
{
public string ContractId { get; set; }
public IList<InvoiceList> Invoices { get; set; }
}
public class InvoiceList
{
public decimal CurrentDueAmount { get; set; }
}
}
I know the following LINQ statement doesn't work but its the general idea on what I'm trying to achieve.
from validContract in validContracts
join contract in paymentRequest.Contracts
on validContract.ContractId equals contract.ContractId
from validInvoice in validContract.InvoiceList
join invoice in contract.Invoices
on validInvoice.InvoiceNumber equals invoice.InvoiceNumber
where invoice.CurrentDueAmount > validInvoice.CurrentDueAmount
select "Invoice Number: " + invoice.InvoiceNumber + ", Current Due >= " + validInvoice.CurrentDueAmount;
I get an error trying to use the range variable "contract" in the second join statement.
I'd like to be able to do the comparison on the objects currentDueAmounts. Any ideas / refactoring, method extractions, etc. would be greatly appreciated.
You need to separate the two sets before joining them like this
var query =
from valid in (
from contract in validContracts
from invoice in contract.InvoiceList
select new { contract, invoice }
)
join request in (
from contract in paymentRequest.Contracts
from invoice in contract.Invoices
select new { contract, invoice }
)
on new { valid.contract.ContractId, valid.invoice.InvoiceNumber }
equals new { request.contract.ContractId, request.invoice.InvoiceNumber }
where request.invoice.CurrentDueAmount > valid.invoice.CurrentDueAmount
select "Invoice Number: " + request.invoice.InvoiceNumber + ", Current Due >= " + valid.invoice.CurrentDueAmount;

Return non-anonymous list entity framework - c#

I have 2 tables in the database :
Table: Order (item_id)
Table: Item ( item_id)
When I'm doing the inner join in entity framework, as you can see below, I need to return in one list the result to manipulate this. Usually when I do the select in one single table , I return a LIST from the entity with the tables name, but I dont know how can I return a LIST when I have 2 or more entity , I mean, using inner join, I would like to return a List that I can manipulate in other class. When I use for only one entity, it is perfect and easy.
public List<????????> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new
{
a.order_numer,
a.total,
b.item_code,
b.item_qty
});
return _result;
}
I don't know how to return it !! I tried to use the .TOLIST(), but still coming "anonymous".
Thank you
First you need to create a custom type like
public class OrderItems
{
public int Order_numer { get; set; }
public int Total { get; set; }
public string Item_code { get; set; }
public int Item_qty { get; set; }
}
After then modify your function like
public List<OrderItems> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new OrderItems()
{
Order_numer= a.order_numer,
Total= a.total,
Item_code=b.item_code,
Item_qty=b.item_qty
}).ToList();
return _result;
}
I hope it will work for you.
You can create a compound model that has a property representing each entity.
public class CompoundModel
{
public Entities.Order { get; set; }
public Entities.Item { get; set; }
}
public List<CompoundModel> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new CompoundModel
{
Order = a
Item = b
});
return _result;
}
Alternatively, if you want to flatten your structure, you can create a class that only has four properties.
public class CompoundModel
{
public string OrderNumber { get; set; }
public int Total { get; set; }
public string ItemCode { get; set; }
public int ItemQuantity { get; set }
}
public List<CompoundModel> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new CompoundModel
{
OrderNumber = a.order_number,
Total = a.total,
ItemCode = b.item_code,
ItemQuantity = b.item_qty
});
return _result;
}
The problem with your code is this part:
select new // This will create an anonymous type
{
a.order_numer,
a.total,
b.item_code,
b.item_qty
}
As the select generates an anonymous type you will get a list of theses anonymous types as a result of the query. In order to get a list typed results, you need to specify the type in the select-clause:
select new TypeYouWantToReturn() // This will create an real type
{
PropA = a.order_numer, // You also need to specify the properties
PropB = a.total, // of the class that you want to assign
PropC = b.item_code, // the resulting values of the query.
PropD = b.item_qty
}
Now the result of the query will return a list of real types. You need to finally call .ToList() so you get a list instead of the IEnumerable that the select statement will return.

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

Entity Framework LINQ Get all items part of another collection

Get all the NWatchRelation records from the DBContext that overlap those in the relationsCollection.
The same Id, RelatedNodeId, and RelationType (enum: int) should be what's considered a match.
public class NWatchRelation : INWatchRelation
{
public int Id { get; set; }
public int NodeId { get; set; }
public NWatchNode Node { get; set; }
public int RelatedNodeId { get; set; }
public NWatchNode RelatedNode { get; set; }
public NWatch.NWatchRelationType RelationType { get; set; }
}
INWatchRelation[] relationsCollection = GetRelations();
You can do a LINQ join between these 2 collections.
var result = from a in db.NWatchRelations.AsEnumerable()
join b in relationsCollection on a.RelatedNodeId equals b.RelatedNodeId
&& a.Id equals b.Id
&& a.RelationType equals b.RelationType
select a;
The only way you can do that fully in LINQ to Entities is to manually compose UNION ALL query by using Queryable.Concat like this:
IQueryable<NWatchRelation> query = null;
foreach (var relation in relationsCollection)
{
var m = relation;
var subQuery = db.NWatchRelations
.Where(r => r.Id == m.Id
&& r.RelatedNodeId == m.RelatedNodeId
&& r.RelationType == m.RelationType);
query = query == null ? subQuery : query.Concat(subQuery);
}
But please note that it's a limited approach and will not work if the relationsCollection is big.
You could create a kind of unique key using the three values:
//To create a unique key (an string, which is a primitive type) combining the three values
var keys=relationsCollection.Select(e=>e.Id+"-"+e.RelatedNodeId+"-"+ ((int)e.RelationType)).Distinct();
var query=db.NWatchRelations.Where(r=>keys.Any(k=>k == (SqlFunctions.StringConvert((double)r.Id)+"-"+
SqlFunctions.StringConvert((double)r.RelatedNodeId )+"-"+
SqlFunctions.StringConvert((double)((int)r.RelationType)) ));
If your NWatchRelations table doesn't have many rows or relationsCollection is a small collection, please, use one of the alternatives that were proposed earlier at your convinience.
Also you can have the directly linked like this
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public NWatchRelation()
{
this.INWatchRelation = new HashSet<INWatchRelation>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<INWatchRelation> INWatchRelation { get; set; }
But the entiry relation must be liked like this in order to work properly
Then you could select/list it like this
db.NWatchRelation.INWatchRelation.ToList();

Categories