Get objects whose property does not exist in enumerable - c#

Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}

I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F

Related

Select many over complex model and multiple collections

I am trying to flatten out my model and I am coming across an issue I can't seem to understand.
Inspection Item Model:
public class InspectionItem
{
public string InspectionItemName { get; set; }
public ICollection<Inspection> Inspections { get; set; }
}
Inspection Model:
public class Inspection
{
public InspectionItem InspectionItem { get; set; }
public string InspectionItemId { get; set; }
public string PassFail { get; set; }
public ICollection<Answer> Answers { get; set; }
public ICollection<InspectionInputAnswer> InspectionInputAnswers { get; set; }
}
The Query:
So my issue is, if an Inspection does not contain a data for BOTH Answers and InspectionInputAnswers, the whole list returns 0 records? If the Inspection contains a record for both it then returns data. I don't understand why my select many clause is working this way? I am trying to flatten Inspections and both Answers/InspectionInputAnswers.
var inspectionItemInspections = await context.InspectionItems
.Include("Inspections")
.Include("Inspections.Answers")
.Include("Inspections.InspectionInputAnswers")
.Where(a => a.InspectionItemTypeId == idFilter)
.SelectMany(x => x.Inspections
.SelectMany(y => y.Answers
.SelectMany(z => y.InspectionInputAnswers,(answer,inputAnswer) => new { answer, inputAnswer })
.Select(d => new {
Date = y.CreatedAt,
InspectionItemName = x.InspectionItemName,
InspectionItemTypeId = x.InspectionItemTypeId,
InspectedById = y.InspectedById,
PassFail = y.PassFail,
QuestionId = d.answer.QuestionId,
ValueMeasured = d.inputAnswer.ValueMeasured
})
)).ToListAsync();
So how can I write this so that the Inspections relationship does not require an entry for both Answers and InspectionInputAnswers?
Thanks!

Recursive query takes too much time to load data in C#

*I have written a recursive query to get unlimited menu layer. The query works fine providing the exact results but it takes too much time to load. It takes probably 10 to 15 seconds. Please help me if I need to do anything to improve the performance. I have provided all the code to find out the problem. for mapping from entity to view model I have used automapper. *
Entity:
public class Menus
{
public int Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
[ForeignKey("Parents")]
public int? ParentsId { get; set; }
public string Key { get; set; }
public bool? Permitted { get; set; }
public Menus Parents { get; set; }
public ICollection<Menus> Subs { get; set; }
public ICollection<MenusRole> MenusRoles { get; set; }
}
Query:
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext
.Menus
.Include(x => x.Subs)
.Where(x => x.Parents == null)
.Select(f => new Menus
{
Id = f.Id,
Key = f.Key,
Label = f.Label,
To = f.To,
Icon = f.Icon,
ParentsId = f.ParentsId,
Subs = f.Subs
}).ToListAsync();
return Get_all_menus(temp);
}
public List<Menus> Get_all_menus(List<Menus> menus)
{
int z = 0;
List<Menus> menuList = new List<Menus>();
if (menus.Count > 0)
{
menuList.AddRange(menus);
}
foreach (Menus item in menus)
{
Menus menu = ApplicationDbContext
.Menus
.Include(y => y.Subs)
.Where(y => y.Id == item.Id)
.Select(y => new Menus
{
Id = y.Id,
Key = y.Key,
Label = y.Label,
To = y.To,
Icon = y.Icon,
ParentsId = y.ParentsId,
Subs = y.Subs,
Permitted = true
}).First();
if (menu.Subs == null)
{
z++;
continue;
}
List<Menus> subMenu = menu.Subs.ToList();
menu.Subs = Get_all_menus(subMenu);
menuList[z] = menu;
z++;
}
return menuList;
}
In Controller
[HttpGet("get-all-menus")]
public async Task<ActionResult> GetAllMenus()
{
var menus = await _menusService.GetAllMenus();
var menusResources = _mapper.Map<IEnumerable<Menus>, IEnumerable<MenusResourceForSidebar>>(menus);
return Ok(menusResources);
}
View Model
public string Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
public bool? Permitted { get; set; }
public ICollection<MenusResourceForSidebar> Subs { get; set; }
Instead of loading the root menus, then loading the children in separate queries, just load the whole collection in one query, and then populate the navigation links afterwards.
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext.Menus.ToList();
List<Menus> topLevel = new List<Menu>();
foreach (var menu in temp)
{
if (menu.ParentsId == null)
{
topLevel.Add(menu);
continue;
}
var parent = temp.Find(x => x.Id == temp.ParentsId.Value);
if (parent.Subs == null)
parent.Subs = new List<Menus>();
parent.Subs.Add(menu);
}
return topLevel;
}
You should just be able to do:
context.Menus.Include(m => m.Subs).ToList();
The relationship fixup in EFCore will link all the menus together in a tree for you. In later EFs you don't even need the Include..
context.Menus.ToList();
Here is a table in SSMS:
Here is the data:
Here it is chopped up in a paint program and rearranged into a tree:
Here's the scaffolded entity:
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System.Collections.Generic;
namespace ConsoleApp7net5.Models
{
public partial class Menu
{
public Menu()
{
InverseParent = new HashSet<Menu>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public string Label { get; set; }
public virtual Menu Parent { get; set; }
public virtual ICollection<Menu> InverseParent { get; set; }
}
}
Here's what we see after asking EFC (5, in my case) to download it all with just a ToList:
Of course it might make sense to start with a root (or multiple roots but my data only has one)
Don't give classes plural names (Menus), btw, and don't give properties plural names if they aren't collections/enumerables (Parents) - it makes for very confusing code

How do I negotiate joins and groupings based on nested properties in LINQ?

So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}

