Why can't I remove columns/props from select in linq statement - c#

I want to remove columns which I don't need to display!
Basically customer needs to see only FirstName and LastName, so I wanted to return only FirstName and LastName to end user.
But my app breaks If I remove other props from select:
var product = await _context.Products.OrderBy(p => p.CreatedDate)
.Select(x => new Product
{
ProductId = x.ProductId,
GroupId = x.GroupId,
ProductStatus = x.ProductStatus,
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync(u => u.ProductId == dbObj.Id && u.GroupId == ProductGroup.Drinks && u.ProductStatus.Id == (int)ProductStatusEnum.Active);
I would like to have something like this:
var product = await _context.Products.OrderBy(p => p.CreatedDate)
.Select(x => new Product
{
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync(u => u.ProductId == dbObj.Id && u.GroupId == ProductGroup.Drinks && u.ProductStatus.Id == (int)ProductStatusEnum.Active);
In classic (T-SQL) SQL statement I should be able to have columns in WHERE but NOT IN SELECT also, and here in LINQ looks like I must have columns in SELECT if I use them in WHERE.
Thanks
Cheers

Your query is not selecting columns, it's selecting new Product objects which you are setting the property values on. In the second case, you are only setting the Title and Price properties, and the others will be whatever the defaults are.
You may want to look at anonymous types, e.g.:
.Select(x => new { Title = x.Title, Price = x.Price });

Just change the order of the Linq statements. First filter by using Where before calling Select and use an anonymous object to project into:
var product = await _context.Products
.Where(u => u.ProductId == dbObj.Id
&& u.GroupId == ProductGroup.Drinks
&& u.ProductStatus.Id == (int) ProductStatusEnum.Active)
.OrderBy(p => p.CreatedDate)
.Select(x => new
{
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync();
This creates an anonymous type (product) with 2 properties Title and Price.
If you don't want to use an anonymous type another option is to create a class which contains only the properties you want.
public class ProductSummary
{
public string Title { get; set; }
public decimal Price { get; set; }
}
Then you can select into the concrete class:
.Select(x => new ProductSummary { Title = x.Title, Price = x.Price })

Related

Does not display data in linq C#

I have Linq which counts the goods, the problem is that the names that I pass, they do not work
ProductName, CompanyName, CustomerName,
Maybe there is a error in Linq?
It produces many anonymous methods that have these fields, but after ToList() everything does not work
public async Task<IEnumerable<SalesReportItem>> GetReportData(DateTime dateStart, DateTime dateEnd)
{
dateStart = new DateTime(2000, 1, 1);
var context = await _contextFactory.CreateDbContextAsync();
var queryable = context.SalesTransactionRecords.Join(
context.Products,
salesTransactionRecords => salesTransactionRecords.ProductId,
products => products.Id,
(salesTransactionRecords, products) =>
new
{
salesTransactionRecords,
products
})
.Join(context.Companies,
combinedEntry => combinedEntry.salesTransactionRecords.CompanyId,
company => company.Id,
(combinedEntry, company) => new
{
combinedEntry,
company
})
.Join(context.VendorCustomers,
combinedEntryAgain => combinedEntryAgain.combinedEntry.salesTransactionRecords.CustomerId,
vendorCustomer => vendorCustomer.Id,
(combinedEntryAgain, vendorCustomer) => new
{
CompanyName = combinedEntryAgain.company.Name,
CustomerName = vendorCustomer.Name,
ProductId = combinedEntryAgain.combinedEntry.products.Id,
ProductName = combinedEntryAgain.combinedEntry.products.Name,
combinedEntryAgain.combinedEntry.salesTransactionRecords.MovementType,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Period,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Quantity,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Amount,
}).Where(x => x.Period >= dateStart && x.Period <= dateEnd)
.GroupBy(combinedEntryAgain => new
{
combinedEntryAgain.ProductId,
combinedEntryAgain.ProductName,
combinedEntryAgain.CompanyName,
combinedEntryAgain.CustomerName,
}
).Select(x => new SalesReportItem
{
ProductId = x.Key.ProductId,
Quantity = x.Sum(a => a.Quantity),
Amount = x.Sum(x => (x.MovementType == TableMovementType.Income ? x.Amount : -(x.Amount)))
});
var items = await queryable.ToListAsync();
return _mapper.Map<IEnumerable<SalesReportItem>>(items);
}
my mistake was that I did not specify the fields in the select, otherwise everything is buzzing, the upper code is working
Select(x => new SalesReportItem
{
ProductId = x.Key.ProductId,
ProductName = x.Key.ProductName,
CompanyName = x.Key.CompanyName,
CustomerName = x.Key.CustomerName,
Quantity = x.Sum(x => (x.MovementType == TableMovementType.Income ? x.Quantity : - x.Quantity)),
Amount = x.Sum(x => (x.MovementType == TableMovementType.Income? x.Amount: - x.Amount))
});
Thanks for the help
Hans Kesting

Using LINQ to compare 2 lists of objects for updated properties

I have 2 lists of the same type of object, Student:
public class Student {
public string StudentId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string PostCode { get; set; }
}
I have 2 lists of StudentData objects - last year's list and this year's. I want to find the data where the Name, Address or Postcode of the student has changed. However, I am struggling with the correct LINQ query. Here's what I have tried so far:
public DataChange<StudentData>[] Compare(StudentData[] previous, StudentData[] current) {
Guard.AgainstNull(previous, nameof(previous));
Guard.AgainstNull(current, nameof(current));
var updatedName = previous
.Where(d => current.Select(d2 => d2.StudentId )
.Contains(d.StudentId )).Where(d => !current.Select(d2 => d2.Name)
.Contains(d.Name)).Where(d => current.Select(d2 => d2.Address)
.Contains(d.Address)).Select(DataChange<StudentData>.Updated).ToList();
var updatedAddress = previous.Where(d => current
.Select(d2 => d2.StudentId )
.Contains(d.StudentId ))
.Where(d => !current.Select(d2 => d2.Address)
.Contains(d.Address))
.Where(d => current.Select(d2 => d2.Name)
.Contains(d.Name)).Select(DataChange<StudentData>.Updated).ToList();
var updatedNameAndAddress = previous
.Where(d => current.Select(d2 => d2.StudentId )
.Contains(d.StudentId )).Where(d => !current.Select(d2 => d2.Address)
.Contains(d.Address)).Where(d => !current.Select(d2 => d2.Name).Contains(d.Name))
.Select(DataChange<StudentData>.Updated).ToList();
}
The reason I've done it this way is to cater for these scenarios:
Where the name has been updated but not the address
Where the address has been updated but not the name
However, this does not pass the test case where a student's name has been updated from say "Fred Blogs" to "Fred Blogger", or the address from "123 Address" to "123 updated".
Can anyone please point me in the right direction to get the correct LINQ query / a smoother way of doing this please?
What's wrong with something like this?
public DataChange<StudentData>[] Compare(StudentData[] previous, StudentData[] current) {
Guard.AgainstNull(previous, nameof(previous));
Guard.AgainstNull(current, nameof(current));
var updated = previous.Where(x => {
var newOne = current.FirstOrDefault(n => n.StudentId == x.StudentId);
if (newOne == null) {
return true; // this is considered a change, not found in 'current', but practically it is a 'delete'
}
return x.Address != newOne.Address || x.Name != newOne.Name;
}).Select(DataChange<StudentData>.Updated).ToList();
}
If you want to find students where the name is different but not the address, then the logic is fairly simple:
Find a match on StudentId
Check if the names are the different
Check if the addresses are the same
This can be repeated for the address being different but not the name, and for different name AND different address. In code it would look something like:
List<StudentData> updatedNames = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name != cur.Name && // Different name
prev.Address == cur.Address)) // Same address
.ToList();
List<StudentData> updatedaddresses = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name == cur.Name && // Same name
prev.Address != cur.Address)) // Different address
.ToList();
List<StudentData> updatedNameAndAddresses = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name != cur.Name && // Different name
prev.Address != cur.Address)) // Different address
.ToList();
Using GroupJoin:
return current.GroupJoin(
previous,
c => c.StudentId,
p => p.StudentId,
(c, ps) => {
if (ps?.Any() != true)
return DataChange<StudentData>.New();
// otherwise, ps should contain single value
// calculate the change in any appropriate way
return DataChange<StudentData>.FromOldAndNew(ps.First(), c);
}
)
// assuming null is returned for similar values
.Where(x => x is not null) // C# 9 feature, otherwise use !(x is null) or (x != null)
.ToArray();

