how to assign collection of classes to a new viewmodel - c#

I have this scenario:
I want to make a ViewModel with the property that I only want but my issue is with the Collections. So here's an example:
Main classes:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
// remove some code for brevity
}
public class Order
{
public int Id { get; set; }
public string ItemName { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
// remove some code for brevity.
}
View Models:
public class OrderVM
{
public string ItemName { get; set; }
}
public class CustomerVM
{
public string Name { get; set; }
public ICollection<OrderVM> Orders { get; set; }
}
Then I tried something like this:
_customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders.Select(order => new OrderVM { ItemName = order.ItemName }) // but it says cannot convert implicitly from IEnumerable to ICollection
}
)
In a nutshell, how can I populate CustomerVM's properties? I only want to select that I want. Any thoughts? Really stuck here.

Linq .Select() generates IEnumerable<T> but your property is ICollection<T>. You can append .ToList() to the query to generate List<T> which is ICollection<T>.
customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders
.Select(order => new OrderVM { ItemName = order.ItemName }).ToList()
}
Alternatively, if you do not specifically need Orders to be ICollection, then you could change your property definition to
public IEnumerable<OrderVM> Orders { get; set; }

just use ToList() to convert IEnumerable to ICollection. Not tested but it should work
customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders.Select(order => new OrderVM { ItemName = order.ItemName }).ToList()
}

Related

Using LINQ SelectMany() to join multiple child tables without a navigation property

