Linq where IN using method syntax - linq

I have been struggling trying to get this one to work and my brain is shot...hoping someone can help out.
I have a table called Company which is the main entity returned from the query. The company table is linked to a table called Category through the table CompanyCategory. The category table has 2 fields that are relevant here: Name and Meaning. These fields contain the same data on a row...ie...Name = "Marketing" and Meaning = "MARKETING". This allows me to query by Meaning, while the display name could potentially change in the future. I will always query the category table by Meaning, and then display the Name.
Since a company can contain 1 to many categories, there are times when I want to return all companies that have a set of categories....say...all companies that are tied to marketing or public relations. Here is the query that I have right now that returns all companies with only the data I want.
IQueryable<ICompanyModel> q = base.Context.Set<KD.CompanyAdventures.Data.Implementation.Company>()
.Include(x => x.Address.City.FirstAdminDivision)
.Include(x => x.CompanyBioInfoes)
.Select(x => new CompanyModel
{
CompanyId = x.CompanyId,
Name = x.Name,
BusinessPhone = x.BusinessPhone,
PrimaryEmail = x.PrimaryEmail,
WebsiteUrl = x.WebsiteUrl,
LogoUrl = x.LogoUrl,
Address = new AddressModel
{
AddressId = x.AddressId,
Address1 = x.Address.Address1,
Address2 = x.Address.Address2,
PostalCode = x.Address.PostalCode,
City = new CityModel
{
CityId = x.Address.City.CityId,
Name = x.Address.City.Name,
FirstAdminDivision = new FirstAdminDivisionModel
{
FirstAdminDivisionId = x.Address.City.FirstAdminDivision.FirstAdminDivisionId,
Name = x.Address.City.FirstAdminDivision.Name
}
}
}
});
I am passing a List<string> to the method (GetCompanies) that has this LINQ query and I need to filter the companies that are returned by that list of category meanings that are being passed in. I have been able to get this to work in a test with 2 simple lists using the following (where list 1 = all employees (my wife and kids names) and list 2 is just the names of the my kids):
IQueryable<string> adultEmployees = employees.Where(emp => !kids.Contains(emp.ToString())).AsQueryable();
But I am not able to get this to work with the company and category example as I can't figure out how to drill down to the meaning with complex objects....ie...not list of strings.
Object classes used by the LINQ query look like the following:
CompanyModel [ CompanyId, CompanyName, List<CategoryModel> ]
CategoryModel [ CategoryId, Name, Meaning ]
Any help is greatly appreciated.

Assume that meanings is a list containing "marketing" and "public relations". If I understand this correctly, you can get companies having CategoryModels's Meaning equals "marketing" or "public relations" like so :
companies.Where(c => c.CategoryModels.Any(cm => meanings.Contains(cm.Meaning)))

Related

Returning a list inside Linq query from left outer join

