How to count entities properly - c#

I have a method which has an attribute "user" and I'm trying to check if he's already in a team of the given "course"
private static bool UserIsInTeamOfCourse(Course course, User user)
{
var count = course.Teams.Count(x => x.Users.Contains(user));
if (count > 0)
{
return true;
}
return false;
}
But it doesn't work. I made a custom Equals method in my User model, but it still don't work.
[EDIT] It always counts zero entries, but there have to be at least one entry.
public override bool Equals(object obj)
{
return UserId == ((User)obj).UserId;
}

bool isUserInAnyTeam = course.Teams.Any(t => t.Users.Any(u => u.Id == user.Id));
This is LINQ to Objects because you are querying on the Teams collection in memory. So it assumes that the Teams collection is already loaded or will be lazily loaded. If it's not loaded and you don't use lazy loading you can create a database query on the collection:
In EF 4.1 with DbContext:
bool isUserInAnyTeam = dbContext.Entry(course).Collection(c => c.Teams).Query()
.Any(t => t.Users.Any(u => u.Id == user.Id));
Or in EF 4.0:
bool isUserInAnyTeam = course.Teams.CreateSourceQuery()
.Any(t => t.Users.Any(u => u.Id == user.Id));
Teams must be of type EntityCollection<Team> in the last case.
Another way is to query from scratch:
bool isUserInAnyTeamOfCourse = context.Courses.Any(
c => c.Id == course.Id && c.Teams.Any(t => t.Users.Any(u => u.Id == user.Id)));

Assuming there is a many to many relationship betweens teams and users and a user will not be part twice of the same team.
var count = (from c in context.Teams
from u in c.Users
where u.Id == 1
select c.Id).Count();
Entity framework can not translate your c# code in which you've overriden equals to a sql function.

Instead of comparing custom objects compare userID (which is probably int or other simple type)
private static bool UserIsInTeamOfCourse(Course course, User user)
{
return course.Teams.Where(team => team.Users.Any(u => u.userID == user.userID)).Count() > 0;
}

Related

Filtering on Include reverted if I perform Select afterwards in EF Core

I am trying to use Filtered Includes in EF Core and I have encountered an issue which I can't seem to pinpoint to a specific cause.
My query looks something like this:
context.Users.Include(u=>u.UserRoles.Where(r => r.Role.Category == 3))
.ThenInclude(r=>r.Role).Where(u => u.userId == currentUserId)
.Select(u=> new UserDTO()
{
UserDisplayName= u.Name,
ListOfRoles = String.Join(",", u.UserRoles.Select(u => u.Role.DisplayName))
}).FirstOrDefaultAsync();
If I omit the Select part from the query and check the object, it is populated only with the appropriate UserRoles, the ones belonging to the category 3, but upon checking the result of this Select, it contains also the Roles that belong to a different category, concatenated into the ListOfRoles.
I would be grateful if anyone had any ideas what could be causing this.
Thank you
Include only applies where you are returning the entity. When you use projection with Select you need to filter the data within the Select expression:
context.Users
.Where(u => u.userId == currentUserId)
.Select(u=> new UserDTO()
{
UserDisplayName= u.Name,
ListOfRoles = String.Join(",", u.UserRoles
.Where(ur => ur.Role.Catecory == 3)
.Select(ur => ur.Role.DisplayName))
}).SingleOrDefaultAsync();
I believe that String.Join would require client-side evaluation in EF Core. This can lead to unexpected data being loaded. A recommendation to avoid this would be to perform the concatination within the DTO so that the Linq query loads the raw data and can translate that to SQL efficiently:
context.Users
.Where(u => u.userId == currentUserId)
.Select(u=> new UserDTO()
{
UserDisplayName= u.Name,
Roles = u.UserRoles
.Where(ur => ur.Role.Catecory == 3)
.Select(ur => ur.Role.DisplayName))
.ToList();
}).SingleOrDefaultAsync();
Where in the DTO you would have:
[Serializable]
public class UserDTO
{
public string UserDisplayName { get; set; }
public IList<string> Roles { get; set; } = new List<string>();
public string ListOfRoles
{
get { return string.Join(",", Roles); }
}
}
This ensures the query can run efficiently and translate fully to SQL, then moves the formatting to the DTO.
Include will work only if you select entities directly. Once you do the projection (Select for example) Include is ignored. You can try to apply category filtering during the concatenation part:
context.Users
.Where(u => u.userId == currentUserId)
.Select(u=> new UserDTO()
{
UserDisplayName= u.Name,
ListOfRoles = String.Join(",", u.UserRoles.Where(r => r.Role.Category == 3).Select(u => u.Role.DisplayName))
})
.FirstOrDefaultAsync();

