Need a alternate approch to do SQL to LINQ conversion - c#

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"],
....

Related

How to get the newly created object inside the .Select() in a LINQ Query

How can I get a reference to the 'parent' object in the Linq below. Something like the way EF does it when you query for objects that are of EF Classes?
void Main()
{
IEnumerable<SomeModel> Brands = ....;
var list = Brands
.Select(b => new BrandModel()
{
ID = b.ID,
BrandName = b.Name,
Locations = b.Locations.Select(l => new LocationModel()
{
ID = l.ID,
LocationName = l.Name,
Brand = *here I would want the Brand object of this Location*
}).ToList()
}).ToList();
}
private class BrandModel
{
public int ID { get; set; }
public string BrandName { get; set; }
public List<LocationModel> Locations { get; set; }
}
private class LocationModel
{
public int ID { get; set; }
public string LocationName { get; set; }
public BrandModel Brand { get; set; }
}
You can create your BrandModel in two steps. First create it without locations, then set locations to it
To do so you need to convert your lambda b => new BrandModel() to block of statements b => { return new BrandModel() }. Try this code:
.Select(b =>
{
var model = new BrandModel
{
ID = b.ID,
BrandName = b.Name
};
model.Locations = b.Locations.Select(l => new LocationModel
{
Brand = model
}).ToList();
return model;
});

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; }
}

Expression-tree to build sub-select results

I'm trying to build a sub-query by using expression-trees. In linq I would write something like:
var single = MyTable
.AsExpandable()
.Select(c => new
{
Childs = Enumerable.Select(
MyTable.VisibleChilds.Invoke(c, dbContext),
cc => Convert(cfg.ChildsConfig).Invoke(dbContext, cc))
});
where the Convert is building an expression like
p => new MyTableSelect {
Id = p.Id,
Name = p.Name
}
depending on the given values from the config (to only read needed data from database).
but I'm struggeling with the second parameter to be passed to the Select call as I need cc to be passed to the Convert-call.
I guess I need something like Expression.Lambda<Func<>> but I don't see it.
Expression.Lambda>(Expression.Invoke(Instance.Convert(cfg.ChildOrganizersFilterConfig), ContextParameter, theEntity));
I am not familiar with your use of Invoke but if you just want to run a 'Converter' in a fluent syntax for use in a Linq Expression I could show you an example of that. Say I have three POCO classes, one parent container, a child container, and a container I want to convert to.
public class POC
{
public int Id { get; set; }
public string Name { get; set; }
public POC(int id, string name)
{
Id = id;
Name = name;
}
}
public class ChildPOC
{
public int ParentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ChildPOC(int parentId, string firstName, string lastName)
{
ParentId = parentId;
FirstName = firstName;
LastName = lastName;
}
}
public class ChildPOCAlter
{
public int ParentId { get; set; }
public string Name { get; set; }
public ChildPOCAlter(string first, string last, int parentId)
{
ParentId = parentId;
Name = $"{first} {last}";
}
}
I could write a converter method to take ChildPOC to ChildPOCAlter like so:
public static Converter<ChildPOC, ChildPOCAlter> ChildPOCOAlter()
{
return new Converter<ChildPOC, ChildPOCAlter>((x) => { return new ChildPOCAlter(x.FirstName, x.LastName, x.ParentId); });
}
I could then populate some data:
var someParents = new List<POC> { new POC(1, "A"), new POC(2, "B") };
var somechildren = new List<ChildPOC> { new ChildPOC(1, "Brett", "x"), new ChildPOC(1, "Emily", "X"), new ChildPOC(2, "John", "Y") };
And then I may want to take these relationships and apply a converter directly on it:
var relationships = someParents.Select(x => new
{
Id = x.Id,
Name = x.Name,
Children = somechildren.Where(y => y.ParentId == x.Id).ToList().ConvertAll(ChildPOCOAlter())
});

Issue Related to SelectMany function in LINQ

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.

what is wrong with my GroupBy Query

Hello all what is wrong with my GroupBy query ?
I have following class:
public class AssembledPartsDTO
{
public int PID { get; set; }
public McPosition Posiotion { get; set; }
public string Partnumber { get; set; }
public string ReelID { get; set; }
public int BlockId { get; set; }
public List<string> References { get; set; }
}
I am trying to perform following query:
assembledPcb.AssembledParts.GroupBy(entry => new
{
entry.PID,
entry.Posiotion.Station,
entry.Posiotion.Slot,
entry.Posiotion.Subslot,
entry.Partnumber,
entry.ReelID,
entry.BlockId
}).
Select( (key , val )=> new AssembledPartsDTO
{
BlockId = key.Key.BlockId,
PID = key.Key.PID,
Partnumber = key.Key.Partnumber,
ReelID = key.Key.ReelID,
Posiotion = new McPosition(key.Key.Station, key.Key.Slot, key.Key.Subslot),
References = val <-- ????
})
But the val that I have there is of type int and not the val of grouping that I can do there val.SelectMany(v => v).ToList(); any idea what is wrong in my code ?
The second parameter of Enumerable.Select is the index of the item in the sequence. So in this case it is the (zero based) number of the group. You just want to select the group, you don't need it's index:
var result = assembledPcb.AssembledParts.GroupBy(entry => new
{
entry.PID,
entry.Posiotion.Station,
entry.Posiotion.Slot,
entry.Posiotion.Subslot,
entry.Partnumber,
entry.ReelID,
entry.BlockId
})
.Select(g => new AssembledPartsDTO
{
BlockId = g.Key.BlockId,
PID = g.Key.PID,
Partnumber = g.Key.Partnumber,
ReelID = g.Key.ReelID,
Posiotion = new McPosition(g.Key.Station, g.Key.Slot, g.Key.Subslot),
References = g.SelectMany(entry => entry.References)
.Distinct()
.ToList()
});
(assuming that you want a list of distinct references)
Side-Note: you have a typo at the property-name: Posiotion

Categories

Resources