Need to select multiple columns in Entity Framework, but need to get only distinct on single column

I have a database table that has the following records. There are more fields than what I have displayed below, but I only want to return the Owner, Brand Name, ID#, and Type fields.
Owner Ingredient BrandName ID# Type
XXX Methoprene Precor 123-333 H
XXX Permethrin Precor 123-333 H
I am trying to write an Entity Framework query to select only the distinct records in the Brand Name field, but still return the other columns in a list back to the controller to display in a partial view. The following is the code I have attempted, but I cannot seem to get the query written correctly:
return db.Pesticides
.Where(c => c.Owner == Owner && c.BrandName == PesticidesBrand)
.GroupBy(c => c.Owner, c =>c.BrandName )
.Select(d => d.FirstOrDefault())
.ToList();
I realize the Select clause is not correct, but need help getting correct syntax to return the 4 specific fields. I would like the query to return the following record:
XXX Precor 123-333 H
Thanks in advance....
I believe this is what you are looking for.
var record = db.Pesticides
.Where(c => c.Owner == Owner && c.BrandName == PesticidesBrand)
.Select(c => new { c.Owner, c.BrandName, c.ID, c.Type })
.FirstOrDefault();
If you want to return this from a result you need to project it to a known type.
PesticideModel record = db.Pesticides
.Where(c => c.Owner == Owner && c.BrandName == PesticidesBrand)
.Select(c => new PesticideModel{ Owner = c.Owner, BrandName = c.BrandName, ID = c.ID, Type = c.Type })
.FirstOrDefault();
PesticideModel.cs
public class PesticideModel {
public string Owner {get;set;}
public string BrandName {get;set;}
public string ID {get;set;}
public string Type {get;set;}
}
If you wanted to return a list containing a single record do the following:
List<PesticideModel> record = db.Pesticides
.Where(c => c.Owner == Owner && c.BrandName == PesticidesBrand)
.Select(c => new PesticideModel{ Owner = c.Owner, BrandName = c.BrandName, ID = c.ID, Type = c.Type })
.Take(1) // take 1st record
.ToList();
return db.Pesticides
.Where(c => c.Owner == Owner && c.BrandName == PesticidesBrand)
.Select(d => new Pesticide() { Owner = d.Owner, BrandName = d.BrandName, ID = d.ID, Type = d.Type })
.Distinct()
.FirstOrDefault()
.ToList();

