How to fill NotMapped property with count in Linq? - c#

I have added the following property to PassAllocations class :
[NotMapped]
public int PendingForApprovalCount { get; set; }
var passAllocations = db.PassAllocations.Include(p => p.PassRule)
.Where(p => p.CompanyID == company.CompanyID).ToList();
I am trying to fill each PendingForApprovalCount property in list passAllocations with result of db.PassLinkings.Where(p => p.PassAllocationID == parentid and p.status="Pending").ToList()
Where 'parentid' is PassAllocationID in each passAllocations list

passAllocations
.ForEach(x=>x.PendingForApprovalCount=db.PassLinkings
.Where(p => p.PassAllocationID == x.id &&
p.status="Pending")
.Count())
It might give you correct result but this is not at all advisable solution as it will led to multiple hits to db ,which will eventually hamper performance. A better solution may be this one
var desiredResult=(from a in db.PassAllocations
.Include(pa => pa.PassRule)
.Where(pa => pa.CompanyID == company.CompanyID)
join b in db.PassLinkings
.Where(pl => pl.status="Pending")
.GroupBy(pl=>pl.PassAllocationID)
on a.PassAllocationID equals b.Key
select new {a,b})
.Select(x=>new PassAllocation
{
PassAllocationID=x.a.PassAllocationID,
.
.
. <------------Other Properties
.
.
PendingForApprovalCount=x.b.Count()
}).ToList();
Hope it helps.

Related

The filters navigation Only one unique filter per navigation is allowed

Getting Exception when using .Include().ThenInclude().ThenInclude() with condition to match exact values. Below is the sample query I am trying with.
public Task<TestModel> Test(int id)
{
return dbContext.Tables
.Include(i => i.A).ThenInclude(s => s.AList.Where(c => c.id == 1).Take(1)).ThenInclude(c => c.B)
.Include(i => i.A).ThenInclude(s => s.AList.Where(c => c.id == 2).Take(1)).ThenInclude(x => x.C)
.Where(i => i.id == id)
.FirstOrDefaultAsync();
}
Getting below error with this EF query.
InvalidOperationException: The filters 'navigation .Where(c => (int)c.id == 1) .Take(1)' and 'navigation .Where(c => (int)c.id == 2) .Take(1)' have both been configured on the same included navigation.
Only one unique filter per navigation is allowed.For more information on including related data
I have to select data from specific table (B or C) based on condition. How can this query be corrected.
My recommendation would be that this would be best served by a projection rather than trying to filter domain models. Entities should always reflect a complete data state or a complete-able data state. (i.e. lazy load-able proxies) Loading entities in a partially complete state (some associated entities, but not all, and no fall-back to lazy load) can easily lead to confusion in the code when dealing with methods that accept entities as parameters. Are they getting a complete TableA, or a partially filled in one?
If this is data relevant to a particular View for example:
[Serializable]
public class ParticularTableAViewModel
{
public int TableAId{ get; set; }
public string FirstRelevantBName { get; set; }
public string SecondRelevantBName { get; set; }
public string SomethingFromFirstC { get; set; }
public string SomethingFromSecondD { get; set; }
}
Then populating the view model via projection:
var viewModel = dbContext.TableAs
.Where(a => a.Id == id)
.Select(a => new ParticularTableAViewModel
{
TableAId = a.Id,
FirstRelevantBName = a.TableABs
.Where(ab => ab.bId == 1)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => ab.TableB.Name)
.FirstOrDefault(),
SecondRelevantBName = a.TableABs
.Where(ab => ab.bId == 2)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => ab.TableB.Name)
.FirstOrDefault(),
SomethingFromFirstC = a.TableABs
.Where(ab => ab.bId == 1)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => ab.TableB.TableC.Something),
.FirstOrDefault(),
SomethingFromSecondD = a.TableABs
.Where(ab => ab.bId == 1)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => ab.TableB.TableD.Something)
.FirstOrDefault()
}.SingleOrDefaultAsync();
There are a number of assumptions to make from the example provided, but a few things to be cautious of: When using methods like FirstOrDefault or Take you should always provide an OrderBy clause to ensure results are predictable & repeatable. The projected view model can flatten details like above or contain related view models to form a structure suitable for the view to iterate through. Avoid mixing view models and domain models though, such as creating a view model that contains properties that are set to Entities. In situations where you want to pull back several complex details from entities prior to populating a view model you can opt for two passes, one to select an anonymous type with an applicable state to pull from into memory, then a second Select to build the view model. This can cut down on repeated filtering statements when pulling various related details. (Whatever ends up easier to read and understand down the road)
var data = await dbContext.TableAs
.Where(a => a.Id == id)
.Select(a => new
{
TableAId = a.Id,
FirstBDetails = a.TableABs
.Where(ab => ab.bId == 1)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => new
{
Name = ab.TableB.Name,
SomethingFromC = ab.TableB.TableC.Something
}).FirstOrDefault(),
SecondBDetails = a.TableABs
.Where(ab => ab.bId == 2)
.OrderBy(ab=>ab.CreatedAt)
.Select(ab => new
{
Name = ab.TableB.Name,
SomethingFromD = ab.TableB.TableD.Something
}).FirstOrDefault(),
}.SingleOrDefaultAsync();
if (data != null)
{
return data.Select(x => new ParticularTableAViewModel
{
TableAId = x.TableAId,
FirstRelevantBName = FirstBDetails.Name,
SecondRelevantBName = SecondBDetails.Name,
SomethingFromFirstC = FirstBDetails.SomethingFromC,
SomethingFromSecondD = SecondBDetails.SomethingFromD
}).Single();
}