I'm new with Linq and hoping for some clarity on a particular query.
I have two tables (simplified for demonstration):
Table: Customer
CustomerId | Name
1 | John Smith
2 | Peter James
Table: Order
id | CustomerId | Total
1 | 1 | $100
2 | 1 | $200
Sample CustomerDto:
public class CustomerDto
{
public long CustomerId { get; set; }
public string Name{ get; set; }
public CustomerOrder[] CustomerOrderList{ get;set;}
}
Linq example for left outer join in the select here, they return string.empty if the join fails, I'm not sure the equivalent when I'm returning an object rather than a string.
My Linq query looks as follows. I've used the DefaultIfEmpty() to assist in a left outer join, however given I'm dealing with my object, I'm not sure how to return null if there isn't anything.
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale
};
As I mentioned, I want to return a list of orders rather than one row per order. So there should be two records returned (the two customers), one with a list of orders and the other without any.
How do I achieve this?
The first step to make the entities easier to work with is to ensure that navigation properties are set up. If the table is called "Order" then the entity can be Order, or renamed to CustomerOrder if you like. A Customer entity can have an ICollection<Order> collection which EF can automatically map provided a consistent naming convention and normalization is used. (Otherwise explicit mapping can be provided)
For example:
public class Customer
{
[Key]
public int CustomerId { get; set; }
// other customer fields.
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
From here you are projecting Customers to a CustomerDTO, so we should also project the Orders to an OrderDTO. Note that when using navigation properties we don't have to explicitly join entities. We don't even have to eager load related data via Include(). The later would apply if/when we want to work with the entities rather than projections.
The resulting query would end up looking like:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders.Select(o => new OrderDto
{
OrderId = o.OrderId,
Total = o.Total
}).ToList()
});
The benefit is no need to explicitly write Join expressions. EF can help simplify accessing related data considerably rather than just facilitating using Linq as an alternative to SQL. This would return an empty list rather than #null if there are no Orders for that customer. It may be possible to substitute a #null if there aren't any orders, though worst case it could be post-processed after the results are materialized Ie:
var customers = await search.ToListAsync();
var noOrderCustomers = customers.Where(c => !c.Orders.Any()).ToList();
foreach(var customer in noOrderCustomers)
customer.Orders = null;
It really just boils down to whether the consumer is Ok knowing there is always an Orders collection that will be empty if there are no orders, or in the Orders collection is only present if there are orders. (via JSON etc. serialization)
The important details to consider: When filtering, such as filling in search criteria, do this before the Select as the IQueryable is working with entities so you have full access to the table fields. Adding Where clauses after the Select will limit the available fields to the ones you have selected for the DTO. (They will still bubble down into the SQL) There is a ToList inside the Select to build the Orders collection. This may look "bad" that it might be materializing data synchronously, but it will be executed only when the main query is. (Such as an awaited async operation on the IQueryable)
When projecting to DTOs be sure not to mix DTOs and entities such as:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders
});
... which can be tempting. The issue here is that "c.Orders" would return a collection of Order entities. Those Orders may have references to other entities or information you don't need to/want to expose to the consumer. Accessing references could result in lazy load costs, null references, or exceptions (i.e. disposed DbContext) depending on when/where they occur.
just put the ternary condition to achieve it like follows:
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale == null ? null : subCustomerSale // add this line and you will get the null as well if there is no record
};

Linq Table select where IN IQueryable

