C# LINQ join - use range variable in secondary join - c#

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;

Related

Inner join not working when use equal with %, What an alternative way to use it like like %

I have Medals class, I call a service that return all Medal class fields except for the two fields ArabicName and ISOCode; Which I have to bring them from another table class "CountrysTBLs" , I made this join code:
The Medal class:
public class Medals {
public int bronze_count { get; set; }
public string country_name { get; set; }
public int gold_count { get; set; }
public string id { get; set; }
public int place { get; set; }
public int silver_count { get; set; }
public int total_count { get; set; }
public string ArabicName { get; set; } // this field not return by service
public string ISOCode { get; set; } // this field not return by service
}
The code:
var cntrs = db.CountrysTBLs.ToList();
List<Medals> objs = call_Service_Of_Medals_Without_ISOCode();
IEnumerable<Medals> query = from obj in objs
join cntry in cntrs on obj.country_name equals '%' + cntry.CommonName + '%'
select new Medals
{
ArabicName = cntry.ArabicName,
ISOCode = cntry.ISOCode,
country_name = obj.country_name,
place = obj.place,
gold_count = obj.gold_count,
silver_count = obj.silver_count,
bronze_count = obj.bronze_count,
total_count = obj.total_count
};
I get no result?!
How to fix that? Is there is any way to bring the two fields (ISOCode, ArabicName) without even use the inner join, and in same time best performance?
You want something like this to achieve LIKE functionality
List<Medals> medals = new List<Medals>();
var list = medals.Select(x => new
{
obj = x,
country = countries.FirstOrDefault(c => c.CommonName.Contains(x.country_name))
});
or something like this (if you want to just enrich each medal)
foreach (var medal in medals)
{
var country = countries.FirstOrDefault(c => c.CommonName.Contains(x.country_name));
medal.ISOCode = country.ISOCode;
medal.ArabicName = country.ArabicName;
}
Do note that this is not as performant as a Dictionary<string,Coutnry> of countries where the key is the country name, but as you need a LIKE comparison you would need to bring in a custom data structure such as Lucene index for fast lookups. But check first, if the lists are small enough, it probably won't be a problem. Otherwise, why not make the Medal.Country_Name and Country.Name the same? So you can do quick Dictionary (hashtable) lookups

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

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();

Unknown column in field list - After MySQL changes

I have a query which pulls some data after making a couple of joins, this worked fine when the application used SQL Server. However after making the transfer to MySQL I'm having some issues.
For example I keep getting the error 'Unknown column Extent.Group_ClientID'. I have identified the line at which this error occurs at but I don't understand why.
Entity:
[Table("tblsupplier")]
public partial class Supplier
{
[Key][Column(Order = 0)]
public int ClientID { get; set; }
[Key][Column(Order = 1)]
public int SupplierID { get; set; }
[StringLength(50)]
public string AccountNo { get; set; }
[StringLength(100)]
public string SupplierName { get; set; }
public string DisplayName {
get {
return this.SupplierName + " (" + this.AccountNo + ")";
}
}
public virtual Client tblClient { get; set; }
}
Query:
public IQueryable<Supplier> GetAllSuppliersByClientWithClaims(int ClientID, List<int> WrittenOffIDs) {
return (from s in alliance.Suppliers
where s.ClientID == ClientID
join h in alliance.Headers
on new { a = s.ClientID, b = s.SupplierID }
equals new { a = h.ClientID, b = h.SupplierID }
join d in alliance.Details
on new { h.ClientID, h.ClaimID }
equals new { d.ClientID, d.ClaimID }
join r in alliance.Reviews
on new { h.ClientID, h.ReviewID }
equals new { r.ClientID, r.ReviewID }
where r.ReviewPeriodID != 0
where d.SplitLine == false
where !WrittenOffIDs.Contains((int)d.WrittenOffID)
select s).Distinct().OrderBy(r => r.SupplierName);
}
Method:
public string GetSupplierAutoComplete(int ClientID) {
DashboardViewModel model = new DashboardViewModel();
GeneralMethods GeneralHelpers = new GeneralMethods(reviewPeriodRepo, supplierGroupRepo, detailRepo);
model.Suppliers = supplierRepo.GetAllSuppliersByClientWithClaims(ClientID, GeneralHelpers.GetWrittenOffCodes(ClientID));
//Fails here
return JsonConvert.SerializeObject(model.Suppliers.Select(r => r.DisplayName), Formatting.Indented);
}
However, I have done some playing around and I've found that one of the where's in the query is causing this issue. where d.SplitLine == false. Now in the database SplitLine is a Tinyint. As suggested because this is the boolean type for MySQL. Now if I pull a single 'SplitLine', it will return true or false based on the 0 or 1. Whereas if I use it in a where statement, it fails. Why it this?
UPDATE:
This only seems to happen when I enumerate the list
Found the answer! I'm not entirely sure why the error was occurring! Anyway..
I have two entities:
Header and Group.
I have a foreign key in my header entity which create a link with group. It said that one header can have one group. Which after I reviewed, was incorrect. One header can have many groups. So I changed the foreign key from this:
public virtual Group Group { get; set; }
To:
public ICollection<Group> Groups {get; set; }
This then worked. However, I haven't made a reference to group so I'm not sure to why this threw an error. If anyone knows, please let me know in the comments.