Turn SQL into Lambda/Linq

I've been trying to turn a fairly basic piece of SQL code into Lamda or Linq but I'm getting nowhere. Here is the SQL query:
SELECT * FROM Form a
INNER JOIN FormItem b ON a.FormId = b.FormId
INNER JOIN FormFee c ON a.FormId = c.FormId
INNER JOIN FeeType d ON c.FeeTypeId = d.FeeTypeId
WHERE b.StatusId = 7
I tried this but it isn't doing what I want.
public Form GetFormWithNoTracking(int id)
{
return ObjectSet
.Where(x => x.FormId == id &&
(x.FormItem.Any(di => di.StatusId == (short)Status.Paid)))
.AsNoTracking()
.FirstOrDefault();
}
I'm trying to return only the rows from FormItem whose StatusId is Paid. However, the above returns all. I know that .Any() will check if there are any matches and if there are return all, so in this case my data, for this form, does have items who have a StatusId of Paid and some items whose StatusId is not paid so it brings back them all.
var query = (from a in ObjectSet.FormA
join b in ObjectSet.FormB on a.field equals b.field
where b.StatusId = 7
select new { a, b})
You can join rest with same logic.
This should be what you are asking for:
Get the Form with FormId = id
Of that form, return all FormItems that have StatusId = Paid
public IEnumerable<FormItem> GetFormWithNoTracking(int id)
{
return ObjectSet
.SingleOrDefault(x => x.FormId == id)
.Select(f => f.FormItem
.Where(di => di.StatusId == (short)Status.Paid))
.AsNoTracking();
}
If you need the Form itself too, you might want to create a custom type (edit: see #Burk's answer) or return a Tuple<Form,IEnumerable<FormItem>>, a IEnumerable<Tuple<Form,FormItem>> or whatever suits your needs best instead.
Alternatively you could remove all non-paid items of the form.
public Form GetFormWithNoTracking(int id)
{
var form = ObjectSet
.SingleOrDefault(x => x.FormId == id)
.AsNoTracking();
var nonPaid = form.Select(f => f.FormItem
.Where(di => di.StatusId != (short)Status.Paid)).ToList();
foreach(FormItem item in nonPaid)
form.FormItem.Remove(item);
return form;
}

Distinct not working in entity framework

function for get list of documents name only with distinct...
public static List<DocumentTypeModel> GetUploadedDocumentsName(int TicketId)
{
List<DocumentTypeModel> documents = new List<DocumentTypeModel>();
using (var db = new UnitOfWork())
{
documents = db.tbl_TrnTicketDocument.Get(x => x.FK_TicketId == TicketId && x.IsActive == true).Select(s => new DocumentTypeModel()
{
DocumentTypeNameEnglish = s.tbl_MstDocumentType.DocumentTypeNameEnglish
}).Distinct().ToList();
}
return documents;
}
currently result is this -
Affidavit in Case of Cancelled Will/No Will
Affidavit in Case of Cancelled Will/No Will
Allotment Letter
Allotment Letter
Death Certificate
Death Certificate
Lease Deed
Lease Deed
Photo Identity of Applicant
Photo Identity of Applicant
Possession Letter
Possession Letter
Registered/Unregistered Will
Registered/Unregistered Will
You can use groupby and select first option like this:
List<DocumentTypeModel> documents = new List<DocumentTypeModel>();
using (var db = new UnitOfWork())
{
documents = db.tbl_TrnTicketDocument.Get(x => x.FK_TicketId == TicketId && x.IsActive == true).Select(s => new DocumentTypeModel()
{
DocumentTypeNameEnglish = s.tbl_MstDocumentType.DocumentTypeNameEnglish
}).ToList();
documents = documents.GroupBy(x => x.DocumentTypeNameEnglish).Select(g => g.First());
}
Distinct() doesn't work like you tried on objects. Use IComparer to get this working https://support.microsoft.com/en-us/kb/320727
Create a comparer class
public class DocumentTypeModelComparer: IComparer
{
int IComparer.Compare(object a, object b)
{
if(a.Id == b.ID)
return 0;
else
return 1;
}
}
Now in your lambda expression
documents = db.tbl_TrnTicketDocument.Get(x => x.FK_TicketId == TicketId && x.IsActive == true).Select(s => new DocumentTypeModel()
{
DocumentTypeNameEnglish = s.tbl_MstDocumentType.DocumentTypeNameEnglish
}).ToList().Distinct(new DocumentTypeModelComparer()).ToList();
You are selecting the documenttypemodel and this is the distinct part (so all fields are checked), you probably want the distinct by via https://github.com/morelinq/MoreLINQ, or you can use group by with first see(linq distinct or group by multiple properties).
The other option is to select only the DocumentTypeNameEnglish field and you will get the unique documents.
documents = db.tbl_TrnTicketDocument.Get(x => x.FK_TicketId == TicketId && x.IsActive == true).Select(s => new { documentTypes =s.tbl_MstDocumentType.DocumentTypeNameEnglish}).Distinct().ToList();
Hopefully this is what you want, if not can you post more details?
https://social.msdn.microsoft.com/Forums/en-US/6303da53-c412-4ce6-a983-bd6bd87f516b/how-to-do-a-select-distinct-with-ef?forum=adodotnetentityframework
Kindly refer this link Its useful to you
Switch to the below per Vladimir's comment:
.ToList().Distinct();

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();

Return one element from where-clause or the first one

I'm trying to query my database with Entity Framework 5 in the following manner:
userProfile = context.UserProfiles.SingleOrDefault(up => up.UserId == userId && up.IsActive);
Now this clearly throws an exception when more than one user profile is present (due to the SingleOrDefault).
How can I construct my query so I get the active userprofile from the current user (up.UserId == userId) or the first one when no userprofile is active?
I totally know how to do this in two queries, but I think that would be too much overhead and this should be possible with just one query:
userProfile = context.UserProfiles.Single(up => up.UserId == userId && up.IsActive); // throws exception when not exactly one
if (userProfile == null)
{
userProfile = context.UserProfiles.FirstOrDefault(up => up.UserId == userId);
}
Edit:
I'm now using this as my parameter and works flawlessly with the IsActive property:
context.UserProfiles.Where(up => up.UserId == userId).OrderByDescending(up => up.IsActive).FirstOrDefault();
Now when there is no IsActive == true row, I'd like to order them by their LastUsed date and select the one that's lately being used:
context.UserProfiles.Where(up => up.UserId == userId).OrderByDescending(up => up.IsActive).OrderByDescending(up => up.LastUsed).FirstOrDefault();
Sadly this always returns the latest one, even when there's a row containing IsActive == true
How about:
var userProfile = context.UserProfiles.Where(up => up.UserId == userId)
.OrderByDescending(up => up.IsActive).FirstOrDefault();
Your second OrderByDescending ignores any ordering done by the previous one. You need to use ThenByDescending instead:
context.UserProfiles
.Where(up => up.UserId == userId)
.OrderByDescending(up => up.IsActive)
.ThenByDescending(up => up.LastUsed)
.FirstOrDefault();

Categories