Entity Framework: search for database entries with null columns - c#

I have the following model:
public class FactCache
{
public int ID { get; set; }
public DateTime MonthDate { get; set; }
public string AttributeKey { get; set; }
public decimal RawValue { get; set; }
public decimal ManDayValue { get; set; }
public decimal FTEValue { get; set; }
public int? InputResourceID { get; set; }
public int? PhaseID { get; set; }
public virtual InputResources InputResource { get; set; }
public virtual DimPhase Phase { get; set; }
}
As you can see above, InputResourceID and PhaseID are nullable optional fields.
I want to write a query to find the first entry for a given date and AttributeKey where both PhaseID and InputResourceID are null.
I have tried the following code:
FactCache fact = db.FactCache.FirstOrDefault(
a => a.InputResourceID == null
&& a.PhaseID == null
&& a.MonthDate == monthDate
&& a.AttributeKey == key);
However, it returns a null object. What is the correct way to write the above query?
I have checked the database and there are indeed entries with null PhaseID and InputResourceID.

Not sure, but perhaps...
FactCache fact = db.FactCache.FirstOrDefault(
a => false == a.InputResourceID.HasValue
&& false == a.PhaseID.HasValue
&& a.MonthDate == monthDate
&& a.AttributeKey == key);

Just a note, I you are lucky enough to be running EF version 5 or above try the solution proposed here: Call is failing with null parameter

Related

Computed property (SUM) In EF Core 5 model causes application to exit

I am having a strange problem in my application.
Using EF Core 5, I have a SalesOrderItem Model:
public class SalesOrderItem
{
public int SalesOrderItemId { get; set; }
public int? Qty { get; set; }
public decimal? UnitPrice { get; set; }
public string Description { get; set; }
[ForeignKey("SalesOrderId")]
public int SalesOrderId { get; set; }
[NotMapped]
public decimal LineTotal => (Qty == null ? 0 : Qty.Value) * (UnitPrice == null ? 0 : UnitPrice.Value);
}
This model has a computed property which gets the line value.
I have a SalesOrder model. I want computed properties for gross, net and tax:
public class SalesOrder
{
public int SalesOrderId { get; set; }
public int VatCodeId { get; set; }
public virtual IEnumerable<SalesOrderItem> SalesOrderItems { get; set; }
[NotMapped]
public decimal? Net
{
get
{
if (SalesOrderItems == null)
return null;
return SalesOrderItems.Sum(p => p.LineTotal);
}
}
[NotMapped]
public decimal? Tax
{
get
{
if (Net == null)
return null;
return (Net.Value * VatCode.Percentage) / 100;
}
}
[NotMapped]
public decimal? Gross
{
get
{
if (Net == null || VatCode == null)
return null;
return Net.Value + Gross.Value;
}
}
}
This causes my application to exit on startup with no error in VS.
If I remove the property "Gross", then the application does not close.
Does anyone know why my "Gross" computed field is causing my app to crash?
I am using the following code which works when Gross is omitted:
var salesOrder = await _context.SalesOrder.Include(p => p.VatCode).Include(i => i.SalesOrderItems).SingleOrDefaultAsync(i => i.SalesOrderId == salesOrderId);
Apologies for the "Snippet". I could not paste the model into code without it breaking.

Handle null values in mapper property

