Linq Value cannot be null on FK - c#

Using C# MVC5 Visual studio 2015.
I have a method that contains the following code:
public List<OffersOnPropertyViewModel> Build(string buyerId)
{
var filtered = _context.Properties.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
var model = filtered.Select(c =>
{
var item = new OffersOnPropertyViewModel()
{
PropertyType = c.PropertyType,
NumberOfBedrooms = c.NumberOfBedrooms,
StreetName = c.StreetName,
Offers = c.Offers.Where(d => d.BuyerUserId == buyerId).Select(x => new OfferViewModel
{
Id = x.Id,
Amount = x.Amount,
CreatedAt = x.CreatedAt,
IsPending = x.Status == OfferStatus.Pending,
Status = x.Status.ToString(),
BuyerUserId = x.BuyerUserId
}),
};
return item;
}).ToList();
//TODO: refactor, shorten linq, duping where clause
return model;
}
Here is the model:
public class Property
{
[Key]
public int Id { get; set; }
[Required]
public string PropertyType { get; set; }
[Required]
public string StreetName { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int NumberOfBedrooms { get; set; }
[Required]
public string SellerUserId { get; set; }
public bool IsListedForSale { get; set; }
public ICollection<Offer> Offers { get; set; }
}
In the DB Offers table has the property id as its FK.
The method fails at runtime saying the Value cannot be null.
When I step through I notice the filtered results (in the example its 1 result), is saying offers is null. Although the query just filtered the results based on "x.Offers".
I simply need a way to retrieve a list of property's that have offers made by the buyerId provided. Is my approach wrong? or am i missing a one liner?
Thanks

You will need to add Include() to your LINQ query to bring in child objects, as follows:
var filtered = _context.Properties.Include("Offers")
.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
The reason your filter works with the Any() is because when generating the SQL query, this part forms the WHERE clause and is not included in the SELECT.

Related

Linq with Lambda - how do I restrict joined table rows?

I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});

I am unable to convert StringBuilder to string in c# asp.net MVC

My invoice class has a StringBuilder property so that if adjustments are made, the user adds notes as the details of why the adjustment was made. This property is set to a new empty StringBuilder upon creation. Only in edit mode will it get .Append() added to. From what I understand, this is more efficient than using adjustmentNotes += newAdjustmentNotes every time new notes are added.
My Invoice.cs class is as follows.
public class Invoice
{
[Key]
public int InvoiceID { get; set; }
[ForeignKey(nameof(Customer))]
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public double InvoiceAmount { get; set; }
public StringBuilder AdjustmentNotes { get; set; }
public bool Paid { get; set; }
}
When populating my list or details view, I query the database as follows:
using (var ctx = new ApplicationDbContext())
{
var query = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.Select(e => new InvoiceListItem
{
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
});
return query.ToArray();
}
Then I get the following error message.
System.NotSupportedException: 'Values of type 'StringBuilder' can not be converted to string.'
But according to Microsoft documentation, other questions on this forum, and various resources this is the proper way to accomplish this.
I can certainly change it back to a string and use += as needed. There will not be too many notes for this particular property, but can someone please explain what I am doing wrong? Thank you so much!
EDIT: For clarification, here is my InvoiceListItem model class:
public class InvoiceListItem
{
public int InvoiceID { get; set; }
public int CustomerID { get; set; }
public List<int> JobIDs { get; set; }
public double InvoiceAmount { get; set; }
[UIHint("Bool")]
public bool Paid { get; set; }
public string AdjustmentNotes { get; set; }
}
EF Core says in the error message :
I don't know how convert StringBuilder.ToString() to target database language.
The solution is to do the conversion after the query :
var result = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.ToList() // First execute the query
.Select( // Second convert Invoice to InvoiceListItem
e => new InvoiceListItem {
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
}
);
If you want limit the returned properties in the query, you can use intermediate type like :
var query = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.Select(
e => new {
e.InvoiceID,
e.CustomerID,
e.InvoiceAmount,
e.Paid,
AdjustmentNotes
}
);
var result = query.ToList().
.Select(
e => new InvoiceListItem {
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
}
);
PS :
I found no information about EF Core support StringBuilder type, but
your context's model is build with success then EF Core seems to support StringBuilder type...
If there is support, it seems very limited (see the error in your question). Maybe you can consider use String instead of StringBuilder in entity class.

ServiceStack OrmLite Include Column in Where Clause but Not Select

