I have a fairly straight forward requirement - to populate a viewmodel, which has a SelectList as one of its properties - NewOccs is defined on the model as:
public class RatesList
{
[Key]
public long TypeID { get; set; }
public string TypeName { get; set; }
public int TypeCount { get; set; }
public IEnumerable<SelectListItem> NewOccs { get; set; }
}
My controller code to populate it is:
var rooms = dbt.Rooms.Where(r => r.hotel_id == AccID)
.GroupBy(p => p.RoomTypes).Select(g => new RatesList
{
TypeName = g.Key.type_name,
TypeCount = g.Count(),
NewOccs = dbt.Rates.Where(rt => rt.type_id == g.Key.type_id).GroupBy(rt => rt.occ).AsEnumerable()
.Select(proj => new SelectListItem
{
Text = proj.Key,
Value =proj.Key
})
}).ToList();
The Rates table it should be getting its information from is:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
}
How to I access any of the other fields in my Rates table - when I'm populating the SelectList? For example, in VSExpressIDE intellisense only allows me to type proj.Key - the other properties are not there. I want occ to be the key/value and I would like the text to be a concatenation of occ and rate - ie:
Text = proj.occ + ' ' + rate.ToString()
...but rate and occ cannot be found in intellisense.
Thank you, Mark
If you step through your debugger, you'll see that GroupBy() provides a GroupedEnumerable, which contains Keys. The keys are Lookup<string, Rates>, because you used GroupBy on a string.
If you changed your Select to a SelectMany, you'd see all your Rates. But that would defeat the purpose of the GroupBy. I'm not totally sure what you want in the end, but here is a good guide to GroupBy
Like this:
public class Client
{
public int SelectedSexId { get; set; }
public IList<Sex> SexList { get; set; }
public IList<SelectListItem> SexListSelectListItems
{
get
{
SexList=SexList??new List<Sex>();
var list = (from item in SexList
select new SelectListItem()
{
Text = item.Name,
Value = item.Id.ToString(CultureInfo.InvariantCulture)
}).ToList();
return list;
}
set { }
}
}
Related
Basically I have this class
public class Gasto
{
public int IdTienda { get; set; }
public int IdGasto { get; set; }
public System.DateTime Fecha { get; set; }
public string ConceptoDeGasto { get; set; }
public double Total { get; set; }
public string TipoDeGasto { get; set; }
public Nullable<int> IdVenta { get; set; }
public Nullable<System.DateTime> FechaVenta { get; set; }
public virtual Tienda Tienda { get; set; }
}
And I'm trying to build a ViewModelClass like this
public class CorteConVentas
{
// STILL NO ATRIBUTE -- THIS IS THE QUESTION
}
Here is the code for the controller where I will build a List of Gasto grouped by TipoDeGasto
var gastos = db.Gastos.Where(g => g.IdGasto >= corte.DesdeIdGasto && g.IdGasto <= corte.HastaIdGasto).ToList();
var GD = gastos.GroupBy(u => u.TipoDeGasto).Select(grp => new { TipoGasto = grp.Key, gastos = grp.ToList() } ).ToList();
As you can see the variable "GD" is a List of Strings (TipoGasto) with List of Gasto.
¿The issue (question) is this GD how can I declare it as an attribute of my viewModelClass?
I tried something like this for the ViewModel
public class CorteConVentas
{
public List<string, List<Gasto>> listaGastosAgrupada { get; set; }
}
But there is something wrong. The output of the error says:
Using the generic type List requires 1 type arguments
Here is the output after grouping by
Finally the solution as #Ziv Weissman said was not to use an anonymous type
So I created a class like this
public class CorteConVentas
{
public List<GastosAgrupados> listaGastosAgrupada { get; set; }
}
public class GastosAgrupados
{
public string TipoGasto { get; set; }
public List<Gasto> gastos { get; set;}
}
And then in the controller when creating the grouped list I did this
var gastos = db.Gastos.Where(g => g.IdGasto >= corte.DesdeIdGasto && g.IdGasto <= corte.HastaIdGasto).ToList();
var gd = gastos.GroupBy(u => u.TipoDeGasto).Select(grp => new GastosAgrupados { TipoGasto = grp.Key, gastos = grp.ToList()) } ).ToList();
Thanks to all for helping me.
You cannot declare a variable of anonymous type:
.Select(grp => new { TipoGasto = grp.Key, gastos = grp.ToList() } )
You must create another class which has these two props.
(or use a KeyValuePair)
Something like -
.Select(grp => new KeyValuePair<string,List<Gasto>> { Key = grp.Key, Value = grp.ToList() } )
Then you can create a strong typed prop.
public class CorteConVentas
{
List<KeyValuePair<string,List<Gasto>>> PropName {get; set;}
}
Simple answer, just look up GroupBy() in the docs, and see what it returns:
IEnumerable<IGrouping<TKey, TElement>>
which is your case would be:
public class CorteConVentas
{
public IEnumerable<IGrouping<string, Gasto>> listaGastosAgrupada { get; set; }
}
Update:
Didn't notice the select after the GroupBy(). Try this:
public class GrupoGastos // forgive my attempts at Spanish via Google Translate
{
public string TipoGasto {get; set;}
public List<Gasto> Gastos {get; set;}
}
then
var GD = gastos.GroupBy(u => u.TipoDeGasto)
.Select(grp => new GrupoGastos
{ TipoGasto = grp.Key, Gastos = grp.ToList() } )
.ToList();
and finally:
public class CorteConVentas
{
public List<GrupoGastos> listaGastosAgrupada { get; set; }
}
First, looks like you want a dictionary, not a list:
public class CorteConVentas
{
public Dictionary<string, List<Gasto>> dictaGastosAgrupada { get; set; }
}
Second, that select list with two columns is going to give you trouble because it comes out as an anonymous type, and you have no way to declare a dictionary that will contain it. Instead, just return the object:
var GD = gastos.GroupBy(u => u.TipoDeGasto).Select(grp);
So now you have GD which will let you enumerate over a list of grp objects. You can add them to your dictionary like this:
foreach (var grp in GD) dictaGastosAgrupada.Add(grp.Key, grp.ToList());
I have 2 entities, with a 1 to many relationship, and I'm going to switch it to many to many but I need help with grouping and counts.
SearchString -> many JobResults
A SearchSting is used to find job results and job results are stored as a collection property of SearchString:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual SearchString SearchString { get; set; }
}
I get the top 5 JobFunctions of all job results as follows:
var top5jobfunctions = JobSearchResults.Where(a => (a.SearchString != null)).
GroupBy(s => new { s.SearchString.JobFunction.JobFunctionId, s.SearchString.JobFunction.JobFunctionName }).
Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() }).
OrderByDescending(x => x.count).
Take(5).ToList();
I'm going to switch it to many to many as such:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual ICollection<SearchString> SearchStrings { get; set; }
}
How do I get my top 5 jobfunctions counts once I switch it to many to many?
Also, is the structure I chose the right approach? For example I wonder if having jobresults a child collection of SearchString was maybe not the best way to go and that perhaps I should just have SearchStrings be a collection property of JobResult.
For the modified model with many many relationship, consider the following modification to your original query:
var top5jobfunctions =
JobSearchResults.SelectMany(j => j.SearchString.Select(s => new {j,s}))
.Where(j => (j.s != null))
.GroupBy(j => new { j.s.JobFunction.JobFunctionId, j.s.JobFunction.JobFunctionName })
.Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() })
.OrderByDescending(x => x.count)
.Take(5).ToList();
Explanation:
Now since JobSearchResult contains ICollection<SearchString>, it needs flattening to execute a similar query as earlier
SelectMany flattens the data and fills the results as an anonymous type, which contains a record for each SearchString
Henceforth similar logic as you have designed is followed
Model Correctness
I would not prefer, this kind of relationship, as it makes overall querying and data insertion unnecessarily complex
In my understanding a 1 to Many relationship would do as good a job in fetching all the relevant information, in this case you may consider just having ICollection<JobSearchResult> aggregated inside SearchString or vice versa relationship based on suitability, I am not sure what kind of use case does a circular many many relationship model solve.
How can i select all levels of a self-referencing table as a view model. if max level was 2 or 3 then i can do that by calling Select multiple times but i have 4-5 level menus and i think there should be a better solution for doing that and select all levels.
this is my viewmodel:
public class MenuViewModel
{
public MenuViewModel()
{
Childs = new HashSet<MenuViewModel>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public ICollection<MenuViewModel> Childs { get; set; }
}
and this is my Menu class:
public class Menu
{
public Menu()
{
Childs = new HashSet<Menu>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string Description { get; se; }
public byte[] Icon { get; set; }
public int Order { get; set; }
public ICollection<Menu> Childs { get; set; }
}
var viewModel = _dataContext.Menus
.Select(x => new MenuViewModel
{
Id = x.Id,
Title = x.Title,
Child = ???
}
.ToList();
When you are using EF , you can do like following way:
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
//.where ...
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
with this way you don't need to use recursive queries but As far as I know,when use view model for fetch data , the dynamic proxy of EF Is destroyed.
about above example:
just select one list of comments and with
.Where(x=>x.Reply==null).Tolist()
EF fill children property of Comments.
Reference
Assuming that Id property is unique you can do it in two passes:
Create viewmodel items without children, but with associated children ids. From that data create the Dictionary that will allow you to get any viewmodel by its id. Values in this dictionary will be the created viewmodels alongside their children ids.
For each viewmodel item get the associated view model items using the children ids.
Something like:
var tempModels = _dataContext
.Menus
.Select(menu => new
{
childrenIds = menu.Childs.Select(item => item.Id).ToArray(),
viewModel =
new MenuViewModel
{
Id = menu.Id,
Title = menu.Title
}
})
.ToDictionary(
keySelector: item => item.viewModel.Id);
var viewModels = tempModels
.Select(kv =>
{
var viewModel = kv.Value.viewModel;
viewModel.Childs = kv
.Value
.childrenIds
.Select(childId =>
tempModels[childId].viewModel)
.ToList();
return viewModel;
})
.ToList();
for depth problem you can use one int property like Depth in your Model then you can fetch data like this :
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public int Depth{get;set}
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
.Where(a=>a.Depth<2)
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
for using viewModel in this senario , I Test with AutoMapper,but when select data with viewModel , the dyamic proxy that EF generate is Destroyed .
Please Note this Issue
I want to get a summary of all products, as only the latest OrderHistory is of interest where I want to use this. I have thousands of products with hundreds of OrderHistory each, but now I only want the product id and the latest OrderHistory for each product.
public class ProductSummary
{
public int ProductId { get; set; }
public OrderHistory LastOrderHistory { get; set; }
}
The OrderHistory is stored inside the Product document like this:
public class Product
{
public int Id { get; set; }
public int MarketGroupId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<OrderHistory> OrderHistory { get; set; }
}
And this is what OrderHistory looks like:
public class OrderHistory
{
public long OrderCount { get; set; }
public long Volume { get; set; }
public DateTime date { get; set; }
public double AvgPrice { get; set; }
}
Now I've tried a few approaches on the index and query to get this running, this is my latest try, but it returns no results.
public class LatestProductOrderHistory : AbstractIndexCreationTask<Product, ProductSummary>
{
public LatestProductOrderHistory()
{
Map = products => from p in products
from oh in p.OrderHistory
select new
{
ProductId = p.Id,
LastOrderHIstory = p.OrderHistory.OrderByDescending(o => o.date).Last()
};
StoreAllFields(FieldStorage.Yes);
}
}
And finally my query:
var results = session
.Query<ProductSummary, LatestProductOrderHistory>()
.ProjectFromIndexFieldsInto<ProductSummary>()
.Take(1024)
.Skip(start)
.ToList();
This combination gives me no results, I have never made indexes in ravendb before, so I'm sorry if this is a dumb question.
EDIT: Well, I'm not sure what I changed, but now I'm getting "Could not read value for property: Id"
EDIT2: The strange issue in previous edit was solved by restarting vs and ravendb, so current result is no result
As Ayende commented, you must add the Reduce function to your index as:
Reduce = results => from result in results
group result by result.Id into g
select new
{
Id = g.Key,
LastOrderHistory = g.SelectMany(x=> x.LastOrderHistory)
.OrderByDescending(o => o.Date).FirstOrDefault()
};
Just selecting wanted fields in Map function does not make your index Map/Reduce.
Then query your index as:
session.Query<ProductSummary, LatestProductOrderHistory>()
I'm trying to get the count of a dictionary's value collection, where a field of the collection item, matches the key in the collection, as well as the type.
So my collection looks like this, note the object is initialized, this is just pseudo code:
var results = Dictionary<string, List<CourseEnrollmentsByStudentSQLResult>();
Now the CourseEnrollmentsByStudentSQLResult is a data transfer object, that maps to the rows returned from my query.
public class CourseEnrollmentsByStudentSQLResult
{
public int StudentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string College { get; set; }
public int CollegeId { get; set; }
public string Prefix { get; set; }
public string CourseNumber { get; set; }
public string Course { get; set; }
public int CourseId { get; set; }
public string Program { get; set; }
public string Term { get; set; }
public string Status { get; set; }
public List<StudentCoursesSQLResult> Courses { get; set; }
}
You will notice that my CourseEnrollmentsByStudentSQLResult class also has a collection of StudentCoursesSQLResult objects. This is the list I'm trying to get a count of, based on two conditions.
First that the course, which is the string KEY in the dictionary, matches the course of the StudentCoursesSQLResult and then I need to filter on the StudentCoursesSQLResult.Type.
Here is the StudentCoursesSQLResult class:
public class StudentCoursesSQLResult
{
public int StudentID { get; set; }
public int Position { get; set; }
public string Prefix { get; set; }
public string CourseNumber { get; set; }
public string Course { get; set; }
public string Type { get; set; }
}
Now to get the count I'm looping over my dictionary and outputting headers for each key, this is where I'm trying to output the count for the dictionary's value collection.
foreach(var result in results) {
<span class="label label-default label-fat">
#result.Value.SelectMany(r => r.Courses).Count(c => c.Course == result.Key && c.Type == "required")
</span>
}
But for some reason it's returning zero, when I know it shouldn't be.
Can someone please point me in the right direction? Everything I've found so far online has said to use SelectMany, but I'm not having any luck.
Here is a screenshot of the debug output for a single course for the dictionary's value collection, where the Type is "required".
Looking at your screen shot, "Advanced French I: Structure and Expression" doesn't match the key of "Medieval Art and Architecture", which explains the incorrect count.
Instead, filter the CourseEnrollmentsByStudentSQLResult collection to those matching the dictionary key, then select all courses in those matched records, and finally count the courses that are "required".
#result.Value.Where(r => r.Course == result.Key)
.SelectMany(r => r.Courses)
.Count(c => c.Type == "required");
A variation on vivat if you want to do this in a program:
var cnt = selections.Where(r => r.Key == "Items").SelectMany(r => r.Value).Count();
"Items" is the Key value within the collection. In my case I created this collection:
Dictionary<string, string[]> selections = new Dictionary<string, string[]>();
Then added a simple set of values based on a key:
selections.Add("Items", txtItems.Text.Replace("\n", "").Split('\r'));
Later on I wanted to check if there had been any values set so I used the above method to count. It works great and in initial testing it helped me discover I needed to cleanup text entries before setting them up.