I'm trying to read a flat file and do some processes. To do that I've defined a mapper. That mapper will assign the values for each property. In the document, the date will be represented with yyMMdd format and it can have "" or 000000 as a null value. That mean, if the date is 6 zeros or 6 blank spaces, the output should be null. I tried to do this by defining a NullFormater. But didn't work.
This is what I've tried:
============================
public class Test : DocumentRecordBase
{
public string StorageOrganisation { get; set; }
public Guid? StorageOrganisationId { get; set; }
public string StorageDescription { get; set; }
public DateTime? PaymentDueDate { get; set; }
public decimal? DiscountRate { get; set; }
public int? MaximumDaysDiscount { get; set; }
public DateTime? DateStorageChargeCommences { get; set; }
public decimal? StorageChargePerBalePerDay { get; set; }
public decimal? PenaltyInterestRate { get; set; }
public DateTime? LotAvailableDate { get; set; }
public decimal? PostSaleRechargeRebate { get; set; }
public Test() : base()
{
}
public override T GetDocumentRecord<T>()
{
if (typeof(T) == typeof(Test))
{
return this as T;
}
return null;
}
public static IFixedLengthTypeMapper<Test> GetMapper()
{
var mapper = FixedLengthTypeMapper.Define<Test>();
mapper.Property(r => r.RecordType, 2);
mapper.Property(r => r.RecordSubType, 1);
mapper.Property(r => r.PaymentDueDate, 6)
.ColumnName("PaymentDueDate")
.InputFormat("yyMMdd")
.NullFormatter(NullFormatter.ForValue("000000")); // if the read value is "000000" or " " then should pass as null
mapper.CustomMapping(new RecordNumberColumn("RecordNumber")
{
IncludeSchema = true,
IncludeSkippedRecords = true
}, 0).WithReader(r => r.RecordNumber);
return mapper;
}
public static bool GetMapperPredicate(string x)
{
return x.StartsWith("11A");
}
}
According to the definition of NullFormatter, (found here), you can only assign 1 fixed value. "If it is a fixed value, you can use the NullFormatter.ForValue method."
NullFormatter = NullFormatter.ForValue("NULL")
If you use "000000", then it should convert 000000 to null otherwise, spaces will be considered actual values. Any number of 0s != 6 will result in non-null value as well.
Also, please define what you mean by "But didn't work". Please provide details and errors for elaboration

Temporary variables in LINQ select statements for EF Core

