I have a model Group:
public class GroupModel
{
[Key]
public int GroupModelId { get; set; }
[Required]
[MaxLength(50)]
[DataType(DataType.Text)]
public string GroupName { get; set; }
[Required]
public virtual ICollection<FocusArea> FocusAreas { get; set; }
...
And a model Focus:
public class FocusArea
{
public int FocusAreaId { get; set; }
public FocusEnum Focus { get; set; }
public List<ApplicationUser> ApplicationUser { get; set; }
public virtual ICollection<GroupModel> GroupModel { get; set; }
public enum FocusEnum
{
Psych,
Medical,
LivingWith
}
Group and Focus has a many-to-many relationship. My Controller is receiving:
public ActionResult GroupSearch(string[] focusSelected) // Values possible are Pysch, Medical and LivingWith
{
List<GroupModel> groups;
...
Problem: I want to select the groups that have all the focus that are inside the focusSelected array.
What I've tried:
groups = groups.Where(t => t.FocusAreas.Where(x => focusSelected.Contains(x.Focus))).ToList()).ToList();
Obviously not working. Does anyone have another idea?
This may help you
var result = groups.Where(g => g.FocusAreas.All(f => focusSelected
.Any(fs => (FocusEnum)Enum.Parse(typeof(FocusEnum), fs, true) == f.Focus)));
Where needs a delegate / expression that returns bool. In your sample - you are putting Where inside Where, where Where returns collection.
Changing inner Where to All should do the trick:
var allSelParsed = focusSelected.Select(s => (FocusEnum)Enum.Parse(typeof(FocusEnum), s)
.ToList();
groups = groups.Where(gr => allSelParsed.All(selected =>
gr.FocusAreas.Any(fc =>
fc.Focus == selected)))
.ToList();
This should give you expected result
var result = groups.Where(g =>
focusSelected.All(fs =>
g.FocusAreas.Any(fa => fa.ToString() == fs)));
Related
I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});
I got an issue to reclaim Hardware that has exact ID (e.g. ID=5). There is my code:
class HardwareTransfer{
public int Id { set; get; }
public ICollection<Hardware> Hardwares { get; set; }
}
class Hardware{
public int Id { set; get; }
public string Title { set; get; }
}
How to get last HardwareTransfer.Id of HardwareTransfer, that contains Hardwares.Id = 5?
you can use this code
//_listHardwareTransfer is a List Of HardwareTransfer
var maxId=_listHardwareTransfer.Where(x => x.Hardwars.Contains(5)).Max(x => x.Id);
There are several ways how you can obtain this. By using LINQ (preferred way):
myHardwareTransfer.Hardwares.Last(a => a.Id == 5);
In C#:
Hardware lastFound;
foreach(var nHardware in myHardwareTransfer.Hardwares)
if(nHardware.Id == 5)
lastFound = nHardware;
I have a many to many relation set up this way
public class Produto
{
public int ProdutoID { get; set; }
public string Nome { get; set; }
public double Preco { get; set; }
public double Altura {get ; set; }
public double Largura {get ; set; }
public double Profundidade {get ; set; }
public virtual ICollection<ProdutoParte> ProdutoPartes { get; set; }
}
public class Parte
{
public int ParteID { get; set; }
public string Nome { get; set; }
public double Preco { get; set; }
public double Altura {get ; set; }
public double Largura {get ; set; }
public double Profundidade {get ; set; }
public virtual ICollection<ProdutoParte> ProdutoPartes { get; set; }
}
public class ProdutoParte
{
public int ProdutoID { get; set; }
public Produto Produto { get; set; }
public int ParteID { get; set; }
public Parte Parte { get; set; }
}
This is my dbcontext
public DbSet<Produto> Produtos { get; set; }
public DbSet<Parte> Partes {get;set;}
public DbSet<ProdutoParte> ProdutoPartes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProdutoParte>()
.HasKey(pp => new { pp.ProdutoID, pp.ParteID });
modelBuilder.Entity<ProdutoParte>()
.HasOne(pp => pp.Produto)
.WithMany(pr => pr.ProdutoPartes)
.HasForeignKey(bc => bc.ProdutoID);
modelBuilder.Entity<ProdutoParte>()
.HasOne(pp => pp.Parte)
.WithMany(pa => pa.ProdutoPartes)
.HasForeignKey(pp => pp.ParteID);
}
One Produto(product) can be composed of many Parte (parts) and
one Parte (part) can be used to create many Produto(products).
I'm trying to write the following query:
produto/{id}/partes
That returns all the parts being used in the product with {id}
This is what I have now
public IActionResult GetPartes([FromRoute] int id)
{
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Produto produto = _context.Produtos.SingleOrDefault(p => p.ProdutoID == id);
if(produto == null)
{
return NotFound();
}
var parts = _context.ProdutoPartes.Where(p => p.ProdutoID == id);
return Ok(parts);
}
With the following test data (still using inmemorydatabse)
if (_context.Produtos.Count() == 0)
{
_context.Produtos.Add(new Produto() {ProdutoID=1, Nome="product1" });
_context.Partes.Add(new Parte() {ParteID=1, Nome="part1" });
_context.Partes.Add(new Parte() {ParteID=2, Nome="part2" });
_context.Partes.Add(new Parte() {ParteID=3, Nome="part3" });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=1 });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=2 });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=3 });
_context.SaveChanges();
}
However using postman over json all I get is this when calling
get api/produto/1/partes
[{"produtoID":1,"produto":{"produtoID":1,"nome":"product1","preco":0.0,"altura":0.0,"largura":0.0,"profundidade":0.0,"produtoPartes":[
When it should be a list of parts.
Would appreciate any help.
When it should be a list of parts.
You aren't asking for a list of parts: you're asking for a list of ProdutoPartes. There are a few ways you can change your query to get what you want. One is:
var parts = _context.Partes
.Where(p => p.ProdutoPartes.Any(pp => pp.ProdutoID == id))
.ToList();
A few other suggestions:
Create a DTO class that represents just the data you really want to send back as part of your API. Use a .Select() statement to map from your Entities to this DTO. That way you don't get tons more information sent back than you really intended to.
It looks like ProdutoPartes is really just an intermediate mapping table. Remove that from your model, in favor of a many-to-many mapping between Produto and Parte. This way, your query becomes:
var parts = _context.Partes
.Where(p => p.Produtos.Any(pp => pp.ProdutoID == id))
.ToList();
This query will give you all the parts for the specified ProdutoID.
List<Parte> parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = x.Parte).ToList();
Is the data in your Parte class sufficient enough? Does it have all the info the client needs? If yes, then you are good.
If not, create a ParteDto:
public class ParteDto
{
public Parte Parte { get; set; }
// Put other properties here
}
Modify your query:
List<ParteDto> parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = new ParteDto { Parte = x.Parte /* , other properties */ })
.ToList();
To be honest, if you are speaking to the DbContext directly from your controller, you do not even need a DTO and you can just return an anonymous type like below. Please note there is no type after the new keyword:
var parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = new { Parte = x.Parte /* , other properties */ })
.ToList();
Following scenario:
public class User{
public virtual ICollection<MediaItem> MediaItems { get; set; }
}
public enum eMediaItemGenre
{
[Display(Name = "Pop")]
POP = 0,
[Display(Name = "Other")]
OTHER = 11
}
public class MediaItem
{
public virtual ICollection<MediaItemGenre> Genres { get; set; }
}
public class MediaItemGenre
{
[Key]
public Int32 Id { get; set; }
public eMediaItemGenre Genre { get; set; }
public Int32 MediaItemId { get; set; }
public virtual MediaItem MediaItem { get; set; }
}
Now I would like to do the following: I have a MediaItem and I would like to find MediaItems that share the same Genre.
I did it this way:
List<MediaItem> lItems = ltCOntext.MediaItems.Where(x => x.Genres.Any(y => pGenres.Contains(y))).ToList();
but I get an error
Only primitive types or enumeration types are supported in this context.
The problem is that you are trying to compare complex types in database with a list of complex types in memory, this is not possible. What I suggest doing is converting your pGenres to a list of int by using projection:
List<int> pGenresId = pGenres.Select(p => p.Id).ToList();
Then you can use your query:
List<MediaItem> lItems = ltCOntext.MediaItems
.Where(x => x.Genres.Select(g => g.Id).Any(y => pGenresId.Contains(y))).ToList();
Now Linq-to-entities can convert this into a query by putting the int's of the Id's in the queries.
And if you try this variant :
ICollection<MediaItemGenre> pGenres = mediaItem.Genres;
List<MediaItem> lItems =
ltCOntext.MediaItems
.Where(m => m.Genres
.Any(g =>
mediaItem.Genres
.Select(c=>c.Id).Contains(g.Id))).ToList();
The mapping below works, but I was wondering if it can be done with less configuration. I've tried playing around with ForAllMembers and ForSourceMember but I haven't found anything that works so far.
Classes
public class User
{
[Key]
public int ID { get; set; }
public string LoginName { get; set; }
public int Group { get; set; }
...
}
public class UserForAuthorisation
{
public string LoginName { get; set; }
public int Group { get; set; }
}
public class Session
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual User User { get; set; }
...
}
Configuration
Mapper.CreateMap<Session, UserForAuthorisation>()
.ForMember(u => u.LoginName, m => m.MapFrom(s => s.User.LoginName))
.ForMember(u => u.Group, m => m.MapFrom(s => s.User.Group));
Query
UserForAuthorisation user = this.DbContext.Sessions
.Where(item =>
item.ID == SessionID
)
.Project().To<UserForAuthorisation>()
.Single();
Edit This works for the reverse.
Mapper.CreateMap<UserForAuthorisation, User>();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u));
var source = new UserForAuthorisation()
{
Group = 5,
LoginName = "foo"
};
var destination = Mapper.Map<Session>(source);
Unfortunately, Reverse() isn't the easy solution, mapping doesn't work.
Mapper.CreateMap<UserForAuthorisation, User>().ReverseMap();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u)).ReverseMap();
var source = new Session()
{
User = new User()
{
Group = 5,
LoginName = "foo"
}
};
var destination = Mapper.Map<UserForAuthorisation>(source);
I can see only one option to do less configurations. You can use benefit of flattering by renaming properties of UserForAuthorisation class to:
public class UserForAuthorisation
{
public string UserLoginName { get; set; }
public int UserGroup { get; set; }
}
In this case properties of nested User object will be mapped without any additional configuration:
Mapper.CreateMap<Session, UserForAuthorisation>();