Linq2Entities query to find records given mix-max range - c#

I have those two entities:
public class Ticket
{
public int Id { get; set; }
public int ScheduleId { get; set; }
public int SeatId { get; set; }
[DataType(DataType.Date)]
[Column(TypeName = "Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ForDate { get; set; }
public Seat Seat { get; set; }
public Schedule Schedule { get; set; }
public ICollection<TicketStop> TicketStops { get; set; }
}
public class TicketStop
{
public int Id { get; set; }
public int TicketId { get; set; }
public int LineStopStationId { get; set; }
public Ticket Ticket { get; set; }
public LineStopStation LineStopStation { get; set; }
}
public class LineStopStation
{
public int Id { get; set; }
public int LineId { get; set; }
public int StopId { get; set; }
public int Order { get; set; }
public bool IsLastStop { get; set; }
public Line Line { get; set; }
public Stop Stop { get; set; }
}
The business case is that I am implementing a bus ticket reservation system (for learning purposes mostly) and I want to find overlapping tickets.
The combination of LineId + ScheduleId + ForDate identifies uniquely that that a ticket is for certain bus at a certain date and departure time.
The problem I have is to identify, given starting and end location whether or not two tickets overlap for one or more stops.
The LineStopStation entity holds information about the StopId and the Order in which it is visited during the bus trip. So basically, overlapping tickets will have same Order number at some point (unless it is the last stop, which means that the passenger is leaving the bus).
So what I have is LineId, ScheduleId, StartId and EndId where starId and endId correspond to LineStopStation.StopId so eventually I am able to get the Order out of them like this.
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
So I am pretty convinced that having all this information I should be able to find if in TicketStop table I have ticket which overlaps with the data in question. TicketStop works this way - if someone bought a ticket for 3 stops I will have three records there with the same TicketId and three different LineStopStationId.
I feel that this question got a bigger than needed. So basically I have this:
public List<Seat> GetAvailableSeats(int lineId, int scheduleId, int startId, int endId, DateTime forDate)
{
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId)
.Where(t => t.Ticket.ForDate == forDate)
//basically I don't know how to proceed here.
//In pseudo code it should be something like:
.Where(t => t.Min(Order) >= endStationOrder || t.Max(Order) <= startStationOrder
}
But obs. this is not how LINQ works. So how can I find all tickets where this range overlaps?

Without an in-depth analysis of your model, perhaps something like this could give you an idea?
var reservedSeats = _context.TicketStops
.GroupBy(t => new { t.Ticket.ScheduleId, t.Ticket.ForDate })
.Where(tg => tg.Key == new { ScheduleId = scheduleId, ForDate = forDate })
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
You could also filter first and do an empty GroupBy:
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
To return all the SeatIds, you just need to select them from the group.
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
.SelectMany(tg => tg.Select(t => t.Ticket.SeatId));

Related

C# Foreach on List is very slow

I have a foreach that goes through response, response is a list that can take up to 230,000 records, foreach response it will filter and sum a second list called _MaterialIssued, this list contains max 30,000 records, in the following if it will only enter if Issued is bigger than 0 which will occur only about 15% of the time, next it will try to get the itemOnhand from the _Onhand list that contains max 17,000 records, in the following if it will go in about 85% of the time, when I wrote the code inside this block is where the performance dropped dramatically, in this if I will go back and filter response for all the child items and loop through them changing the _onhand list and the response list!
To go through this foreach depends on the machine I use but takes from 45 to 75 minutes and I cannot find what line is my bottleneck or how I can improve performance for this code block.
foreach (var b in response)
{
var Issued = _MaterialIssued.Where(x => x.ItemId == b.ItemId && x.Job == b.Job).Sum(c => c.Qty);
if (Issued > 0)
{
var prctIssued = Issued / b.QtyDemand;
var childItems = response.Where(x => x.Job == b.Job && x.BomPath.StartsWith(b.BomPath));
foreach (var child in childItems)
{
child.QtyIssued = child.QtyDemand * prctIssued;
}
}
var itemOnhand = _OnHand.Where(x => x.ItemId == b.ItemId).FirstOrDefault();
if (itemOnhand.Onhand > 0)
{
decimal prctOnhand = 1;
var childItems = response.Where(x => x.Job == b.Job && x.BomPath.StartsWith(b.BomPath) && x.ItemId != b.ItemId && x.SiteRef == b.SiteRef && x.QtyIssued < x.QtyDemand);
var DemandWithIssued = b.QtyDemand - b.QtyIssued;
if (itemOnhand.Onhand < DemandWithIssued)
{
prctOnhand = itemOnhand.Onhand / DemandWithIssued;
}
itemOnhand.Onhand -= DemandWithIssued * prctOnhand;
foreach (var child in childItems)
{
child.QtyParentAvailable = (child.QtyDemand - child.QtyIssued) * prctOnhand;
}
}
}
The model for _OnHand is
private class ItemOnhand
{
public string ItemId { get; set; }
public string SiteRef { get; set; }
public decimal Onhand { get; set; }
}
The model for _MaterialIssued is
public class FiniteDemandBase
{
public string ItemId { get; set; }
public string Item { get; set; }
public string JobId { get; set; }
public string Job { get; set; }
public DateTime StartDate { get; set; }
public string SiteRef { get; set; }
public decimal Qty { get; set; }
}
The model for response is
public class FiniteDemand
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public string BomPath { get; set; }
public string SiteRef { get; set; }
public string Job { get; set; }
public string ItemId { get; set; }
public string JobId { get; set; }
public string ParentItemId { get; set; }
public decimal QtyPerUnit { get; set; }
public decimal QtyDemand { get; set; }
public decimal QtyOnhand { get; set; }
public decimal QtyWip { get; set; }
public decimal QtyOnhandRunning { get; set; }
public decimal QtyInSchedule { get; set; }
public decimal QtyIssued { get; set; }
public decimal QtyParentAvailable { get; set; }
public decimal QtyDemandNeeded { get; set; }
public decimal QtyDemandNeededRunning { get; set; }
}
I tried changing the _OnHand list to a HashSet but the performance was the same.
Let's break down your code into parts which contain an explicit of implicit iteration:
Iteration over response — n-times:
foreach (var b in response)
{
Nested iteration over _MaterialIssued — m-times:
var Issued = _MaterialIssued.Where(x => x.ItemId == b.ItemId && x.Job == b.Job).Sum(c => c.Qty);
Nested iteration over response (again) — n-times:
var childItems = response.Where(x => x.Job == b.Job && x.BomPath.StartsWith(b.BomPath));
Nested iteration over _OnHand — up to h-times:
var itemOnhand = _OnHand.Where(x => x.ItemId == b.ItemId).FirstOrDefault();
Nested iteration over response (again) — n-times:
var childItems = response.Where(x => x.Job == b.Job && x.BomPath.StartsWith(b.BomPath) && x.ItemId != b.ItemId && x.SiteRef == b.SiteRef && x.QtyIssued < x.QtyDemand);
Hence, the complexity of your algorithm is: O(n * (m + n + h + n)) = O(2*n^2 + n*(m+h))!
If the number of responses is 230,000, we are speaking about 52.9 billion iterations! No wonder it runs for ages :)
This sounds an awful lot like you're trying to manage a database. I would suggest you go all the way and do exactly that, and look into using something like MySQL instead.
E.g.:
Create a database and add tables in Visual Studio
Create a SQL Server Database programmatically by using ADO.NET and Visual C# .NET
Whilst, technically, a List can contain up to two billion elements, once you're above a few thousand you'll start to run into trouble. They aren't really meant for the sort of heavy data processing you're attempting. That is exactly what database engines are for.

How to merge object properties of the same class together via LINQ

Say I have a class, I want to select multiple objects of it but create one unified object in the end. This is because of the requirement for the collection properties of the object to be combined.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Nozomi.Base.Core;
namespace Nozomi.Data.Models.Currency
{
public class Currency : BaseEntityModel
{
public Currency(ICollection<Currency> currencies)
{
if (currencies.Any())
{
var firstCurr = currencies.FirstOrDefault();
if (firstCurr != null)
{
// Doesn't matter...
Id = firstCurr.Id;
CurrencyTypeId = firstCurr.Id;
CurrencyType = firstCurr.CurrencyType;
Abbrv = firstCurr.Abbrv;
Name = firstCurr.Name;
CurrencySourceId = firstCurr.CurrencySourceId;
CurrencySource = firstCurr.CurrencySource;
WalletTypeId = firstCurr.WalletTypeId;
PartialCurrencyPairs = currencies
.SelectMany(c => c.PartialCurrencyPairs)
.DefaultIfEmpty()
.ToList();
}
}
}
[Key]
public long Id { get; set; }
public long CurrencyTypeId { get; set; }
public CurrencyType CurrencyType { get; set; }
public string Abbrv { get; set; } // USD? MYR? IND?
public string Name { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// This will have a number if it is a crypto pair to peg to proper entities
public long WalletTypeId { get; set; } = 0;
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
return !String.IsNullOrEmpty(Abbrv) && !String.IsNullOrEmpty(Name) && CurrencyTypeId > 0 && CurrencySourceId > 0;
}
}
}
Here's what a PartialCurrencyPair is:
namespace Nozomi.Data.Models.Currency
{
/// <summary>
/// Partial currency pair.
/// </summary>
public class PartialCurrencyPair
{
public long CurrencyId { get; set; }
public long CurrencyPairId { get; set; }
public bool IsMain { get; set; } = false;
public CurrencyPair CurrencyPair { get; set; }
public Currency Currency { get; set; }
}
}
So basically, if you want to make EURUSD, you'll have to take two currencies to form a pair. A CurrencyPair is made up of two PartialCurrencyPairs. The reason why we can have many EUR or many USDs is that they come from different sources.
Here's what a CurrencyPair is:
public class CurrencyPair : BaseEntityModel
{
[Key]
public long Id { get; set; }
public CurrencyPairType CurrencyPairType { get; set; }
/// <summary>
/// Which CPC to rely on by default?
/// </summary>
public string DefaultComponent { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// =========== RELATIONS ============ //
public ICollection<CurrencyPairRequest> CurrencyPairRequests { get; set; }
public ICollection<WebsocketRequest> WebsocketRequests { get; set; }
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
var firstPair = PartialCurrencyPairs.First();
var lastPair = PartialCurrencyPairs.Last();
return (CurrencyPairType > 0) && (!string.IsNullOrEmpty(APIUrl))
&& (!string.IsNullOrEmpty(DefaultComponent))
&& (CurrencySourceId > 0)
&& (PartialCurrencyPairs.Count == 2)
&& (firstPair.CurrencyId != lastPair.CurrencyId)
&& (!firstPair.IsMain == lastPair.IsMain);
}
}
I have an IQueryable to combine into one single currency.
Code with comments (The comments basically tells you what I'm trying to achieve.
var query = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
// Something here that will join the PartialCurrencyPair collection together and create one single Currency object.
.SingleOrDefault();
How do I come about it? Thank you so much in forward! Here's the
progress I've made so far and it works, but I'm pretty LINQ has a beautiful way to make this better and optimised:
var combinedCurrency = new Currency(_unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Include(c => c.PartialCurrencyPairs)
.ThenInclude(pcp => pcp.CurrencyPair)
.ThenInclude(cp => cp.CurrencyPairRequests)
.ThenInclude(cpr => cpr.RequestComponents)
.ThenInclude(rc => rc.RequestComponentDatum)
.ThenInclude(rcd => rcd.RcdHistoricItems)
.ToList());
return new DetailedCurrencyResponse
{
Name = combinedCurrency.Name,
Abbreviation = combinedCurrency.Abbrv,
LastUpdated = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.OrderByDescending(rc => rc.ModifiedAt)
.FirstOrDefault()?
.ModifiedAt ?? DateTime.MinValue,
WeeklyAvgPrice = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc =>
rc.ComponentType.Equals(ComponentType.Ask) ||
rc.ComponentType.Equals(ComponentType.Bid)))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromDays(7))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Average(),
DailyVolume = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc => rc.ComponentType.Equals(ComponentType.VOLUME)
&& rc.DeletedAt == null && rc.IsEnabled))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromHours(24))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Sum(),
Historical = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.Where(rc => componentTypes != null
&& componentTypes.Any()
&& componentTypes.Contains(rc.ComponentType)
&& rc.RequestComponentDatum != null
&& rc.RequestComponentDatum.IsEnabled
&& rc.RequestComponentDatum.DeletedAt == null
&& rc.RequestComponentDatum.RcdHistoricItems
.Any(rcdhi => rcdhi.DeletedAt == null &&
rcdhi.IsEnabled))
.ToDictionary(rc => rc.ComponentType,
rc => rc.RequestComponentDatum
.RcdHistoricItems
.Select(rcdhi => new ComponentHistoricalDatum
{
CreatedAt = rcdhi.CreatedAt,
Value = rcdhi.Value
})
.ToList())
};
Here's the end result I want on that single object: A DetailedCurrencyResponse object.
public class DistinctiveCurrencyResponse
{
public string Name { get; set; }
public string Abbreviation { get; set; }
public DateTime LastUpdated { get; set; }
public decimal WeeklyAvgPrice { get; set; }
public decimal DailyVolume { get; set; }
}
A historical datum is basically a kvp, where the Key (ComponentType) is an enum.
public class DetailedCurrencyResponse : DistinctiveCurrencyResponse
{
public Dictionary<ComponentType, List<ComponentHistoricalDatum>> Historical { get; set; }
}
public class ComponentHistoricalDatum
{
public DateTime CreatedAt { get; set; }
public string Value { get; set; }
}
The query you have outlined will attempt to return you a single Currency object, but given you are looking for any with a given abbreviation, if multiple currency objects share an abbreviation, the SingleOrDefault could error due to multiple returns.
It sounds like you want to define a structure to represent the currency pairs. That structure is not a Currency entity, but a different data representation. These are commonly referred to as ViewModels or DTOs. Once you've defined what you want to return, you can use .Select() to populate that from the Currency and applicable abbreviations.
For instance, if I create a CurrencySummaryDto which will have the currency ID, Abbrevation, and a string containing all of the applicable pairs:
public class CurrencySummaryDto
{
public long CurrencyId { get; set; }
public string Abbreviation { get; set; }
public string Pairs { get; set;}
}
... then the query...
var currencySummary = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
.AsNoTracking()
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Select( c => new {
c.Id,
c.Abbrv,
Pairs = c.PartialCurrencyPairs.Select(pc => pc.PairName).ToList() // Get names of pairs, or select another annonymous type for multiple properties you care about...
}).ToList() // Alternatively, when intending for returning lots of data use Skip/Take for paginating or limiting resulting data.
.Select( c => new CurrencySummaryDto
{
CurrencyId = c.Id,
Abbreviation = c.Abbrv,
Pairs = string.Join(", ", c.Pairs)
}).SingleOrDefault();
This is if you want to do something like combine data from the currency pairs into something like a string. If you're happy to leave them as a collection of simplified data, then the extra anonymous type and .ToList() are not required, just select directly into the Dto structure. This example combines the data into a string where string.Join() is not supported in EF expressions so we have to get our data out into objects to hand over to Linq2Object for the final mapping.
Edit: Ok, you're requirement/example just got a whole lot more complicated with the pair structure, but you should be able to leverage this into the query rather than selecting the entire graph of entities by moving the selection of those values up into the main query... However...
Given the complexity of the data relationships, the approach I would recommend using since this would be assumed to be a read-only result, would be to construct a View in the database to flatten these averages and totals, then bind a simplified entity to this view rather than attempting to manage this with EF Linq. I believe it can be done with linq, but it will be quite onerous to look at, and a view-based summary entity would be a lot cleaner while keeping the execution of this logic to be executed in the database.

