Issue Related to SelectMany function in LINQ - c#

I have two tables in Database:
PostCalculationLine
PostCaluclationLineProduct
PostCalculationLineProduct(table2) contains Foriegn key of PostCalucationLineId(table1)
In C# code I have two different Models for these two tables as follows:
public class PostCalculationLine : BaseModel
{
public long Id{ get; set; }
public string Position { get; set; }
public virtual Order Order { get; set; }
public virtual Task Task { get; set; }
//some other properties go here
public virtual IList<PostCalculationLineProduct> PostCalculationLineProducts { get; set; }
}
and
public class PostCalculationLineProduct : BaseModel
{
public long Id {get;set;}
public string Description { get; set; }
//some other properties go here
}
Now in Entityframework code, I fetch data from PostCalculationLineProduct as follows:
PostCalculationLineRepository pclr = new PostCalculationLineRepository();
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts)
.Where(c => c.Product.ProductType.Id == 1 && c.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.Id,
Date = c.From,
EmployeeName = c.Employee != null ?c.Employee.Name:string.Empty,
Description= c.Description,
ProductName = c.Product != null?c.Product.Name :string.Empty,
From = c.From,
To = c.Till,
Quantity = c.Amount,
LinkedTo = "OrderName",
Customer ="Customer"
PostCalculationLineId = ____________
})
.ToDataSourceResult(request);
In the above query I want to get PostCalculationLineId(from Table1) marked with underLine. How can I achieve this?
Thanks

You can use this overload of SelectMany to achieve this:-
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts,
(PostCalculationLineProductObj,PostCalculationLineObj) =>
new { PostCalculationLineProductObj,PostCalculationLineObj })
.Where(c => c.PostCalculationLineProductObj.Product.ProductType.Id == 1
&& c.PostCalculationLineProductObj.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.PostCalculationLineProductObj.Id,
Date = c.PostCalculationLineProductObj.From,
//Other Columns here
PostCalculationLineId = c.PostCalculationLineObj.Id
};
This will flatten the PostCalculationLineProducts list and returns the flattened list combined with each PostCalculationLine element.

Related

Convert SQL to Linq with EF Core