I am having trouble writing a slightly complicated Select statement.
My EF objects look like this:
public partial class SensorEvent
{
public SensorEvent()
{
SensorData = new HashSet<SensorData>();
}
public int Id { get; set; }
public int SensorId { get; set; }
public int RecordTime { get; set; }
public Sensor Sensor { get; set; }
public ICollection<SensorData> SensorData{ get; set; }
}
public partial class SensorData
{
public int Id { get; set; }
public int SensorEventId { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
public SensorEvent SensorEvent { get; set; }
}
public partial class Sensor
{
public Sensor()
{
SensorEvent = new HashSet<SensorEvent>();
SensorPlacement = new HashSet<SensorPlacement>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<SensorEvent> SensorEvent{ get; set; }
public ICollection<SensorPlacement> SensorPlacement{ get; set; }
}
public partial class Room
{
public Sensor()
{
SensorPlacement = new HashSet<SensorPlacement>();
}
public int Id { get; set; }
public int FloorId { get; set; }
public string Name { get; set; }
public Floor Floor { get; set; }
public ICollection<SensorPlacement> SensorPlacement{ get; set; }
}
public partial class Floor
{
public Floor()
{
Room = new HashSet<Room>();
}
public int Id { get; set; }
public int BuildingId { get; set; }
public string Name { get; set; }
public Building Building { get; set; }
public ICollection<Room> Room{ get; set; }
}
public partial class Building
{
public Building()
{
Floor = new HashSet<Floor>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Floor> Floor{ get; set; }
}
public partial class SensorPlacement
{
public int Id { get; set; }
public int SensorId { get; set; }
public int RoomId { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public Sensor Sensor { get; set; }
public Room Room { get; set; }
}
Let me explain a bit. The main data being inserted to the db is SensorEvent. SensorEvent is essentially a list of SensorData objects sent from a sensor, along with which sensor sent it and when it was recorded. So far pretty simple, if I want to present all the SensorEvents and which sensor they came from, I can return the following domain objects
public class DomainSensorEvent
{
public int Id { get; set; }
public int SensorName { get; set; }
public int RecordTime { get; set; }
public IList<DomainSensorData> SensorData{ get; set; }
}
public partial class DomainSensorData
{
public int Id { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
}
And I can get it like this (Ignoring the Where, Take, OrderBy, and Skip parts for the sake of simplicity).
public List<DomainSensorEvent> GetDomainSensorEvents()
{
return DbContext.SensorEvent.Select(dse => new DomainSensorEvent
{
Id = dse.Id,
SensorName = dse.Sensor.Name,
RecordTime = dse.RecordTime,
SensorData = dse.SensorData.Select(sd => new DomainSensorData
{
Id = sd.Id,
DataType = sd.DataType,
Description = sd.Description
}).ToList()
}).ToList();
}
Pretty straight forward so far, the problem arises when I want to include where a sensor was placed (Which Room, which Floor and Building) when this data was recorded. See, I don't have this information at insert time. The sensor can be moved around, so the SensorPlacement table might be updated after a SensorEvent from that sensor in that time interval has been inserted. Meaning instead of linking a SensorEvents to both a Room and Sensor, the Room is resolved at query time. Yes, this means a query could return the wrong Room, if SensorPlacement has not been updated, but it will eventually be correct. Complicating matters further is of course that a SensorEvent might not have occured in a Room, so I also need to check for null.
The new domain model and query (the part I need help with) looks like this.
public class DomainSensorEvent
{
public int Id { get; set; }
public int SensorName { get; set; }
public int Room { get; set; }
public int Floor { get; set; }
public int Building { get; set; }
public int RecordTime { get; set; }
public IList<DomainSensorData> SensorData{ get; set; }
}
public List<DomainSensorEvent> GetDomainSensorEvents()
{
return DbContext.SensorEvent.Select(dse => new DomainSensorEvent
{
Id = dse.Id,
SensorName = dse.Sensor.Name,
Room = dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault() != null ?
dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault().Room.Name : null,
Floor = dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault() != null ?
dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault().Room.Floor.Name : null,
Building = dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault() != null ?
dse.Sensor.SensorPlacement.Where(sp => dse.RecordTime > sp.From && (sp.To == null || dse.RecordTime < sp.To)).FirstOrDefault().Room.Floor.Building.Name : null,
RecordTime = dse.RecordTime,
SensorData = dse.SensorData.Select(sd => new DomainSensorData
{
Id = sd.Id,
DataType = sd.DataType,
Description = sd.Description
}).ToList()
}).ToList();
}
The most obvious problem is of course the same statement repeated 6 times (making an already slow query even worse). I have tried to solve it by storing it as a temporary variable, but apparently you can only have a single statement in a Select query that is translated to SQL. I have tried running an extra Select statment before this one, returning an anonomous type with SensorEvent and a corresponding SensorPlacement, but this breaks navigation properties. Using Joins also makes using navigation properties difficult.
Am I looking at this from the wrong angle? Is there some syntax I am missing, or do I need to do this another way? I know the best solution would be to be able to know SensorPlacement at insert time, but that is not currently possible.
You have a few questions here, so I don't know that this can really be answered but a few quick thoughts:
Use your navigation properties sooner, and put your where clause on the initial select:
public List<DomainSensorEvent> GetDomainSensorEvents()
{
return DbContext.SensorEvent
.Include("Sensor")
.Include("SensorData")
.Include("Sensor.SensorPlacement")
.Where(w => w.Sensor.SensorPlacement.Where(sp => w.RecordTime > sp.From
&& (sp.To == null || w.RecordTime < sp.To)))
.ToList();
}
This should (assuming the fks and Navigation properties are actually correct) give you a list of your Entities with your List navigations that are in the .Includes, that match on the where Clause (I don't think you'd want it as is though this way).
I'd then look at AutoMapper or something similar to convert it to your domain / view models.
var results = GetDomainSensorEvents();
Mapper.Map<DomainSensorEvent>(results);
Since you know some properties and properties of properties are null in the database, you can handle them in the mapping or even the .AfterMap() as required.
I'd also reconsider your naming strategy. DomainEntityName is descriptive, but it sort of ruins Intellisense when every class starts with the same word. It also reminds me of why you don't want to use Hungarian Notation.
On the Where Clause: This should work for One to One hierarchical properties (Navigation properties with one child:
.Where(w => w.Sensor.SensorPlacement.Where(sp => w.RecordTime > sp.From
&& (sp.To == null || w.RecordTime < sp.To)))
But for getting the parent with a one to many you'd need slightly different logic:
.Where(w => w.Sensor.SensorPlacement.Where(sp => sp.Any(w.RecordTime > sp.From
&& (sp.To == null || w.RecordTime < sp.To))))
This should get you any parent where the included List (In your case List ) has Any matching properties.
Lastly, this was a shot at reducing code I obviously can't reproduce and run locally. Hopefully this helps but it isn't going to be the exact solution.

EF can't return the items with condition but sql can why?

I have a query like this :
int a= pgc.Fronts.Where(i => i.ItemCode == itemcode && i.PC == pc).Count()
PGC is my dbcontext .The itemcode is '10414' and the pc value is null the result is 0.
So i changed the query to sql command :
/****** Script for SelectTopNRows command from SSMS ******/
SELECT [Id]
,[No]
,[ItemCode]
,[QtyRequest]
,[PC]
,[Date]
,[Type]
,[Line]
,[joint]
,[QTYIssue]
,[Recivedby]
FROM [pgc].[dbo].[Fronts] where ItemCode='10414' and pc is null
it returns 3 records as you can see here :
So why it happens ?
public partial class Front
{
public long Id { get; set; }
public string No { get; set; }
public string ItemCode { get; set; }
public Nullable<double> QtyRequest { get; set; }
public string PC { get; set; }
public System.DateTime Date { get; set; }
public string Type { get; set; }
public string Line { get; set; }
public string joint { get; set; }
public double QTYIssue { get; set; }
public string Recivedby { get; set; }
}
If I remember correctly (at least for Linq2Sql) if you want to compare with null values you should use Object.Equals:
int a = pgc.Fronts.Where(i =>
i.ItemCode == itemcode && Object.Equals(i.PC, pc)).Count();
This "bug" seems to have been fixed in later versions of Entity framework. For now you can use:
int a = pgc.Fronts.Where(i =>
i.ItemCode == itemcode && (i.PC == pc || (pc == null && i.PC == null)).Count();

LINQ check if rows fall in between dates with no gaps

I need to write a query where it will only return the rows if the dates fall between a start and end date without having gaps.
Below would fail and return nothing:
Requested -----[---------------------------------]--------
In the DB ----------------[------]------------------------
Requested -----[---------------------------------]--------
In the DB --[----------------]----------------------------
Requested -----[---------------------------------]--------
In the DB --[----------------]---------[----------------->
Below would pass and return the rows from the database:
Requested -----[---------------------------------]--------
In the DB -----[---------------------------------]--------
Requested -----[---------------------------------]--------
In the DB --[------------------------------------------]--
Requested -----[---------------------------------]--------
In the DB --[----------------][---------------][--------]-
EDIT
Here's the POCO object of what the table will look like:
public class SubscriptionPeriod
{
public int Id { get; set; }
public int SubscriptionId { get; set; }
public int? MatchedSubscriptionPeriodId { get; set; }
public SubscriptionPeriodStatuses Status { get; set; }
public int Qty { get; set; }
public int? FillQty { get; set; }
public DateTime SubscriptionStartDate { get; set; }
public DateTime? SubscriptionEndDate { get; set; }
public int? RenewQty { get; set; }
public bool RollOverQty { get; set; }
public DateTime? ProcessedDate { get; set; }
public DateTime CreateDate { get; set; }
}
SubscriptionPeriods
.Where(sp => sp.Subscription.UserId == userId &&
sp.Subscription.ZoneId == zoneId &&
sp.Subscription.Type == 2 &&
sp.MatchedSubscriptionPeriodId == null &&
sp.SubscriptionStartDate <= subscriptionEndDate &&
sp.SubscriptionEndDate >= subscriptionStartDate &&
(sp.Status == SubscriptionPeriodStatuses.Initalized || sp.Status == SubscriptionPeriodStatuses.Open))
.Sum(s => (int?)s.Qty)
NOTE: When SubscriptionEndDate is null means the subscription lasts forever from the start date.

Categories

Resources