Entity Framework trying to use methods to create joins help needed

I could really use some help, I have the following SQL query that works and have been trying to convert it to Entity Framework to no avail. I am using the Methods rather than the other option, I think that is how you say it.
Anyway the SQL is
SELECT c.ACCOUNTID,
c.TOACCOUNTID,
fa.ACCOUNTNAME, ta.ACCOUNTNAME,
p.PAYEENAME
FROM checking AS c
LEFT JOIN
ACCOUNT AS fa ON c.ACCOUNTID = fa.ACCOUNTID
LEFT JOIN
ACCOUNT AS ta ON c.TOACCOUNTID = ta.ACCOUNTID
LEFT JOIN
PAYEE AS p ON c.PAYEEID = p.PAYEEID
WHERE c.ACCOUNTID == 1 OR
c.TOACCOUNTID == 1;
So far I have managed to get it this far.
var checking =
db.Transactions
.Where(item => item.ACCOUNTID == LookupAccount || item.TOACCOUNTID == LookupAccount)
.GroupJoin(db.PAYEE,
transaction => transaction.PAYEEID,
payee => payee.PAYEEID,
(check, payee) => new { Payee = payee }
).SelectMany(
transaction => transaction.Payee.DefaultIfEmpty(),
(transaction, payee) => new { Payee = payee })
.Select(item => new
{
ToAccount = item.ToAccount.AccountName
FromAccount = item.FromAccount.AccountName
Withdrawal = 0,
Deposit = 0,
Payee = item.Payee.PAYEENAME
}).ToList();
The issue I have now, is that I am not sure I understand how joins work in this manner, every time I try to get the 2 other joins in I end up falling flat on my face.
When I add this to the above code, the Payee section is out of whack and I don't understand why. I know it has to do with the select new {} section, I could really use some help understanding how this works.
.Join(db.ACCOUNT,
check => check.ACCOUNTID,
account => account.ACCOUNTID,
(check, fromaccount) => new { FromAccount = fromaccount }
)
Models
Transaction
public partial class Transaction
{
[Key]
public int Id { get; set; }
public int AccountID { get; set; }
public int? ToAccountId { get; set; }
public int PayeeId { get; set; }
[Column(TypeName = "numeric")]
public decimal TransAmount { get; set; }
[Column(TypeName = "numeric")]
public decimal ToTransAmount { get; set; }
public virtual Account Account { get; set; }
}
Account
public partial class Account
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Account()
{
Transaction = new HashSet<Transaction>();
}
[Key]
public int AccountId { get; set; }
[Required]
[StringLength(150)]
public string AccountName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Transaction> Transaction { get; set; }
}
This won't work as a comment, but it might get you further. You have 2 FK in your tx class so you need 2 navigation properties:
public partial class Transaction
{
public int Id { get; set; } // Key by convention
public int FromAccountID { get; set; }
public virtual Account FromAccount { get; set; }
public int? ToAccountId { get; set; }
public virtual Account ToAccount { get; set; }
public int PayeeId { get; set; }
public virtual Payee Payee { get; set; }
public decimal TransAmount { get; set; }
public decimal ToTransAmount { get; set; }
}
public partial class Account
{
public Account()
{
Transaction = new HashSet<Transaction>();
}
public int AccountId { get; set; } // Key by convention
[Required]
[StringLength(150)]
public string AccountName { get; set; }
[InverseProperty("FromAccount")]
public virtual ICollection<Transaction> TransactionsFrom { get; set; }
[InverseProperty("ToAccount")]
public virtual ICollection<Transaction> TransactionsTo { get; set; }
}
Now your query becomes:
var checking =
db.Transactions
.Include(tx => tx.Payee)
.Include(tx => tx.FromAccount)
.Include(tx => tx.ToAccount)
.Where(tx => tx.FromAccountId == lookupAccountId || tx.ToAccountId == lookupAccountId)
.Select(tx => new
{
ToAccountName = tx.ToAccount.AccountName
FromAccountName = tx.FromAccount.AccountName
Withdrawal = tx.ToTransAmount,
Deposit = tx.TransAmount,
PayeeName = tx.Payee.PAYEENAME
}).ToList();
https://coding.abel.nu/2012/06/dont-use-linqs-join-navigate/
I have created this as an answer, for any one else who may come across this and is learning the syntax.
The reason I wasn't getting it to work was really down to a lack of how EF actually works when joining.
Transactions
.Join(Accounts,
tr => tr.AccountId,
ac => ac.AccountId,
(tr, ac) => new { Transaction = tr, ac})
.GroupJoin(Accounts,
tr => tr.Transaction.ToAccountId,
ta => ta.AccountId,
(tr, ta) => new { Transaction = tr.Transaction, Account = ta, FromAccount = tr.ac})
.SelectMany(
transaction => transaction.Account.DefaultIfEmpty()
,(transaction, account) => new { tt = transaction.Transaction, ToAccount = account, FromAccount = transaction.FromAccount}
)
.GroupJoin(Payees,
tr => tr.tt.PayeeId,
payee => payee.PAYEEID,
(tr, payee) => new { Transaction = tr, Payee = payee })
.SelectMany(
transaction => transaction.Payee.DefaultIfEmpty(),
(transaction, payee) => new {transaction = transaction.Transaction.tt, FromAccount = transaction.Transaction.FromAccount, ToAccount = transaction.Transaction.ToAccount, Payee = payee })
.Where (x=> x.FromAccount.AccountId == 1 || x.ToAccount.AccountId == 1)
.Select(item => new
{
item.transaction.Id,
TransDate = item.transaction.TRANSDATE,
Number = item.transaction.TransactionNumber,
Payee = item.Payee.PAYEENAME,
Status = item.transaction.Status,
Category = item.transaction.CategoryId,
FromAccount = item.FromAccount.AccountName,
ToAccount = item.ToAccount.AccountName,
Withdrawal = 0,
Deposit = 0,
Notes = item.transaction.Notes
})
The parts that I was not understanding was the relationship in code, from the joins and how the Selects then took over and created a hierarchy through the objects. I really wish that I could have used Navigation here, but from my understanding of Navigation's there needs to be a relationship between the columns and nearly all the fields being joined on here, can be null and therefore a foreign key would not satisfy the requirements for a Navigation.
I am still convinced there is a better way, but for now I have placed this here for others who are still learning and wish to see a working solution.

