query collection for multiple fields with Linq - c#

I have an IEnumerable<Videos> object that I want to search for a single searchterm in multiple fields of the IEnumerable, inlcuding related data (Tags).
The Video, VideoTags and Tag object looks like this
public class Video
{
public Guid Id {get; set;}
public string Name {get; set;}
public string Description {get; set; }
public ICollection<VideoTags> VideoTags {get; set;}
}
public class VideoTags
{
public Guid VideoId {get; set; }
public Video Video {get; set; }
public Guid TagId{get; set;}
public Tag Tag {get; set; }
}
public class Tag
{
public Guid Id {get; set; }
public string TagName {get; set;}
}
How can I efficiently search for a single searchterm in the fields Name, Description and TagName, if any of them contains the searchTerm, and then return a List<Tuple<string, string, string>>.
The code below doesn't work
videos = videos.Where(v => v.Name.Contains(searchTerm) ||
v.Description.Contains(searchTerm) ||
v.VideoTags.Where(t => t.Tag.TagName.Contains(searchTerm))
How can I achieve this?

Add a Select clause to return the Tuple. Also Any is used to validate if video tags contains the search term for TagName.
videos = videos.Where(v => v.Name.Contains(searchTerm) ||
v.Description.Contains(searchTerm) ||
v.VideoTags.Any(t => t.Tag.TagName.Contains(searchTerm)))
.Select(v => new Tuple<string, string, string>(v.Name, v.Description, v.VideoTags.FirstOrDefault(t => t.Tag.TagName.Contains(searchTerm))));
The VideoTags is not a collection so you can Check the search term in TagName by accessing the property directly.

Related

One select list consisting of two classes

I have 3 classes
public class A
{
public decimal id {get; set;}
public virtual B? BNavigation {get; set;};
public virtual C? CNavigation {get; set;};
}
public class B
{
public int id {get; set;}
public string title {get; set;}
[...]
}
public class C
{
public int id {get; set;}
public string title {get; set;}
[...]
}
I try example above, string concatenation, tried to do it with LINQ.
I realized that I need to do this through an auxiliary class.
And i want to make SelectList by class A
Example:
ViewData["A"] = new SelectList( A,"Id", "BNavigation.title - CNavigation.title");
How can i do it ?
I want this post to be closed, because i found block of code which decide my problem
ViewBag.PackageId = db.Packages.Where(p => p.status == "A")
.Select(p => new SelectListItem
{
Text = p.u_package_id + "-" + p.package_nme,
Value = p.u_package_id.ToString()
});

Building includes map using EF Core

Hi I have a simple database, and what I am trying to do is build simple include maps as string using eager loading mechanism in EF CORE.
So in other words mu db models looks like:
And models that are supporting them:
public class StartTable
{
public int Id {get; set;}
public ICollection<TableA> TableA {get; set;}
public ICollection<TableB> TableA {get; set;}
}
public class TableA
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableAChild TableAChild {get; set;}
public int TableAChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TableB
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableBChild TableBChild {get; set;}
public int TableBChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TablAChild
{
public int Id {get; set;}
public TableA TableA {get; set;}
}
public class TableBChild
{
public int Id {get; set;}
public TableB TableB {get; set;}
}
public class TableAB
{
public int Id {get; set;}
public TableA TableA {get; set;}
public TableB TableB {get; set;}
}
I think relations are readible from models. Now I just want to create a map, that is I want to select start table and with include of all branches so final include path should looks like:
_context.StartTable.Include("StartTable.TableA.TableAChild")
_context.StartTable.Include("StartTable.TableA.TableAB")
_context.StartTable.Include("StartTable.TableB.TableBChild")
_context.StartTable.Include("StartTable.TableB.TableAB")
And If I type this manually it works, but this will grow a lot so I don't want to update this every time something will come up, I tried AutoInclute() in context on main table but it includes only 1 level down.
I thought I can create some sort of map function that looks like:
private static IEnumerable<string> BuildIncludeTree(DbContext context, Type type)
{
var entityAssemblyTypes = Assembly.GetExecutingAssembly().GetReferencedAssemblies().SelectMany(assembly => Assembly.Load(assembly).GetTypes());
void AddAssetByString(ref HashSet<string> navigation, List<string> createdPaths)
{
foreach (var path in createdPaths)
{
var splitPath = path.Split('.');
var relationNavigationNode = splitPath.Last();
var parentNavigationType = entityAssemblyTypes.FirstOrDefault(t => t.Name == relationNavigationNode);
if (parentNavigationType == null)
{
throw new ArgumentException($"Unknown type parent: {relationNavigationNode}");
}
var parentNodesProperties =
parentNavigationType.GetProperties().Where(prop => !prop.PropertyType.IsSimple() && !splitPath.Contains(prop.Name)).ToArray();
if (!parentNodesProperties.Any())
{
navigation.Add(path);
continue;
}
navigation.Add(path);
AddAssetByString(ref navigation, parentNodesProperties.Select(prop => $"{path}.{prop.Name}").ToList());
}
}
IEntityType entityType = context.Model.FindEntityType(type);
if (entityType == null) throw new ArgumentException($"Unknown entity type {type.Name}");
var navigationsByString = new HashSet<string>();
var relationsByString = entityType.GetNavigations().Select(nav => $"{type.Name}.{nav.Name}");
AddAssetByString(ref navigationsByString, relationsByString.ToList());
return new List<string>();
}
But problem here is relation to TableAB, I mean when I get to mapping this part function goes circular and creates map:
StartTable.TableA.TableAChild.TableB.StartTable.TableA ... and so on
Can this be prevented and what am I missing?
Can EF Core detect in some sort of way navigation downwards and upwards?
Or is there any other and simpler way to do that?
You can't Include all .There is already post about that here
Is there a way to Include() all with dbcontext?
But if correctly understand you can use Linq to make you code shorter like:
_context.StartTable.Include(x => x.TableA.TableAChild && x.TableA.TableAB && x. ....)
And in that case you will need to add entities you want to include in your class also you can use [NotMapped] attribute if you working direct with your class instead of dto. So in that way you can access the mapped entities direct from the class like class.TableAChild
+
public virtual TableAChild TableAChild { get; set; }
public virtual TableAB TableAB { get; set; }
Greetings and good luck

Mapping entity to dto with interfaces

I've my entity class I retrieve from database:
public class User{
public string Username {get; set;}
public List<IAddress> Addresses {get; set;}
}
public class Address: IAddress{
public string Line1 {get; set;}
public string Line2 {get; set;}
}
public class AddressExtended:Address, IAddress{
public string Line3 {get; set;}
public string Line4 {get; set;}
}
public interface IAddress{
}
I use Automapper to map this entity to the mirrored DTO:
public class UserDto{
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("addresses")]
public List<IAddressDto> Addresses { get; set; }
}
public class AddressDto: IAddressDto{
[JsonProperty("line1")]
public string Line1 { get; set; }
[JsonProperty("line2")]
public string Line2 { get; set; }
}
public class AddressExtendedDto:AddressDto, IAddressDto{
[JsonProperty("line3")]
public string Line3 { get; set; }
[JsonProperty("line4")]
public string Line4 { get; set; }
}
public interface IAddressDto{
}
Automapper configuration is the following:
CreateMap<IAddress, IAddressDto>();
CreateMap<Address, AddressDto>();
CreateMap<AddressExtended, AddressExtendedDto>();
The problem is that when I run my application, if in the entity I have 2 addresses and 1 addressExtended, in DTO the Addresses property () is mapped like this:
[
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>},
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>},
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>}
]
The Username property it is correctly valued.
What I miss?
UPDATE
I added a fiddler here:
https://dotnetfiddle.net/ZkUZgp
As per my knowledge, one approach solving the issue is using Construct using below code
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<AddressExtended, AddressExtendedDto>();
cfg.CreateMap<IAddress, IAddressDto>().ConstructUsing((IAddress addressDto) =>
{
if (addressDto is AddressExtended) return Mapper.Map<AddressExtendedDto>(addressDto);
return Mapper.Map<AddressDto>(addressDto);
});
Edit 1:
Here is the final answer and it solves your problem
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<AddressExtended, AddressExtendedDto>();
cfg.CreateMap<IAddress, IAddressDto>().ConstructUsing((addressDto, ctx) =>
{
var destination = Mapper.Instance.ConfigurationProvider.GetAllTypeMaps()
.First(t => t.SourceType == addressDto.GetType());
return ctx.Mapper.Map(addressDto, addressDto.GetType(), destination.DestinationType) as IAddressDto;
});
Instead of getting destination type using LINQ you can build a dictionary and get from it for faster execution.