I kind of got confused on how to achieve what I want, and after searching in the internet, I think SelectMany() is the way to go, but I am getting lost on how to make this work (I have very weak understanding of how lambda expressions work I think)..
My goal is to be able to use LINQ to populate this class:
public class AttendanceList
{
public int AttendancePeriodId { get; set; } // from AttendancePeriod class
public int Activity { get; set; } // from DailyAttendance class
public string Name { get; set; } // from Employee class
public string Position { get; set; } // from Employee class
public string Department { get; set; } // from Employee class
}
I have a totally wrong and non-working code, but to illustrate, I wanna use something like:
var query = context.AttendancePeriod
.Include(i => i.DailyAttendance)
.Include(i => i.DailyAttendance).ThenInclude(ii => ii.Employee)
.Select(s => new AttendanceList
{
AttendancePeriodId = s.Id,
Activity = ...,
Name = ...,
Position = ...,
Department = ...
});
How do I use SelectMany() to achieve the above?
For reference, these are my classes:
public class AttendancePeriod
{
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public ICollection<DailyAttendance> DailyAttendances { get; set; }
}
public class DailyAttendance
{
public int Id { get; set; }
public Employee Employee { get; set; }
public TimeSpan TimeIn { get; set; }
public TimeSpan TimeOut { get; set; }
public string Activity { get; set;}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public string Department { get; set; }
}
Untested and without any null checking:
var query = context.AttendancePeriod
// .Include(i => i.DailyAttendance)
// .Include(i => i.DailyAttendance).ThenInclude(ii => ii.Employee)
.SelectMany(s => s.DailyAttendances.Select(a =>
new AttendanceList
{
AttendancePeriodId = s.Id,
Activity = a.Activity ,
Name = a.Employee.Name,
Position = a.Employee.Position,
Department = a.Employee.Department,
}));
Maybe you are looking for this
First step get a flat list of all DailyAttendances
.SelectMany(x => x.DailyAttendances)
Now transform those into AttendanceList
.Select(x => new AttendanceList
{
//x is of type `DailyAttendance`
AttendancePeriodId = x.AttendancePeriod.Id,
Activity = x.Activity,
Name = x.Employee.Name,
Position = x.Employee.Position,
Department = x.Employee.Department,
}
If DailyAttendance doesn't have a member for AttendancePeriod you could do the following, instead of
.SelectMany(x => x.DailyAttendances)
use this, this will create a tuple contains x = AttendancePeriod and y = DailyAttendance
.SelectMany(x => x.DailyAttendances.Select(y => (x, y))
and now transform it to this
.Select(x => new AttendanceList
{
//x is of type `ValueTuple<AttendancePeriod, DailyAttendance>`
//x.Item1 is of type AttendancePeriod
//x.Item2 is of type DailyAttendance
AttendancePeriodId = x.Item1.Id,
Activity = x.Item2.Activity,
Name = x.Item2.Employee.Name,
Position = x.Item2.Employee.Position,
Department = x.Item2.Employee.Department,
}

LINQ. Why full load only the first time?

I have an entity Contracts, ListKindWorks and KindWorks.
public partial class Contracts
{
public Contracts()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
...
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
public partial class ListKindWorks
{
public int IdContract { get; set; }
public int IdKindWork { get; set; }
public virtual Contracts IdContractNavigation { get; set; }
public virtual KindWorks IdKindWorkNavigation { get; set; }
}
public partial class KindWorks
{
public KindWorks()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
And class Item
public class Item
{
public int Id { get; set; }
public string Value { get; set; }
}
I want to load related elements via method Load():
source = model.Contracts
.OrderByDescending(r => r.Id)
.Skip(Page * Size)
.Take(Size)
.Select(c => new ContractTableRow
{
IdContract = c.Id,
FullName = c.WorkerNavigation.FullName,
IdWorker = c.Worker,
...
// this code
KindWork = c.ListKindWorks
.Select(y => new Item
{ Id = y.IdKindWork, Value = y.IdKindWorkNavigation.Short })
.ToList(),
Subject = c.ListSubjects
.Select(y => new Item
{ Id = y.IdSubject, Value = y.IdSubjectNavigation.Short })
.ToList()
})
.ToList();
The first call of the method Load() (during load app) gives full elements. KindWork and Subject are not empty. But then when I change Page, KindWork and Subject are empty. The rest is always changing.
Why don't subsequent calls load this part?
Use variable source as IQueryable so every time you use .ToList() query will be executed against database so you will get your changed data.
IQueryable<Contracts> source = *...* // without .ToList()
This will save this as a query (without data, as it is not executed yet on db)
Every time you call .ToList() EF will execute on sql.
var smth = source.ToList();

How to group by a list of objects and declare this list as an attribute of a viewmodel

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());

Select from self-referencing table and convert to viewmodel

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

Entity Framework Select Query

I have the following classes:
public class Seller : Entity
{
public int SellerId { get; set; }
public string Name { get; set; }
public ICollection<InventoryItem> InventoryItems { get; set; }
}
public class InventoryItem : Entity
{
public int InventoryId { get; set; }
public int SellerId { get; set; }
public string SellerSku { get; set; }
public ICollection<SiteInventoryItem> SiteInventoryItems { get; set; }
}
public class SiteInventoryItem : Entity
{
public int Id { get; set; }
public int InventoryId { get; set; }
public SiteType Site { get; set; }
}
So a Seller has a collection of InventoryItem which in turn have a collection of SiteInventoryItem.
How do I get a Seller with a list of InventoryItems that have a list of SiteInventoryItems where the SiteType == SiteType.SiteName and the Seller.SellerId == 14?
The SiteType is an Enum and the db type is int.
I have tried the following and it compiles and runs, but the result is not what is expected:
var seller = repo.Query(
x => x.InventoryItems,
x => x.InventoryItems.Select(y => y.SiteInventoryItems)
).Select(x => new
{
Seller = x,
InventoryItems = x.InventoryItems,
SiteInventoryItems = x.InventoryItems.Select(y => new
{
InventoryItem = y,
SiteInventoryItems = y.SiteInventoryItems.Where(z => z.Site == SiteType.Amazon)
})
}).Where(x => x.Seller.SellerId == 14).First();
var seller = repo.Query()
.Select(x => new
{
Seller = x,
InventoryItems = x.InventoryItems.Select(y => new
{
InventoryItem = y,
SiteInventoryItems = y.SiteInventoryItems.Where(z => z.Site == SiteType.Amazon)
}).Where(y => y.SiteInventoryItems.Any())
}).Where(x => x.Seller.Id == 14).First();
I've restructured the result type a bit, because I think it makes more sense this way. Now you see that the result only returns Sellers that have InventoryItems that have SiteInventoryItems.
Note that I also removed the arguments for repo.Query(). I assume that they serve as Includes. But I also hope that repo.Query() returns an IQueryable, so that the Where clause will be translated into the generated SQL. If so, the Includes are useless, because you're already querying the included entities because they are part of the anonymous type.

Categories