Dapper: mapping hierarchy and single different property

I really love Dapper's simplicity and possibilities. I would like to use Dapper to solve common challenges I face on a day-to-day basis. These are described below.
Here is my simple model.
public class OrderItem {
public long Id { get; set; }
public Item Item { get; set; }
public Vendor Vendor { get; set; }
public Money PurchasePrice { get; set; }
public Money SellingPrice { get; set; }
}
public class Item
{
public long Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Title { get; set; }
public long? CategoryId { get; set; }
}
public class Vendor
{
public long Id { get; set; }
public string Title { get; set; }
public Money Balance { get; set; }
public string SyncValue { get; set; }
}
public struct Money
{
public string Currency { get; set; }
public double Amount { get; set; }
}
Two challenges have been stumping me.
Question 1:
Should I always create a DTO with mapping logic between DTO-Entity in cases when I have a single property difference or simple enum/struct mapping?
For example: There is my Vendor entity, that has Balance property as a struct (otherwise it could be Enum). I haven't found anything better than that solution:
public async Task<Vendor> Load(long id) {
const string query = #"
select * from [dbo].[Vendor] where [Id] = #id
";
var row = (await this._db.QueryAsync<LoadVendorRow>(query, new {id})).FirstOrDefault();
if (row == null) {
return null;
}
return row.Map();
}
In this method I have 2 overhead code:
1. I have to create LoadVendorRow as DTO object;
2. I have to write my own mapping between LoadVendorRow and Vendor:
public static class VendorMapper {
public static Vendor Map(this LoadVendorRow row) {
return new Vendor {
Id = row.Id,
Title = row.Title,
Balance = new Money() {Amount = row.Balance, Currency = "RUR"},
SyncValue = row.SyncValue
};
}
}
Perhaps you might suggest that I have to store amount & currency together and retrieve it like _db.QueryAsync<Vendor, Money, Vendor>(...)- Perhaps, you are right. In that case, what should I do if I need to store/retrive Enum (OrderStatus property)?
var order = new Order
{
Id = row.Id,
ExternalOrderId = row.ExternalOrderId,
CustomerFullName = row.CustomerFullName,
CustomerAddress = row.CustomerAddress,
CustomerPhone = row.CustomerPhone,
Note = row.Note,
CreatedAtUtc = row.CreatedAtUtc,
DeliveryPrice = row.DeliveryPrice.ToMoney(),
OrderStatus = EnumExtensions.ParseEnum<OrderStatus>(row.OrderStatus)
};
Could I make this work without my own implementations and save time?
Question 2:
What should I do if I'd like to restore data to entities which are slightly more complex than simple single level DTO? OrderItem is beautiful example. This is the technique I am using to retrieve it right now:
public async Task<IList<OrderItem>> Load(long orderId) {
const string query = #"
select [oi].*,
[i].*,
[v].*,
[c].*
from [dbo].[OrderItem] [oi]
join [dbo].[Item] [i]
on [oi].[ItemId] = [i].[Id]
join [dbo].[Category] [c]
on [i].[CategoryId] = [c].[Id]
join [dbo].[Vendor] [v]
on [oi].[VendorId] = [v].[Id]
where [oi].[OrderId] = #orderId
";
var rows = (await this._db.QueryAsync<LoadOrderItemRow, LoadItemRow, LoadVendorRow, LoadCategoryRow, OrderItem>(query, this.Map, new { orderId }));
return rows.ToList();
}
As you can see, my question 1 problem forces me write custom mappers and DTO for every entity in the hierarchy. That's my mapper:
private OrderItem Map(LoadOrderItemRow row, LoadItemRow item, LoadVendorRow vendor, LoadCategoryRow category) {
return new OrderItem {
Id = row.Id,
Item = item.Map(category),
Vendor = vendor.Map(),
PurchasePrice = row.PurchasePrice.ToMoney(),
SellingPrice = row.SellingPrice.ToMoney()
};
}
There are lots of mappers that I'd like to eliminate to prevent unnecessary work.
Is there a clean way to retrive & map Order
entity with relative properties like Vendor, Item, Category etc)
You are not showing your Order entity but I'll take your OrderItem as an example and show you that you don't need a mapping tool for the specific problem (as quoted). You can retrieve the OrderItems along with the Item and Vendor info of each by doing the following:
var sql = #"
select oi.*, i.*, v.*
from OrderItem
inner join Item i on i.Id = oi.ItemId
left join Vendor v on v.Id = oi.VendorId
left join Category c on c.Id = i.CategoryId";
var items = connection.Query<OrderItem, Item, Vendor, Category, OrderItem>(sql,
(oi,i,v,c)=>
{
oi.Item=i;oi.Item.Category=c;oi.Vendor=v;
oi.Vendor.Balance = new Money { Amount = v.Amount, Currency = v.Currency};
return oi;
});
NOTE: The use of left join and adjust it accordingly based on your table structure.
I'm not sure I understand your question a 100%. And the fact that no one has attempted to answer it yet, leads me to believe that I'm not alone when I say it might be a little confusing.
You mention that you love Dapper's functionality, but I don't see you using it in your examples. Is it that you want to develop an alternative to Dapper? Or that you don't know how to use Dapper in your code?
In any case, here's a link to Dapper's code base for your review:
https://github.com/StackExchange/dapper-dot-net
Hoping that you'd be able to clarify your questions, I'm looking forward to your reply.