lambda expression path through Collection Property

I need to get all Students which have Registration on specyfic Realisation.
I was thinking it would be s => s.Registrations.RealisationId == realisationId but it doesn't work :). I'm trying to make this like in the example code but I'm getting:A lambda expression with a statement body cannot be converted to an expression tree. I have no idea how can I write this expression correctly, can anyone help me with this?
I couldn't figure out how to title this question better, sorry.
Database:
public class Student : BaseEntity {
public int StudentId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
public class Registration : BaseEntity {
public int RegistrationId {get; set;}
public int StudentId {get; set;}
public int RealisationId {get; set;}
public Student Student {get; set;}
public Realisation Realisation {get; set;}
}
public class Realisation : BaseEntity {
public int RealisationId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
My try:
public IEnumerable<Student> GetByRealisationId(int realisationId) {
return Context.Set<Student>().Where(s => {
foreach(Registration r in s.Registrations) {
if (r.RealisationId == realisationId)
return true;
}
return false;
});
}
You'll need to select the ID's out and use Contains:
return Context.Set<Student>()
.Where(s => s.Registrations
.Select(r => r.RealisationId)
.Contains(realisationId));
Generally this is converted to a WHERE IN clause.

return a model with .Include to view

I have the following item model:
public class Item
{
public int Id {get; set;}
public string name {get; set;}
public virtual ICollection<Comment> Comments {get; set;}
public virtual Comment Comment {get; set;}
}
I have the following User model:
public class User
{
public int Id {get; set;}
public string UserName {get; set;}
public string email {get; set;}
}
And I have the following Comment model:
public class Comment
{
public int Id {get; set;}
public string text {get; set;}
public DateTime DateCreated {get; set;}
public int ItemId {get; set;}
[ForeignKey("ItemId")]
public virtual Item Item {get; set;}
public int UserId {get; set;}
[ForeignKey("UserId")]
public virtual User User {get; set;}
}
In my onModelCreating Context I have
modelBuilder.Enitity<Item>().HasOptional(c=>c.Comment).WithMany();
My aim is to return to a view the items which only the requesting user has commented on.
So far I have done:
int UserId = db.Users.Where(u=>u.UserName.Equals(User.Identity.Name))
.Select(u=>u.Id).FirstOrDefault();
var itemsWithComments = db.Items.Include(c=>c.Comments).......
At this point I want to be able to say: Select the items which the UserId == Comments.UserId, return the Items as a list.
And my View (using Razor)
#model IEnumerable <project.Models,Item>
#foreach (var item in Model)
.....
.....
Any help is much appreciated.
If you need me to clarify any point(s) please ask.
Kind regards
Assuming you want to filter only the comments from a specific user, you can't use Include() but you can use a projection to select a filtered list.
var itemsWithComments = db.Items.Select(o => new
{
Item = o,
Comments = o.Comments.Where(c => c.UserId == userId)
});
If the user is attached to the item itself then the query is simple:
var itemsWithComments = db.Items.Include(o => o.Comments).Where(o => o.UserId == userId);
One thing to remember - calling Select() will almost always reset any Include() calls before it so query.Include().Select() will not have the include while query.Select().Include() will.

Categories

Resources