How to use Linq join on non key field - linq

Here is a simplified example
public class Book
{
public string BookId
public string AuthorName
}
public class Author
{
public string Name
}
public class BookWithAuthor
{
public Book Book;
public Author Author;
}
For the sake of this example it is not possible to give Author an Id and use this as the foreign key.
List<Book> books = _unitOfWork.BookRepo.Get.ToList();
List<Author> authors= _unitOfWork.AuthorRepo.Get.ToList();
I am trying to write a LINQ join on Author.Name/Book.AuthorName fields and return a List of BookWithAuthor. All my attempts so far failed usually to do with "join parameters could not be determined..."

This should do it:
var results = _unitOfWork.BookRepo.Get
.Join(_unitOfWork.AuthorRepo.Get,
book => book.AuthorName,
author => author.Name,
(book, author) => new BookWithAuthor
{
Book = book,
Author = author
})
.ToList();

Use following
List<Book> books = _unitOfWork.BookRepo.Get.ToList();
List<Author> authors= _unitOfWork.AuthorRepo.Get.ToList();
List<BookWithAuthor> lst =
(from c in books
join d in authors on new {Aname = c.AuthorName } equals new {Aname = d.Name } into cd
from tcd in cd.DefaultIfEmpty()
select new BookWithAuthor
{
Book = c,
Author = tcd
}).ToList();

Related

EF Core many-to-many fetch query

I have the following many-to-many relation modelled
public class Profile
{
ICollection<Category> Categories { get; set;}
// One-To-Many
ICollection<Platform> Platforms { get; set; }
}
public class Category
{
ICollection<Profile> Profiles { get; set; }
}
public class ProfileCategory
{
public int ProfileId { get; set; }
public Profile Profile { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set;}
}
I'm using ASP.NET Core MVC and have a filter view model where a filter on some attributes on profile name and it works.
Trying to filter based on category proved much harder to implement (at least the solution isn't obvious to me :)
From the web the user can select zero, one or many categories to filter on so basically what is sent to my controller is a list of category ids.
IQueryable<Profile> query = _context.Profiles.Include(p => p.Categories).Include(p => p.Platforms);
if(string.IsNullOrEmpty(search.Name))
{
query = query.Where(p => p.Name.IndexOf(search.Name StringComparison.OrdinalIgnoreCase) > 0);
}
if(search.Categories?.Any() != null)
{
query = query.SelectMany(p => p.ProfileCategories)
.Join(search.Categories, pc => pc.CategoryId, cId => cId, (pc,_) => pc.Profile);
}
From this point the Profile object is different and other navigational properties such as Platforms is null hence breaking other parts.
How can I perform the join while retaining the original instance of Profile object. I first thought that they would be the same, but I was wrong.
Currently, EF Core JOINs are not perfect, and I recommend make two queries:
1) Select list of ProfileId (based on category list):
var profileIds = await _context.ProfileCategory
.Where(x => categoryIds.Contains(x.CategoryId)) // filtering goes here
.Select(x => x.ProfileId)
.Distinct()
.ToListAsync();
2) Select required data based on known IDs:
var result = await _context.Profiles
.Include(p => p.Categories).Include(p => p.Platforms)
.Where(x => profileIds.Contains(x.ProfileId))
.ToListAsync();
Yes, this is two queries instead one, but two simple queries, easily optimized using indexes.

IF condition in Linq

how to use If condition in this LINQ
public Student SearchStudentStatus()
{
students=from stud in students
let status=(stud.Status=="PASS")?true:false
return students;
}
If the status is true(PASS), then check for Student's birthday, if it is today then take it otherwise don't take it
So you want to show only students which status is "PASS" and which have birthday today?
public List<Student> SearchStudentStatus()
{
return students
.Where(s => s.Status == "PASS" && s.Birthday.Date == DateTime.Today)
.ToList();
}

Aggregate with OrderBy and ThenBy