Entity Framework building Where clause on the fly using Expression

Using Entity Framework C# and have this query, I need the part where it says:
where x.Login_Status == "Submitted"
to be dynamic. There are different cases it could be "Submitted" or null or something else and instead of writing multiple if statement with different queries in it, want to have a Predicate in a where clause.
status = (from x in ctx.table
where x.Login_Status == "Submitted"
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
Is that possible?
Solution:
Inside the if statement when more parameters encountered use this
where_expression = x => x.Login_Status == "Submitted" || x.Login_Status == null;
Here is a complete code that worked for me, anything between square brackets replace to suit your code:
Expression<Func<[Replace with your Entity], bool>> where_submitted = x => x.Login_Status == "Submitted";
// Check if all selected
if (CheckBox_Show_All_Submitted.Checked)
{
where_submitted = x => x.Login_Status == "Submitted" || x.Login_Status == null;
}
status =
ctx.[Replace with your Entity Table]
.Where(where_submitted)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
You need an Expression<Func<Entity,bool>>, not a Predicate<Entity>. The difference is that a predicate is a compiled delegate, and an expression is code as data and thus can be translated to SQL.
Here is an example:
//You can have this expression have different values based on your logic
Expression<Func<Entity,bool>> where_expression = x => x.Login_Status == "Submitted";
var query =
ctx.Table
.Where(where_expression)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table())
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList();
Please note that you need to replace Entity with the name of the real class.
Create an extension method for IQueryable like this:
public static class MethodExtensions{
public static IEnumerable<Model_Table> Query(this IQueryable<TEntity> source, string data){
return (from x in source
where x.Login_Status == data
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
}
}
Now you can use it like this:
var result = ctx.table.Query("somethingelse");

Lambda expression Group by in C#

I would like to group my LINQ query by ItemNumber and return the whole table with the total for Quantity.
Example:
ItemNumber - ItemName - Quantity
100 Item1 1
150 Item2 2
100 Item1 2
200 Item3 1
150 Item2 2
Should be:
ItemNumber - ItemName - Quantity
100 Item1 3
150 Item2 4
200 Item3 1
This is the query I am trying to group:
public IQueryable<WebsiteOrderStatus> GetOrderStatusByAccountNumberWithoutDeleted
(string accountNumber)
{
return db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1);
}
And my best result so far(this can't compile though):
public IQueryable<IGrouping<Int32?, WebsiteOrderStatus>> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g => new
{
g.Key.ItemNumber,
Column1 = (Int32?)g.Sum(p => p.Quantity)
});
}
EDIT:
Thanks for the replies everyone, I must face it. Theese anonymous types are pretty hard to work with in my opinion, so I found another solution.
I made another method, which sums the quantity of the users items and grouped the first one.
public IQueryable<WebsiteOrderStatus> GetOrderStatusByAccountNumberWithoutDeleted(string accountNumber)
{
return db.WebsiteOrderStatus.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1).GroupBy(x => x.ItemNumber).Select(grp => grp.First());
}
public int GetQuantityOfUsersItem(string accountNumber, string itemNumber)
{
return db.WebsiteOrderStatus.Where(x => x.ItemNumber == itemNumber && x.AccountNumber == accountNumber).Sum(x => x.Quantity);
}
At the page where I have my gridview I did:
var query = websiteOrderStatusRep.GetOrderStatusByAccountNumberWithoutDeleted(AppSession.CurrentLoginTicket.AccountNumber).Select(x => new { x.ItemName, x.ItemNumber, x.FormatName, x.Price, x.Status, x.Levering, Quantity = websiteOrderStatusRep.GetQuantityOfUsersItem(x.AccountNumber, x.ItemNumber)});
public IQueryable<IGrouping<Int32?, WebsiteOrderStatus>> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g => new
{
ItemNumber = g.Key,
ItemName = g.First().ItemName,
Count = g.Sum(item => item.Quantity)
});
}
public IQueryable<OrderStatus > lol(string accountNumber)
{
return db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g =>
new OrderStatus //This is your custom class, for binding only
{
ItemNumber = g.Key,
ItemName = g.First().ItemName,
Quantity = g.Sum(g => g.Quantity)
}
);
}
I think the Select should be:
.Select(g => new
{
ItemNumber = g.Key,
Column1 = (Int32?)g.Sum(p => p.Quantity)
});
Note the change in the first line of the anonymous type. The key of the grouping is already the item number.
The only problems I see with your query are
Missing return statement as per comments
The select statement should be:
-
.Select(g => new {
ItemNumber = g.Key,
Total = g.Sum(p => p.Quantity)
});
EDIT: If you want to get, lets say ItemNumber and ItemName , in the resulting object, you must also group on those fields
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => new { g.ItemNumber, g.ItemName })
.Select(g => new
{
ItemNumber = g.Key.ItemNumber,
ItemName = g.Key.ItemName,
Count = g.Sum(item => item.Quantity)
});
You cannot use anonymous type for return value type. So you will never compile the code.
Also your linq expression has IQueryable< [anonymous type] > result type.
I believe that you can do something like this:
public IQueryable<OrderStatus> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(order => order.AccountNumber == accountNumber && order.LastUpdatedStatus != 1)
.GroupBy(order => order.ItemNumber)
.Select(grouping => new OrderStatus //This is your custom class, for binding only
{
ItemNumber = grouping.Key,
ItemName = grouping.First().ItemName,
Quantity = grouping.Sum(order => order.Quantity)
});
}
I`ve fixed my answer too :)

Categories