Filtering Child Object with Multiple Criteria

I'm having an issue filtering records returned using linq. The objects involved look like this:
Appointment
public partial class Appointment
{
public Appointment()
{
Callbacks = new HashSet<Callback>();
}
[Key()]
public int AppointmentId { get; set; }
public DateTime Start { get; set; }
public DateTime? Deleted { get; set;}
public virtual ICollection<Callback> Callbacks { get; set; }
}
Callback
public partial class Callback
{
[Key()]
public int CallbackId { get; set; }
public DateTime? Deleted { get; set; }
public virtual Appointment Appointment { get; set; }
public virtual User AssignedTo { get; set; }
}
User
public partial class User
{
public User()
{
Callbacks = new HashSet<Callback>();
}
[Key()]
public int UserId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Ref { get; set; }
public virtual ICollection<Callback> Callbacks { get; set; }
}
I'm trying to return records that meet the following criteria:
The appointment start date must equal searchDate
The appointment is not deleted
The appointment start date must not clash with any appointments that the user already has
I've tried using the following query, but no results are returned (there are appointments available for the date 01/03/2016 (dd/mm/yyyy).
public List<AppointmentSearchResultsViewModel> SearchByDate(DateTime searchDate, string userName)
{
return _context.Appointments
.Where(a =>
a.Start.Date == searchDate
&& a.Deleted == null
&& a.Callbacks.Any(c =>
!(c.Appointment.Start != a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
))
.OrderBy(a => a.Start)
.Select(a)
.ToList();
}
Could anyone help me with how to filter correctly based on the criteria above?
Edit
To try and clarify the model:
A user has callbacks
A callback has an appointment
The aim of this query is to search for all appointments on the searchDate where the user does not already have a callback scheduled for the appointment time.
I think you need a negative comparison for your Any-statement:
!a.Callbacks.Any(c =>
(c.Appointment.Start == a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
Thus you only got those Callbacks from a.Callbacks which have a different Start-date.
Furtheremore you can ommit the Select(a)-statement at the end and immediately call ToList.
The model and what you are trying to achieve is not really clear to me, but I will try my chances anyway on the part O could understand:
return _context.Appointments
.Where(a =>
a.Start.Date == searchDate
&& a.Deleted == null
&& !a.Callbacks.Any(c =>
(c.Appointment.Start == a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
))
.OrderBy(a => a.Start)
.ToList();
Try this and let me know what you get in return...
return context.Users.Where(user => user.Ref = userName)
.SelectMany(user => user.Callbacks)
.Where(cb => cb.Appointment.Deleted == null)
.Where(cb => cb.Appointment.Start == searchDate)
.Select(cb => cb.Appointment)
.ToList();
This should return any appointments that clash with the searchDate parameter

Entity Framework Virtual ICollection How to Query

First of all, I am new to Entity Framework etc and trying to figure some things out. I have a model like this:
public class EventInstance {
[Column("EVENT_INSTANCE_ID")]
public int EventInstanceID { get; set; }
[Column("CUSTOMER_ID")]
public int CustomerID { get; set; }
[Column("EVENT_ID")]
public int EventID { get; set; }
[Column("START_DATE_TIME")]
public System.DateTime StartDateTime { get; set; }
public virtual Event Event { get; set; }
}
I need to access a property in a table called EventTimeEventInstances but this table is not included in the model. I have two questions.
If I add:
public virtual ICollection<EventTimeEventInstance> EventTimeInstances { get; set; }
Will that effect other areas of our application?
Secondly, how do I access the property from the ICollection in a query like this:
public IQueryable<EventInstance> GetInstances(int scheduleID) {
// only returning instances that are 3 months back
DateTime dateRange = DateTime.Now.AddDays(-180);
return EventDBContext.EventInstances.Where
(x => x.CustomerID == MultiTenantID && x.StartDateTime >= dateRange)
.OrderBy(x => x.StartDateTime).AsQueryable();
}
I need to be able to add EventTimeInstances.EventTimeID == scheudleID to this query. How can I do this?
You can use it like that in your query:
public IQueryable<EventInstance> GetInstances(int scheduleID)
{
// only returning instances that are 3 months back
DateTime dateRange = DateTime.Now.AddDays(-180);
return EventDBContext.EventInstances.Where(x =>
x.CustomerID == MultiTenantID &&
x.StartDateTime >= dateRange &&
x.EventTimeInstances.Any(a => a.EventTimeID == scheudleID) ).OrderBy(x => x.StartDateTime).AsQueryable();
}

Categories