Linq Query with join on subquery

Basically I'm trying to write a query where it joins on select top 1 from a second table so something like:
SELECT Sum(pinfo.quantity + p.itemcount),
i.owner
FROM invoice i
JOIN purchase_info pinfo
ON pinfo.invoice = i.invid
JOIN (SELECT DISTINCT sku,
productlineid,
itemcount
FROM products WHERE productlineid in (13, 14)) p
ON p.sku = pinfo.item
WHERE i.owner = 22623
GROUP BY i.owner
Here's my pathetic attempt in linq that has somewhat invalid syntax, any ideas would be much appreciated.
(from i in _invoiceRepository.Table
join pi in _purchaseInfoRepository.Table on i.InvoiceId equals pi.InvoiceId
join p in (from p2 in _productRepository.Table where p2.Sku == pi.Item select new { p2.Sku, p2.ItemCount }).Take(1)
on pi.Item equals p.Sku
where i.MemberId == memberId &&
(p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
select pi.Quantity * p.ItemCount)
.DefaultIfEmpty(0)
.Sum();
Here is my first stab at this.
From the sql, it looks like you want to find how many Ink and Toner Cartridges a particular customer has ordered from you ever.
This should give you the same results as the sql (this is depending on the order of the Products table since we are taking the top 1 without some sort of ordering being done:
var count = from i in _invoiceRepository.Table
where i.OwnerId == memberId
select new
{
OwnerId = i.OwnerId,
TotalProductCount = i.Purchases.Sum(pro => pro.Products
.Where(p => p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
.Take(1)
.Sum(p => p.ItemCount * pro.Quantity))
};
Since I did not know the the classes of the three objects (Invoice, PurchaseInfo, and Product) I made a guess at what they are:
Invoice Class: I assume it has a list/collection of PurchaseInfos
public class Invoice
{
public int Id { get; set; }
public int OwnerId { get; set; }
public List<PurchaseInfo> Purchases { get; set; }
}
PurchaseInfos: An invoice has multiple PurchaseInfos, each one links to (ideally) one product but since the SKU is not unique I assome that this has a list/collection of Products in it.
public class PurchaseInfo
{
public int Id { get; set; }
public int Quantity { get; set; }
public int InvoiceId { get; set; }
public Invoice Invoice { get; set; }
public int Item {get;set;}
public List<Product> Products { get; set; }
}
Product Class: I assome that there is an Id field (not shown) or a composite primary key somewhere
public class Product
{
public int Sku { get; set; }
public int ProductLineId { get; set; }
public int ItemCount { get; set; }
public List<PurchaseInfo> PurchaseInfos { get; set; }
}
Hopefully you can take this a get what you need. If this is way off, please update question with the class definitions (you can remove unneeded properities if you want) so a better answer can be produced.

Categories