I have a list which elements follows a sequence. I want use aggregate with OrderBy and ThenBy
List<string> list = new List<string> {"codCustomer", "customerName", "address1", "address2"};
list = list
.OrderBy(o => o.codCustomer)
.ThenBy(o => o.customerName)
.ThenBy(o => o.address1)
.ThenBy(o => o.addres2)
This list contain many elements and I want use aggregate with OrderBy and ThenBy, but I don't know how do it.
The matter is that this list is passed as a parameter and contains the fields to sort.
You should define a class called Customer with the properties Code, Name, Address1 and Address2 and create a list of customer objects, not a list of strings. Then you can order the list of customers the way you described.
class Customer
{
public string Code { get; set; }
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
}
...
// at some point
List<Customer> customers = new List<Customer>();
customers.Add(new Customer()
{
Code = "cod1",
Name = "nam1",
Address1 = "ad1",
Address2 = "ad2"
};
// ... more customers
// you order customers the way you need
List<Customer> orderedCustomers = customers.OrderBy(p => p.Code).ThenBy(p => p.Name).ToList(); // you can continue ordering by all the properties if you want.

Joining 2 table with LINQ to Entities

I have two tables, for Users and Songs. I need to JOIN them both on the UserID column and return a list to the view. I tried writing an anonymous type but I'm getting an error about:
'Only parameterless constructors and initializers are supported in LINQ to Entities.'
How can I JOIN and return to list?
public class User
{
[Key]
public virtual Guid UserId { get; set; }
[Required]
public virtual String Username { get; set; }
[Required]
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
}
public class Song
{
public int SongId { get; set; }
public virtual Guid UserId { get; set; }
public string SongTitle { get; set; }
}
I tried implementing a method like this one here:
What is the return type of my linq query?
The query I ended up with was:
var query = from u in db.Users
join s in db.Songs
on u.UserId equals s.UserId
select new HomeSongList(s.SongId, s.UserId, u.Username);
return View(query.ToList());
You can't use parameterized constructors but you can use initializers, if you change your select to the following it should work.
select new HomeSongList{SongId=s.SongId, UserId=s.UserId, Username=u.Username};
Note that this will require that HomeSongList has a parameterless constructor and writable properties.
Well the error message is rather clear, no ?
You try to create a new HomeSongList in an linq2entity query (which will be translated in sql : not possible)
With the "User property" correction, you should make
var query = db.Songs.Select(s => new{
songId = s.SongId,
userId = s.User.UserId,
userName = s.User.Username})
.ToList()
.Select(x => new HomeSongList(x.songId, x.userId, x.userName);
or with your actual code
var query = (from u in db.Users
join s in db.Songs
on u.UserId equals s.UserId
select new {
songId = s.SongId,
userId = s.UserId,
userName = u.Username
})//new anonymous object is possible in linq2entities
.ToList()
.Select(x => new HomeSongList(x.songId, x.userId, x.userName);

Deeper level LINQ query in a lambda expression

I would like to get those employees who have a phone number == "666666" using a LINQ query.
These are the class definitions:
public class Employees
{
public List<Phones> Phones{get;set}
}
public class Phones
{
public string Id{get;set;}
public string Number{get;set;}
}
This is my query (my doubt indicated as ???):
var employees= data.GetEmployees()
.Where(e=> e.Phones ???i need to navigate a level below phones ???)
.Select(e => new Employee()
{
Id=e.Id,
Name=e.Name
});
My problem is I don't know how to go a deeper level in this LINQ expression, because in e=>e... I have access to Phones as an IEnumerable but I would like to navigate to Phone's properties.
The easiest way to do this is to use nested LINQ queries. In this case you should look at the Any method.
var employees= data
.GetEmployees()
.Where(e => e.Phones.Any(p => p.Number == "666666"))
.Select(e => new Employee() {
Id = e.Id,
Name = e.Name
});
The parameter passed to the where method is merely a function that returns true or false for each given element, all methods (including LINQ ones (subject to accessing ref/out params etc)) can still be called within it:
var employees= data.GetEmployees()
.Where(e => e.Phones.Any(p => p.Number == "666666"))
.Select(e => new Employee()
{
Id=e.Id,
Name=e.Name
});
var employees= data.GetEmployees()
.Where(e=> e.Phones.Contains(x=>x.Number == "666666"))
.Select(e => new Employee()
{
Id=e.Id,
Name=e.Name
});

Resources