Loading Nested collection in Entity Framework

I have belowe Classes:
1- PurchaseMaster :Has collection >> PurchaseDetail
2- PurchaseDetail :Has Product
3- Product
I want get PurchaseMaster with Detail and Product . I try with belowe queries but Its return error :
var purchaseMasterModel = _purchaseMasters
.Include("StoreMasters.Details")
.Include("ProductHeader")
.FirstOrDefault(row => row.Code == code);
var purchaseMasterModel = _purchaseMasters
.Include("StoreMasters.Details")
.Include("StoreMasters.Details.ProductHeader")
.FirstOrDefault(row => row.Code == code);
var purchaseMasterModel = _purchaseMasters
.Include("StoreMasters.Details")
.Include("Details.ProductHeader")
.FirstOrDefault(row => row.Code == code);
I getting this errors:
A specified Include path is not valid. The EntityType 'DataLayer.Context.StoreDetail' does not declare a navigation property with the name 'ProductHeader'.
my classes like this :
public class PurchaseMaster:BaseEntity
{
public virtual ICollection<PurchaseDetail> PurchaseDetails { get; set; }
}
public class PurchaseDetail:BaseEntity
{
public PurchaseMaster PurchaseMaster { get; set; }
public Guid PurchaseMasterId { get; set; }
public ProductHeader ProductHeader { get; set; }
public Guid ProductHeaderId { get; set; }
}
public class ProductHeader:BaseEntity
{
public virtual ICollection<PurchaseDetail> PurchaseDetails { get; set; }
}
You are already selecting PurchaseMaster, so you should not specify StoreMaster.
You also have to use the property names. ProductHeader has a property PurchaseDetails, but not Details.
The following query should work:
var purchaseMasterModel = _purchaseMasters
.Include("PurchaseDetails")
.Include("PurchaseDetails.ProductHeader")
.FirstOrDefault(row => row.Code == code);

Clean linq implementation to filter a list by grandchildren

I have a list of Users whom are attached to applications that included clients. I'm looking to filter a list of users by the application and client via Linq and am spinning.
Ideally I'd be using a single statement where Application.Name == "example" that are also in ClientApp.Id == 1.
This is where I'm at thus far but am having some internal brain issues regarding nesting. Any help is appreciated
var users2 = users.Where(x => x.App.Select(y => y.Name).Contains("example"));
public class User
{
public string FirstName { get; set; }
public List<Application> App { get; set; }
}
public class Application
{
public string Name { get; set; }
public List<ClientApp> Client { get; set; }
}
public class ClientApp
{
public string Id { get; set; }
}
You can use nested calls to Enumerable.Any to filter this:
var filtered = users.Where(u =>
u.App.Any(
a => a.Name == "example"
&& a.Client.Any(c => c.Id == 1)));

Categories