I have two classes; the first one is:
public class People
{
public int Id {get;set;}
public Dog Dogs {get;set;}
}
public class Dog
{
public int Id {get;set;}
public string Name {get; set;}
public int PeopleId {get;set;}
public bool IsNewborn {get;set;}
}
PeopleId of Dog class is the Id of People class.
Now, with Entity Framework, I retrive the list of Newborn Dogs:
var AllNB_dogs = _dog_repository.Table;
AllNB_dogs = AllNB_dogs.Where(x => x.IsNewborn );
What I need to retrive now, is the list of People that have newborn dogs.
I try with:
var PeopleWithNB = _people_repository.Table.Where(x => AllNB_dogs.Contains(x.Id));
but I know that in "Contains" I cannot put an Int but I need to insert a People object.
I try also with:
var PeopleWithNB = _people_repository.Table.Select(x => ...);
but without success.
Can someone help me? Or there is another way to accomplish this?
I'm using EF Core 2.2.
Assuming you have a relation between People and Dogs, so that you can use Any:
var PeopleWithNB = _people_repository.Table.Where(x => x.Dogs.Any(d => d.IsNewborn)).ToList();
See Relationships in Entity-Framework Core
According to your design every human has exactly one Dog, there are no People without dogs (I'm sure there are a lot of people without dogs), nor people with more than one Dog. I wonder what you would do if someone sells his Dog.
Furthermore, People have a property Dogs, it seems that you wanted to design a one-to-many relationship between People and Dogs. Every Human has zero or more Dogs, every Dog belongs to exactly one Human, namely the Human with PeopleId.
If you really wanted a one-to-one relationship (every Human has exactly one Dog), see below. I'll first handle the one-to-many relation.
One To Many
People have zero or more Dogs, every Dog belongs to exactly one Human, namely the Dog that PeopleId refers to.
Usually tables are identified with plural nouns, rows in tables are identified with singular nouns. Because the word People has some problems when explaining, I'll change it to something more standard. For your final solution the identifiers that you select are not important. Most important is that everybody immediately understands what you mean.
Rewriting your classes:
public class Customer
{
public int Id {get;set;}
public string Name {get; set;}
... // other Customer properties
// every Customer has zero or more Dogs (one-to-many)
public virtual ICollection<Dog> Dogs {get;set;}
}
public class Dog
{
public int Id {get;set;}
public string Name {get; set;}
public bool IsNewborn {get;set;}
... // Other Dog properties
// Every Dog is owned by exactly one Customer, the Customer that OwnerId refers to
public int OwnerId {get;set;}
public virtual Customer Owner {get; set;}
}
In Entity framework, the columns of the tables are represented by the non-virtual properties, the virtual properties represent the relations between the tables (one-to-many, one-to-one, many-to-many, ...)
Note that a foreign key is an actual column in your table, hence it is a non-virtual property.
The -to-many part is best represented by a Collection<...>, not by a IList<...>. The reason for this is that all functionality of a Collection is supported by Entity Framework: Add / Remove / Count. An IList has functionality that is not supported by entity framework, better not offer it then, don't you agree?
For completeness the DbContext:
class MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set; }
public DbSet<Dog> Dogs {get; set;}
}
Most people directly access the DbContext. It seems you have a repository class. Alas you forgot to mention this class. Anyway, you have functions like:
IQueryable<Customer> Customers = ... // query all Customers
IQueryable<Dog> Dogs = ... // query all Dogs
** Back to your question **
Ok, so you have a query to fetch the sequence of several Dogs (in this case: all new born Dogs) and you want the list of Customers that own these Dogs.
If you use entity framework with proper virtual relations, this is quite easy:
// Get all owners of New Born Dogs:
var result = dogs.Where(dog => dog.IsNewBorn)
.Select(dog => new
{
// Select only the Dog properties that you plan to use:
Id = dog.Id,
Name = dog.Name,
...
Owner = new
{
// Select only the properties that you plan to use:
Id = dog.Owner.Id,
Name = dog.Owner.Name,
...
}),
});
In words: from the sequence of Dogs, keep only the newborn dogs. From every remaining Dog, select properties Id, Name. From the Owner of this Dog, select properties Id and Name.
You see: by using proper plural and singular nouns, and by using the virtual relations, the query seems to be very natural.
The problem with this is that, if Customer[4] has two new born dogs, you get this Customer twice. It would be better to query:
Give me all Customers with their newborn Dogs.
Whenever you have a one-to-many relationship, like Customers with their Dogs, Schools with their Students, Orders with their Products, so whenever you want items with their sub-items, it is best to start at the one side (Customers) and use a GroupJoin.
var customersWithTheirNewbornDogs = customers.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
...
NewbornDogs = customer.Dogs.Where(dog => dog.IsNewborn)
.Select(dog => new
{
Id = dog.Id,
...
// not needed, you already know the value:
// OwnerId = dog.OwnerId,
})
.ToList(),
})
// Result: all Customers, each with their newborn Dogs,
// even if the Customer has no newborn Dogs.
// if you only want Customers that have newborn Dogs:
.Where(customer => customer.NewbornDogs.Any());
In words: from every Customer take some properties, and from all Dogs that he owns, keep only the newborn ones. From the remaining ones, take some properties. Finally, keep only Customers that have at least one newborn Dog.
** but I'm using entity framework CORE! **
I've heard from some people who can't use the virtual properties when using EF-core. In that case, you'll have to join the tables yourself:
Starting on the many side: GroupJoin:
// GroupJoin Customers and newbornDogs
var customersWithTheirNewbornDogs = customers
.GroupJoin(dogs.Select(dog => dog.IsNewborn),
customer => customer.Id, // from every Customer take the Id
dog => dog.OwnerId, // from every Dog take the foreign key
(customer, dogsOfThisCustomer) => new // when they match, take the customer and his Dogs
{ // to make one new object
Id = customer.Id,
...
NewBornDogs = dogsOfThisCustomer.Select(dog => new
{
Id = dog.Id,
...
});
});
Or if you want to start on the many side: Join
var newbornDogsWithTheirOwners = dogs.Where(dog => dog.IsNewborn)
.Join(customers,
dog => dog.OwnerId, // from every Dog take the OwnerId,
customer => customer.Id, // from every Customer take the Id,
(dog, owner) => new // when they match, take the dog and the owner
{ // to make one new
Id = dog.Id,
...
Owner = new
{
Id = owner.Id,
...
}
});
One-to-One
If in your world every People has exactly one Dog, there are no People without Dogs, and no one has several Dogs, than you can do the Join similar as above.
(by the way, see how strange it sounds if you don't use proper plurals and singular nouns?)
var newbornDogsWithTheirOwners = dogs.Where(dog => dog.IsNewborn)
.Join(People,
dog => dog.PeopleId, // from every Dog take the foreign key,
customer => customer.Id, // from every Customer take the Id,
(dog, owner) => new // when they match, take the dog and the owner
{ // to make one new
Id = dog.Id,
...
Owner = new
{
Id = owner.Id,
...
}
});

Entity Framework Many-To-Many Join Query

I've got a standard social networking paradigm where a User has a collection of friends who are also users.
I'm using Entity Framwork Code First, and my Friend Relationship is defined as follows:
modelBuilder.Entity<User>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m =>
{
m.ToTable("Friendships");
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
});
What I want to do is to search my users table returning all users with an indicator of whether each returned user is friends with the current user. To be clear, I want to return Users who are Friends and Users who are not friends, but also with a boolean indicating whether each user is a friend. I know how to do this in TSQL its a basic left outer join.
I've seen examples of how to do a left join in LINQ, but all the examples I've seen are joining to a mapped type. My Friendships column doesn't have a Mapped type.
How do I do this in EntityFramework?
var list = context.Users
.Where(u => u.Age >= 20) // sample condition, if you want ALL remove the line
.Select(u => new
{
User = u,
FriendOfCurrentUser = u.Friends.Any(f => f.UserId == currentUserId)
})
.ToList();
Result is a list of anonymous objects containing the user and the boolean friendship indicator. You can also create a helper class UserWithFriendship and project into it (Select(u => new UserWithFriendship { ... }) instead of an anonymous type.

How to combine linq queries and selecting distinct records?

I have an object that can have a single user assigned to it or a work group. A user may be assigned directly or though a work group, but the object can never have both set.
public class Procedure
{
.....
public Guid? AssignedToId {get;set;} //Foreign Key to AssignedTo
public Contact AssignedTo {get;set;} //Single user assignment
public Guid? AssignedWorkGroupId {get;set;} //Foreign Key to AssignedWorkGroup
public WorkGroup AssignedWorkGroup {get;set;} //Multiple user assignment
public Guid? AssignedBuisnessPartnerId {get;set;}
public BusinessPartner AssignedBuisnessPartner {get;set;}
}
I am trying to figure out how to write a single query where I can find procedures where a user may be assigned directly or is part of a work group that is assigned. Currently I have 2 separate queries and combining the lists I get back. Which works, but probably not as efficient.
Here is what I have now:
var procedures = _procedureRepository.Get(p => p.AssignedToId == assignedId).ToList();
procedures.AddRange(_procedureRepository.Get(p => p.AssignedWorkGroup.Contacts.Select(c => c.Id).Contains(assignedId) || p.AssignedBuisnessPartner.Contacts.Select(c => c.Id).Contains(assignedId));
It looks like you are looking for a Union All in sql, which is equivalent to Concat in linq. The following code will only execute one call to the database. Not sure if it will be faster than your current method.
var procedures2 = _procedureRepository.Get(p => p.AssignedWorkGroup.Contacts
.Select(c => c.Id)
.Contains(assignedId) ||
p.AssignedBuisnessPartner.Contacts
.Select(c => c.Id)
.Contains(assignedId));
var procedures = _procedureRepository.Get(p => p.AssignedToId == assignedId)
.Concat(procedures2);

How to get object collection from another collection?

Suppose I have a collection defined as:
IEnumerable<Employee> Employees;
Entity Employee has property Person.
I have loaded Employees from Ria service including Person with eager-loading.
Now I want to get the collection of Person from Employees, something like
IEnumerable<Person> People = Employees.Person;
How to use Linq to get all Person? any other solution for this case?
Unless I'm missing something, it should be as easy as (assuming Person isn't another collection):
var persons = Employees.Select(e => e.Person);
Try the following
IEnumerable<Employee> collection = ...;
IEnumerable<Person> persons = collection.Select(x => x.Person);

Resources