Finding the most specific matching item

User input will be like 'BY1 2PX', which will split and stored into list like below
var items = new List<string> {'BY1 2PX', 'BY12', 'BY1', 'BY'};
I have source list of Products
public class Product
{
public string Name {get;set;}
public string Id {get;set;}
}
Below is a sample product list. There is no guarentee on ordering, it could be in any order.
var products = new List<Product>{
new Product("1", "BY1 2PX"),
new Product("2", "BY12"),
new Product("3", "BY1"),
new Product("4", "BY"),
new Product("5", "AA2 B2X"),
//...etc
}
my output should fetch 1, because its most specific match. If Id = 1 is not there then it should have fetched Id =2 like that...etc Could anyone help me in writing a linq query. I have tried something like below, is this fine?
var result = items.Select(x => products.FirstOrDefault(p =>
string.Equals(p.Name.Trim(), x, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
Well, you can use dictionary with its fast lookups :
var productsDict = products.ToDictionary(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.ContainsKey(i));
Product result = key != null ? productsDict[key] : null;
Or as Tim suggested, if you have multiple elements with same names you can use Lookup :
var productsDict = products.ToLookup(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.Contains(i));
Product result = key != null ? productsDict[key] : null;
If you want to select the best-matching product you need to select from the product- not the string-list. You could use following LINQ approach that uses List.FindIndex:
Product bestProduct = products
.Select(p => new {
Product = p,
Index = items.FindIndex(s => String.Equals(p.Name, s, StringComparison.OrdinalIgnoreCase))
})
.Where(x => x.Index != -1)
.OrderBy(x => x.Index) // ensures the best-match logic
.Select(x => x.Product)
.FirstOrDefault();
The Where ensures that you won't get an arbitrary product if there is no matching one.
Update:
A more efficient solution is this query:
Product bestProduct = items
.Select(item => products.FirstOrDefault(p => String.Equals(p.Name, item, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault(p != null); // ensures the best-match logic
You can try to find resemblance of words by using a specific algorythm called Levenshtein's distance algorythm, which is mostly used on "Did you mean 'word'" on most search websites.
This solution can be found here:
https://stackoverflow.com/a/9453762/1372750
Once you find the distance difference, you can measure which word or phrase is more "like" the searched one.
This will find for each product what is the "most specific" (the longest) match in items and will return the product with the longest match (regardless to order of either of the collections)
var result = products
.Select(p => new
{
Product = p,
MostSpecific = items.Where(item => p.Name.Contains(item))
.OrderByDescending(match => match.Length
.FirstOrDefault()
})
.Where(x => x.MostSpecific != null)
.OrderByDescending(x => x.MostSpecific.Length)
.Select(x => x.Product)
.FirstOrDefault();

Entity Framework filter nested collection

I have a entity relation diagram as follows.
ClassEntity:
public int id
public int std
public virtual ICollection<StudentEntity> students
StudentEntity:
public int id
public string name
public string gender
public virtual ClassEntity class
public virtual StudentAddressEntity studentAddress
StudentAddressEntity:
public int id
public string address
I need to get the class and its male children.
var classEntity = dbContext.Set<ClassEntity>().Where(t => t.id == classId);
var query = classEntity.Include(c => c.students.Select(s => s.studentAddress))
.FirstOrDefault(c => c.students.Any(s => s.gender == GenderEnum.Male));
But it is returning the class with all the students. How to filter only male students?
I have used joins to accomplish similar results in the past. For eg I've accounts that have addresses nested (1:M). If I want to get, say, all the accounts that belong to a particular country, I would use joins as below:
(from a in accountRepo.GetAll()
join aa in accountAddressRepo.GetAll() on a.AccountId equals aa.AccountId
join ad in addressRepo.GetAll() on aa.AddressId equals ad.AddressId
where ad.CountryId == codeCountryId
select a).ToList();
If you are not using repository pattern you can simply replace accountRepo.GetAll() with DbContext.Set().
In your case you should be able to join Student, Address and Class entities and get similar results. Something like below should work for you:
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();
please note this is a simple representation based on my understanding of your database and entity names. You may need to tweak this query a bit to make it compilable but the underlying idea should work for you. Please let me know how did it work for you.
You intentionally "can't" do this directly with the EF proxies. For example, consider what would happen when you tried to call SaveChanges() and all of the female students are missing from ClassEntity.Students!
Instead, the usual thing to do if you're just displaying data is to project onto an anonymous type or a DTO, e.g.:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new // I'm using an anonymous type here, but you can (and usually should!) project onto a DTO instead
{
// It's usually best to only get the data you actually need!
Id = x.Id
Students = x.Students
.Where(y => y.Gender == GenderEnum.Male)
.Select(y => new { Name = y.Name, ... })
});
Or, if you desperately need to make changes and save them:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new
{
Class = x,
MaleStudents = x.Students.Where(y => y.Gender == GenderEnum.Male)
});
I quite strongly recommend the former unless there's no way around it. It's really easy to introduce bugs if you're making changes to filtered data and trying to save it.
The below should load only the male students for each class.
var classEntity = testContext.Set<ClassEntity>().Where(t => t.Id == classId);
var classes = classEntity.ToList().Select(c =>
{
testContext.Entry(c)
.Collection(p => p.Students)
.Query()
.Where(s => s.Gender == GenderEnum.Male)
.Load();
return c;
});
I think join is the right way to go about it as suggested by Manish Kumar.
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();

LINQ to SQL querying across relationships

This is driving me mad. I thought it seemed simple enough but the below is returning a list of IEnumerable containing the entities I need, instead of just a list of entities:
db.tblPeople.Where(p => p.id == id).Select(s => s.tblCars.Select(z => z.tblCarType)).ToList();
My attempt is to retrieve a list of all carType entities associated with the personId.
I assume it's something to do with the last nested select?
Do like this because you are expecting multiple records to be returned:
var result = db.tblPeople
.Where(p => p.id == id)
.Select(s => s.tblCars
.SelectMany(z => z.tblCarType)).ToList();
Use SelectMany in order to flatten IEnumerable<IEnumerable<CarType>> into IEnumerable<CarType>.
var carTypes =
db.tblPeople
.Where(p => p.id == id)
.SelectMany(s =>
s.tblCars
.Select(z => z.tblCarType))
.ToList();
This translates from
var carTypes =
(from person in tblPeople
from car in person.tblCar
from carType in car.tblCarType
where person.id == id
select carType).ToList();
This is what you want/need:
db.tblPeople.Where(p => p.id == id).SelectMany(s => s.tblCars.Select(z => z.tblCarType)).ToList();

Is it possible to use Select(l=> new{}) with SelectMany in EntityFramework

I am trying something that i not really sure but i want to ask here if it s possible.
Is it able to be done ?
public IQueryable<Info> GetInfo(int count, byte languageId)
{
return db.Info.SelectMany(i => i.LanguageInfo)
.Where(l => l.Language.id == languageId)
.Select(l => new Info { AddDate = l.Info.AddDate,
Description = l.Description,
EntityKey = l.Info.EntityKey,
id = l.Info.id,
Title = l.Title,
ViewCount = l.Info.ViewCount }
)
.OrderByDescending(i => i.id)
.Take(count);
}
When this method is executed i got an error
The entity or complex type
'GuideModel.Info' cannot be
constructed in a LINQ to Entities
query.
Does it mean "not possible" ?
Thank you
The error essentially indicates that the Entity Framework doesn't know how to create an Info object, since it is not bound to a table object. (Put another way, the Select call on the IQueryable cannot be translated into equivalent SQL.) You could perform the Select projection on the client via:
public IQueryable<Info> GetInfo(int count, byte languageId)
{
return db.Info.SelectMany(i => i.LanguageInfo)
.Where(l => l.Language.id == languageId)
.Take(count)
.AsEnumerable()
.Select(l => new Info { AddDate = l.Info.AddDate,
Description = l.Description,
EntityKey = l.Info.EntityKey,
id = l.Info.id,
Title = l.Title,
ViewCount = l.Info.ViewCount }
)
.OrderByDescending(i => i.id);
}
It is possible to use Select(l => new ...), but not with an Entity type. You need to use an anonymous type or a POCO type with a parameterless constructor. Entity types are "special" because of the way they interact with the ObjectContext. You can select them, but not new them up in a query.
The code below worked for me. Here "SearchTerm" is a complex type. Thanks Jason :)
var lstSynonym = TechContext.TermSynonyms
.Where(p => p.Name.StartsWith(startLetter))
.AsEnumerable()
.Select(u => new SearchTerm
{
ContentId = u.ContentId,
Title = u.Name,
Url = u.Url
});

Categories