I am using .NET Core 2.2, EF Core, C# and SQL Server 2017.
I am not able to translate the query I need to Linq.
This is the query I need to convert:
SELECT TOP 5
p.Id,
p.Title,
AVG(q.RatingValue) AvgRating
FROM Movies AS p
INNER JOIN Ratings AS q ON p.Id = q.MovieId
GROUP BY p.Id, p.Title
ORDER BY AvgRating DESC, p.Title ASC
The idea of the previous query is to get the Top 5 movies according to the Avg rating, ordering it by the highest average first, and in case of same average order alphabetically.
So far this is my query that makes the join, but then still missing: the group by, average, and ordering:
public class MovieRepository : IMovieRepository
{
private readonly MovieDbContext _moviesDbContext;
public MovieRepository(MovieDbContext moviesDbContext)
{
_moviesDbContext = moviesDbContext;
}
public IEnumerable<Movie> GetTopFive()
{
var result = _moviesDbContext.Movies.OrderByDescending(x => x.Id).Take(5).
Include(x => x.Ratings);
return result;
}
}
And these are the entities:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public int YearOfRelease { get; set; }
public string Genre { get; set; }
public int RunningTime { get; set; }
public IList<Rating> Ratings { get; set; }
}
public class Rating
{
public int Id { get; set; }
public int MovieId { get; set; }
public int UserId { get; set; }
public decimal RatingValue { get; set; }
}
I tried to use Linqer tool also to convert my query to Linq, but it was not working.
I will appreciate any help to convert that query to LINQ for the method "GetTopFive".
Thanks
Try this one -
var data = _moviesDbContext.Movies.Include(x => x.Ratings)
.Select(x => new {
Id = x.Id,
Title = x.Title,
Average = (int?)x.Ratings.Average(y => y.RatingValue)
}).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();
Try as follows:
public IEnumerable<Movie> GetTopFive()
{
var result = _moviesDbContext.Ratings.GroupBy(r => r.MovieId).Select(group => new
{
MovieId = group.Key,
MovieTitle = group.Select(g => g.Movie.Title).FirstOrDefault(),
AvgRating = group.Average(g => g.RatingValue)
}).OrderByDescending(s => s.AvgRating).Take(5).ToList();
return result;
}
This will exclude the movies having no ratings.
But if you do as follows (as artista_14's answer):
public IEnumerable<Movie> GetTopFive()
{
var result = _moviesDbContext.Movies.GroupBy(x => new { x.Id, x.Title })
.Select(x => new {
Id = x.Key.Id,
Title = x.Key.Title,
Average = x.Average(y => y.Ratings.Sum(z => z.RatingValue))
}).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();
return result;
}
this will include the movies having no ratings also.
Note: I see your Rating model class does not contain any Movie navigation property. Please add this as follows:
public class Rating
{
public int Id { get; set; }
public int MovieId { get; set; }
public int UserId { get; set; }
public decimal RatingValue { get; set; }
public Movie Movie { get; set; }
}
and finally this is the code working nicely:
var data = _moviesDbContext.Movies.Include(x => x.Ratings)
.Select(x => new MovieRating
{
Id = x.Id,
Title = x.Title,
Average = x.Ratings.Average(y => y.RatingValue)
}).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();
return data;
The problem was creating an anonymous type in the select, so this line resolves the issue: .Select(x => new MovieRating
And this is the complete code for the method and the new class I have created to map the select fields with a concrete type:
public class MovieRepository : IMovieRepository
{
private readonly MovieDbContext _moviesDbContext;
public MovieRepository(MovieDbContext moviesDbContext)
{
_moviesDbContext = moviesDbContext;
}
public IEnumerable<Movie> GetAll()
{
return _moviesDbContext.Movies;
}
public IEnumerable<MovieRating> GetTopFive()
{
var result = _moviesDbContext.Movies.Include(x => x.Ratings)
.Select(x => new MovieRating
{
Id = x.Id,
Title = x.Title,
Average = x.Ratings.Average(y => y.RatingValue)
}).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();
return result;
}
}
public class MovieRating
{
public int Id { get; set; }
public string Title { get; set; }
public decimal Average { get; set; }
}

Using Linq to read from multiple tables

I'm sure someone else has asked this but I searched on what I could think of to find the solution.
I've got the following data models to match tables in my SQL db:
public class ProfileDetailModel
{
public string id { get; set; }
public string name { get; set; }
public StyleList[] styleList { get; set; }
public FabricList[] fabricList { get; set; }
}
public class StyleList
{
public string id { get; set; }
public string name { get; set; }
}
public class FabricList
{
public string id { get; set; }
public string fabricName { get; set; }
}
This is the current query code:
var query = (from t in db.tblProfiles
select new ProfileDetailModel()
{
id = t.id,
name = t.name
});
var querylist = await query.ToListAsync();
(prototyped linq queries below for style and fabric)
var styleQuery = (from t in db.tblStyles
select new styleList()
{
id = t.id,
name = t.name
});
var fabricQuery = (from t in db.tblFabrics
select new fabricList()
{
id = t.id,
name = t.name
});
if (queryList.Count > 0)
{
var item = queryList[0];
item.styleList = styleQuery;
item.fabricList = fabricQuery;
}
I'll have one profileDetailModel with multiple items in styleList and in fabricList. EG.
ProfileDetailModel
Data: Pants
styleList: Bell Bottom, Straight Leg, Boot fit
fabricList: jean-blue, jean-black, plaid
All three above models are tables in my db. I could issue 3 separate queries to read the data then assemble after the fact. But is there a way I can do a linq query to include the two arrays in the main query in one shot?
Try this:
var newQuery = (from p in db.tblProfiles
select p)
.AsEnumerable()
.Select(x => new ProfileDetailModel()
{
id = x.id,
name = x.name,
styleList = styleQuery,
fabricList = fabricQuery
});

Need a alternate approch to do SQL to LINQ conversion

I'm trying to get a particular result set for my View to bind. I'm new to Linq expression, so I'm not very sure about the different ways of doing it.
Here is my MenuModel
public class MenuModel : DisposeBase
{
public string ParentID { get; set; }
public string ParentName { get; set; }
public List<MenuItemModel> MenuItems { get; set; }
}
My MenuItemModel
public class MenuItemModel : DisposeBase
{
public string ChildID { get; set; }
public string ChildName { get; set; }
public string PageURL { get; set; }
}
MenuModel is the output type I'm expecting as a result set. I'm getting result set of type DataTable from backend
DataTable dtable = oDatabase.ExecuteAdapter(System.Data.CommandType.StoredProcedure, "SP_GETUSERNAVMENUDATA");
Here is my SQL result set,
My DataTable will looks like this
Now I need to convert this Datatable to type MenuModel.
I tried to Query distinct MenuModel and based on that I'm building MenuItemModel object.
List<MenuModel> lstMenuModel = dtable.DataTableToList<MenuModel>()
.GroupBy(p => new { p.ParentID, p.ParentName })
.Select(g => g.First())
.ToList<MenuModel>();
foreach (MenuModel parentItem in lstMenuModel)
{
List<MenuItemModel> lstUserMenuItemData = dtable.DataTableToList<MenuItemModel>()
.Select(i => new { i.ChildID, i.ChildName, i.PageURL, i.ParentID })
.Where(i => i.ParentID.Equals(parentItem.ParentID))
.ToList<MenuItemModel>();
}
But still I'm getting conversion error while building MenuItemModel. Now I wanted to know, is there any best practice to do this same conversion of these nested class type? I'm sure there should be something simple to do so.
Any help could be appreciated. Thanks!
Note: DataTableToList is a method that will convert DataTable object to specific generic type
Its not clear what your DataTableToList<MenuModel>() method is doing or returning, but it would need to return a collection of a model that contains all 5 properties represented in the data table.
Assuming you have the following model
public class MenuSQLSet
{
public string ParentID { get; set; }
public string ParentName { get; set; }
public string ChildID { get; set; }
public string ChildName { get; set; }
public string PageURL { get; set; }
}
then your query should be
List<MenuModel> lstMenuModel = dtable.DataTableToList<MenuSQLSet>()
.GroupBy(x => new { x.ParentID, x.ParentName })
.Select(x => new MenuModel()
{
ParentID = x.Key.ParentID,
ParentName = x.Key.ParentName,
MenuItems = x.Select(y => new MenuItemModel()
{
ChildID = y.ChildID,
ChildName = y.ChildName,
PageURL = y.PageURL
}).ToList()
}).ToList();
Alternatively you can use .AsEnumerable() on the DataTable and reference the column names
List<MenuModel> lstMenuModel = dtable.AsEnumerable()
.GroupBy(x => new { ParentID = x["ParentID"], ParentName = x["ParentName"] })
.Select(x => new MenuModel()
{
ParentID = x.Key.ParentID,
ParentName = x.Key.ParentName,
MenuItems = x.Select(y => new MenuItemModel()
{
ChildID = y["ChildID"],
....

EF get list of parent entities that have list of child

I have to next 2 entities in my project
public class Product
{
public Product()
{
this.ProductImages = new HashSet<ProductImage>();
this.ProductParams = new HashSet<ProductParam>();
}
public int ID { get; set; }
public int BrandID { get; set; }
public int CodeProductTypeID { get; set; }
public string SeriaNumber { get; set; }
public string ModelNumber { get; set; }
public decimal Price { get; set; }
public bool AvailableInStock { get; set; }
public virtual Brand Brand { get; set; }
public virtual CodeProductType CodeProductType { get; set; }
public virtual ICollection<ProductImage> ProductImages { get; set; }
public virtual ICollection<ProductParam> ProductParams { get; set; }
}
public class ProductParam
{
public int Id { get; set; }
public int ProductId { get; set; }
public int CodeProductParamId { get; set; }
public string Value { get; set; }
public virtual Product Product { get; set; }
public virtual CodeProductParam CodeProductParam { get; set; }
}
and I want to get list of Products which has list of specified parameters
var prodParamCritria = new List<ProductParam>()
{
new ProductParam(){CodeProductParamId =1, Value="Black" },
new ProductParam(){CodeProductParamId =2, Value="Steal"}
};
in sql I can do it by using EXISTS clause twise
SELECT *
FROM Products p
WHERE EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND (pp.CodeProductParamId = 1 AND pp.[Value] = N'Black')
)
AND EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND pp.CodeProductParamId = 2
AND pp.[Value] = N'Steal'
)
How can i get same result by EF methods or linq
Try this:
var products= db.Products.Where(p=>p.ProductParams.Any(pp=>pp.CodeProductParamId == 1 && pp.Value == "Black") &&
p.ProductParams.Any(pp=>pp.CodeProductParamId == 2 && pp.Value == "Steal"));
Update
The problem in work with that list of ProductParam to use it as a filter is that EF doesn't know how to translate a PodructParam object to SQL, that's way if you execute a query like this:
var products2 = db.Products.Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
You will get an NotSupportedException as you comment in the answer of #BostjanKodre.
I have a solution for you but probably you will not like it. To resolve that issue you could call the ToList method before call the Where. This way you will bring all products to memory and you would work with Linq to Object instead Linq to Entities, but this is extremely inefficient because you are filtering in memory and not in DB.
var products3 = db.Products.ToList().Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
If you want filter by one criteria then this could be more simple and you are going to be able filtering using a list of a particular primitive type. If you, for example, want to filter the products only by CodeProductParamId, then you could do this:
var ids = new List<int> {1, 2};
var products = db.Products.Where(p => ids.All(i=>p.ProductParams.Any(pp=>pp.CodeProductParamId==i))).ToList();
This is because you are working with a primitive type and not with a custom object.
I suppose something like that should work
db.Product.Where(x => x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 1) != null && x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 2) != null).ToList();
or better
db.Product.Where(x => x.ProductParams.Any(y => y.CodeProductParamId == 1) && x.ProductParams.Any(y => y.CodeProductParamId == 2)).ToList();
Ok, if you need to make query on parameters in list prodParamCriteria it will look like this:
db.Product.Where(x => prodParamCritria.All(c=> x.ProductParams.Any(p=>p.CodeProductParamId == c.CodeProductParamId && p.Value== c.Value))).ToList();
I forgot that complex types cannot be used in query database, so i propose you to convert your prodParamCriteria to dictionary and use it in query
Dictionary<int, string> dctParams = prodParamCritria.ToDictionary(x => x.CodeProductParamId , y=>y.Value);
db.Product.Where(x => dctParams.All(c => x.ProductParams.Any(p=> p.CodeProductParamId == c.Key && p.Value== c.Value))).ToList();
another variation:
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
ToList();
with a named class like (or maybe without, but not in linqpad)
public class daoClass {
public Product p {get; set;}
public Int32 cs {get; set;}
}
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new daoClass {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
SelectMany(y => y.p).
ToList();

Grouping and flattening list with linq and lambda

I have the following class
public class SolicitacaoConhecimentoTransporte
{
public long ID { get; set; }
public string CodigoOriginal { get; set; }
public DateTime Data { get; set; }
public List<CaixaConhecimentoTransporte> Caixas { get; set; }
}
I would like to know if there is a way of achiveing the same behavior of the code below using Linq (with lambda expression syntax),
List<SolicitacaoConhecimentoTransporte> auxList = new List<SolicitacaoConhecimentoTransporte>();
foreach (SolicitacaoConhecimentoTransporte s in listaSolicitacao)
{
SolicitacaoConhecimentoTransporte existing =
auxList.FirstOrDefault(f => f.CodigoOriginal == s.CodigoOriginal &&
f.Data == s.Data &&
f.ID == s.ID);
if (existing == null)
{
auxList.Add(s);
}
else
{
existing.Caixas.AddRange(s.Caixas);
}
}
return auxList;
In other words, group all entities that have equal properties and flat all lists into one.
Thanks in advance.
Use anonymous object to group by three properties. Then project each group to new SolicitacaoConhecimentoTransporte instance. Use Enumerable.SelectMany to get flattened sequence of CaixaConhecimentoTransporte from each group:
listaSolicitacao.GroupBy(s => new { s.CodigoOriginal, s.Data, s.ID })
.Select(g => new SolicitacaoConhecimentoTransporte {
ID = g.Key.ID,
Data = g.Key.Data,
CodigoOriginal = g.Key.CodigoOriginal,
Caixas = g.SelectMany(s => s.Caixas).ToList()
}).ToList()

Categories

Resources