Consider the LoyaltyCard database DTO I have below:
[Alias("customer_ids")]
[CompositeIndex("id_code", "id_number", Unique = true)]
public class LoyaltyCard
{
[Alias("customer_code")]
public int CustomerId { get; set; }
[Alias("id_code")]
public string LoyaltyCardCode { get; set; }
[Alias("id_number")]
public string LoyaltyCardNumber { get; set; }
[Alias("date_issued ")]
public DateTime? IssueDate { get; set; }
[Alias("linked_id")]
public bool LinkedId { get; set; }
[Alias("positive_id")]
public bool PositiveId { get; set; }
}
I have CustomerId as a property because I need it to be included in the WHERE clause of the generated SQL, but I don't want the column to also be selected in the result set. Is there a way to include it in the where but exclude it from the select?
// I was hoping that result would not have a populated CustomerIds
var result = db.Select<LoyaltyCard>(id => id.LoyaltyCardCode == "PS" && id.CustomerId == customerCode);
I've tried adding the [Ignore] attribute but that fails with SqlException: Invalid column name 'CustomerId'.
If you don't want the entire POCO populated you'd need to specify a Custom Select with just the fields you want selected, e.g:
var result = db.Select<LoyaltyCard>(db.From<LoyaltyCard>()
.Where(id => id.LoyaltyCardCode == "PS" && id.CustomerId == customerCode)
.Select(x => new {
x.LoyaltyCardCode,
x.LoyaltyCardNumber,
x.IssueDate,
x.LinkedId,
x.PositiveId
}));

Lambda Expression for Finding properties

I want to retreive LineOfBusiness property from promotionCatalogResponseRootObject where PromotionID is equal to myId
public class PromotionCatalogResponseRootObject
{
public PromotionCatalogResponse PromotionCatalog { get; set; }
}
public class Promotion
{
public string PromotionId { get; set; }
public string LineOfBusiness { get; set; }
public string PromotionName { get; set; }
}
public class PromotionCatalogResponse
{
public List<Promotion> Promotion { get; set; }
}
I started off like this but I know I'm doing it wrong. Please guide
string myId = "7596";
string lineOfBusiness = dataPromotionCatalog.PromotionCatalog
.Promotion.Find(p => p.LineOfBusiness);
What you are missing is the the Find method is expecting to receive a predicate with a bool return value but you are returning a string, the LineOfBusiness property.
First filter the items you want and then select the desired property. In addition, I'd go with the Where instead of the Find. (Find() vs. Where().FirstOrDefault())
var result = dataPromotionCatalog.PromotionCatalog.Promotion
.Where(i => i.PromotionId == myId)
.Select(i => i.LineOfBusiness);
or in query syntax
var result = (from item in dataPromotionCatalog.PromotionCatalog.Promotion
where item.PromotionId == myId
select item.LineOfBusiness);
If you are expecting/want to have one item then use FirstOrDefault:
var result = dataPromotionCatalog.PromotionCatalog.Promotion
.FirstOrDefault(i => i.PromotionId == myId)?.LineOfBusiness;

Using a calculated value in the OrderBy clause with EF

I'm trying to use a calculated value in my OrderBy clause in a LINQ query.
The error I am getting is:
DbArithmeticExpression arguments must have a numeric common type.
My model looks like this:
public class PostModel
{
public int ID { get; set; }
public DateTime Created { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string FilePath { get; set; }
public float Rank { get; set; }
public UserProfile Creator { get; set; }
public bool ShowPost { get; set; }
public PostModel()
{
Created = DateTime.Now;
Rank = 0;
ShowPost = false;
}
}
and I'm trying to select posts using this:
var todaysDate = DateTime.Now.AddDays(-10);
var result = _database.Posts
.Where(p => p.ShowPost == true)
.OrderBy(x => ((float)x.Rank) - (((float)(x.Created - todaysDate).TotalDays)) / 2f)
.Skip(page * StaticVariables.ResponseDataPageSize)
.Take(StaticVariables.ResponseDataPageSize)
.Select(s => new
{
id = s.ID,
rank = s.Rank,
title = s.Title,
description = s.Description
}
);
It's the order by causing the error. I first thought it was that I was not casting all my variables to the same type, but adding (float) does not seem to help.
The purpose of the code is to make make high ranking posts fall down the list over time as to allow newer information to be shown.
Any ideas?
Use EntityFunctions in LinqToEntity:
EntityFunctions.DiffDays(todaysDate, x.Created)

Categories

Resources