How should I get distinct Record in linq query - entity-framework

Hi I have tried below query to get the distinct record but I am not able to get the distinct record from below query.
var query = (from sr in db.StudentRequests
join r in db.Registrations on sr.RegistrationId equals r.RegistrationId
join cc in db.Campus on r.CampusId equals cc.CampusId
join c in db.Classes on sr.ClassId equals c.ClassId
from tc in db.TutorClasses.Where(t => t.ClassId == sr.ClassId).DefaultIfEmpty()
from srt in db.StudentRequestTimings.Where(s => s.StudentRequestId == sr.StudentRequestId).DefaultIfEmpty()
from tsr in db.TutorStudentRequests.Where(t => t.StudentRequestId == srt.StudentRequestId && t.TutorId == registrationid).DefaultIfEmpty()
where tc.RegistrationId == registrationid
select new
{
StudentRequestId = sr.StudentRequestId,
RegistrationId = sr.RegistrationId,
Location = sr.Location,
PaymentMethod = sr.PaymentMethod,
CreatedOn = sr.CreatedOn,
ClassName = c.ClassName,
CampusName = cc.CampusName,
StatusId = tsr.StatusId == null ? 1 : tsr.StatusId,
Time = db.StudentRequestTimings.Where(p => p.StudentRequestId == sr.StudentRequestId)
.Select(p => p.FromTime.ToString().Replace("AM", "").Replace("PM", "") + "-" + p.ToTime)
}).Distinct().ToList().ToPagedList(page ?? 1, 3);
But I am getting error as
The 'Distinct' operation cannot be applied to the collection
ResultType of the specified argument.\r\nParameter name: argument
So I had removed the .Distinct() from the above query and below that code I had written code as
query = query.Distinct().ToList();
But still the duplicate record was showing
So I had tried Group by clause to get distinct record but over there also I am facing the issue.Please Review below code
query = query.ToList().GroupBy(x => new { x.StudentRequestId, x.StatusId, x.Location, x.RegistrationId, x.PaymentMethod, x.CreatedOn, x.ClassName, x.CampusName})
.Select(group => new
{
StudentRequestId = group.Key.StudentRequestId,
StatusId = group.Key.StatusId,
Location = group.Key.Location,
RegistrationId = group.Key.RegistrationId,
PaymentMethod = group.Key.PaymentMethod,
CreatedOn = group.Key.CreatedOn,
ClassName = group.Key.ClassName,
CampusName = group.Key.CampusName,
Time1 = group.Key.Time
});
But I am getting error for time as
How can I get the distinct Value?
Also make in concern that I am using ToPagedList in the query
The actual issue is coming from Time column, If I remove that column all things are working fine.

I had added Group by clause at the end of the query as suggested by #Rajaji and that worked for me.
var query = (from sr in db.StudentRequests
join r in db.Registrations on sr.RegistrationId equals r.RegistrationId
join cc in db.Campus on r.CampusId equals cc.CampusId
join c in db.Classes on sr.ClassId equals c.ClassId
from tc in db.TutorClasses.Where(t => t.ClassId == sr.ClassId).DefaultIfEmpty()
from srt in db.StudentRequestTimings.Where(s => s.StudentRequestId == sr.StudentRequestId).DefaultIfEmpty()
from tsr in db.TutorStudentRequests.Where(t => t.StudentRequestId == srt.StudentRequestId && t.TutorId == registrationid).DefaultIfEmpty()
where tc.RegistrationId == registrationid
select new
{
StudentRequestId = sr.StudentRequestId,
RegistrationId = sr.RegistrationId,
Location = sr.Location,
PaymentMethod = sr.PaymentMethod,
CreatedOn = sr.CreatedOn,
ClassName = c.ClassName,
CampusName = cc.CampusName,
StatusId = tsr.StatusId == null ? 1 : tsr.StatusId,
Time = db.StudentRequestTimings.Where(p => p.StudentRequestId == sr.StudentRequestId).Select(p => p.FromTime.ToString().Replace("AM", "").Replace("PM", "") + "-" + p.ToTime)
}).ToList().GroupBy(p => new { p.StudentRequestId }).Select(g => g.First()).ToList();

Related

Accent insensitive in filter backend to datatable in ASP.NET MVC 5

I made a method on the back-end side to handle the filter of my datatable.
On the other hand, this one does not manage the accents of the French language, so if I have "école" and I write "ecole" it cannot find it.
I found this method on another question on stackoverflow
public static String RemoveDiacritics(this String s)
{
String normalizedString = s.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < normalizedString.Length; i++)
{
Char c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
and it works, but only for part of my problem. It works on the letter or the word that is written in the search, but I am not able to apply it in my linq query, so with the .RemoveDiacritics() method my "école" becomes "ecole", but I don't am not able to apply it in the column of my table and it always looks for "école".
Here the code for the search:
if (search != null)
{
int n;
search = search.Trim();
var isNumeric = int.TryParse(search, out n);
if (isNumeric)
{
IdFilter = n;
query = query.Where(x => x.UsagerId == IdFilter || x.Niveau == IdFilter);
}
else if (search != "")
{
// this line work
textFilter = search.ToLower().RemoveDiacritics();
// This is the full line, but absolutely takes the accents out to get the right information out
// query = query.Where(x => x.Nom.ToLower().Contains(textFilter) || x.Prenom.ToLower().Contains(textFilter) || x.Username.ToLower().Contains(textFilter) || x.Email.ToLower().Contains(textFilter) || x.EtabNom.ToLower().Contains(textFilter) || x.ActifStatut.ToLower().Contains(textFilter));
// This is the line that will replace the line above, which I try and it doesn't work ( this part: x.Prenom.ToLower().RemoveDiacritics())
query = query.Where(x => x.Prenom.ToLower().RemoveDiacritics().Contains(textFilter));
}
}
This is the basic query:
IQueryable<ListeUsagers> query = (from u in db.USAGERs
join e in db.ETABLISSEMENTs on u.USAGER_INST equals e.ETAB_CODE
where u.USAGER_INST == instId && u.USAGER_NIVEAU > 3 && u.USAGER_NIVEAU < 5 //&& u.USAGER_ACTIF == 1
select new ListeUsagers()
{
UsagerId = u.USAGER_id,
Nom = u.USAGER_NOM,
Prenom = u.USAGER_PRENOM,
EtabCode = e.ETAB_CODE,
EtabNom = e.ETAB_NOM_COURT,
EtabType = e.ETAB_TYPE,
Niveau = u.USAGER_NIVEAU,
Username = u.USAGER_USERNAME,
UserPassword = u.USAGER_MP,
DateCreation = u.USAGER_DATE_INSC,
Sexe = u.USAGER_SEXE,
Lang = u.USAGER_LANGUE,
Telephone = u.USAGER_TELEPHONE,
Email = u.USAGER_EMAIL,
FonctionTravail = u.USAGER_FONCTION,
LieuTravail = u.USAGER_LIEUTRAVAIL,
Note = u.USAGER_NOTE,
Actif = u.USAGER_ACTIF,
ActifStatut = u.USAGER_ACTIF == 0 ? "Inactif" : "Actif"
});
This is the error:
LINQ to Entities does not recognize the method 'System.String RemoveDiacritics(System.String)' method, and this method cannot be translated into a store expression.
There's built-in functionality to do this in entityframework: https://learn.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity if you're using EF 5+
You'll want an accent insensitive collation ("AI", not "AS" in the examples on that page.)

Navigation Properties Missing After Left Join

I'm trying to do a left join on two entities that have navigation properties. I have disabled lazy loading.
Here is my code:
var awis =
from ai in Context.AdItems
.Include(ai => ai.Item)
.Include(ai => ai.Item.Buyer)
.Include(ai => ai.Item.OrderHeader)
.Where(ai => ai.AdYear == adYear && ai.AdNumber == adNumber)
join si in Context.StoreItems
.Include(si => si.Store)
.Where(si => si.StoreId == storeId) on ai.ItemId equals si.ItemId into x
from r in x.DefaultIfEmpty()
select new AdWeekItem
{
AdItemId = ai.AdItemId,
AdItem = ai,
StoreItemId = r == null ? 0 : r.StoreItemId,
StoreItem = r
};
The outer join works fine but I do not have my navigation properties included in the projected AdWeekItem.
That is, AdWeekItem.AdItem.Item is null, etc.
How do I include these navigation properties and do a left outer join?
In EF6, Includes are always ignored when a LINQ query ends in a projection (select new ...). Your only option is to explicitly query the navigation properties you want to have included and then project to the end result:
var temp = from ai in Context.AdItems
.Where(ai => ai.AdYear == adYear && ai.AdNumber == adNumber)
join si in Context.StoreItems
.Where(si => si.StoreId == storeId) on ai.ItemId equals si.ItemId into x
from r in x.DefaultIfEmpty()
select new
{
AdItemId = ai.AdItemId,
AdItem = ai,
ai.Item,
ai.Item.Buyer,
ai.Item.OrderHeader,
StoreItemId = r == null ? 0 : r.StoreItemId,
StoreItem = r,
r.Store
};
var awis = from x in temp.AsEnumerable() // pull into memory and continue
select new AdWeekItem
{
AdItemId = x.AdItemId,
AdItem = x.AdItem,
StoreItemId = x.StoreItemId,
StoreItem = x.StoreItem
};
EF will have populated AdItem.Item etc. by relationship fixup (i.e. auto-population of navigation properties).

EF Append to an IQueryable a Where that generates an OR

I'm trying to achieve dynamic filtering on a table. My UI has filters that can be enabled or disabled on demand, and as you can imagine, my query should be able to know when to add filters to the query.
What I have so far is that I check if the filter object has a value, and if it does it adds a where clause to it. Example:
var q1 = DBContext.Table1
if (!string.IsNullOrEmpty(filterModel.SubjectContains))
q1 = q1.Where(i => i.Subject.Contains(filterModel.SubjectContains));
if (filterModel.EnvironmentId != null)
q1 = q1.Where(i => i.EnvironmentId == filterModel.EnvironmentId);
if (filterModel.CreatedBy != null)
q1 = q1.Where(i => i.CreatedByUserId == filterModel.CreatedBy);
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();
The code above generates T-SQL that contains a WHERE clause for each field that uses AND to combine the conditions. This is fine, and will work for most cases.
Something like:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
But then I have a filter that explicitly needs an IssueId. I'm trying to figure out how the EF Where clause can generate an OR for me. I'm looking something that should generate a Tsql that looks like this:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
or (IssueId = 10001)
Found a solution for this that doesn't have to do multiple database call and works for me.
//filterModel.StaticIssueIds is of type List<Int32>
if (filterModel.StaticIssueIds != null)
{
//Get all ids declared in filterModel.StaticIssueIds
var qStaticIssues = DBContext.Table1.Where(i => filterModel.StaticIssueIds.Contains(i.IssuesId));
//Let's get all Issues that isn't declared in filterModel.StaticIssueIds from the original IQueryable
//we have to do this to ensure that there isn't any duplicate records.
q1 = q1.Where(i => !filterModel.StaticIssueIds.Contains(i.IssuesId));
//We then concatenate q1 and the qStaticIssues.
q1 = q1.Concat(qStaticIssues);
}
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();

how to debug EF 5 null reference exception?

I am getting a null refrence exception when im filtering EF but I am absolultely clueless.
public IEnumerable<TonalityBatchModel> GetTonalityBatch(int briefID)
{
try
{
var brief = NeptuneUnitOfWork.Briefs.FindWhere(b => b.ID == briefID).FirstOrDefault();
if (brief != null && brief.TonalityCriteria != null)
{
return brief.TonalityCriteria.TonalityBatches
.Select(b => new TonalityBatchModel()
{
BriefID = b.BriefID,
Status = b.TonalityCriteria.IsActive == true ?"Active":"Ended",
BatchID = b.ID,
CompetitorID = b.BriefCompetitorID,
Competitor = brief.BriefCompetitors.Where(i=>i.ID == b.BriefCompetitorID).Select(c=>c.Organisation.Name).First(),
Size = b.BatchSize,
StartDate = b.StartDate,
EndDate = b.EndDate,
IsPublished = b.Lookup_TonalityBatchStatus.ID == (int)TonalityBatchStatus.Published?"Yes":"No",
IsCompleted = b.Lookup_TonalityBatchStatus.ID == (int)TonalityBatchStatus.Completed ? "Yes" : "No",
IsAssigned = b.Lookup_TonalityBatchStatus.ID == (int)TonalityBatchStatus.Allocated ? "Yes" : "No",
ImportantCount = b.TonalityItems.Count(i=> i.IsImportant),
ArticlesCount = b.TonalityItems.Count,
FavourableCount = b.TonalityItems.Count(i => i.Lookup_TonalityScoreTypes.ID ==(int)TonalitySourceType.Favourable),
UnfavourableCount = b.TonalityItems.Count(i => i.Lookup_TonalityScoreTypes.ID ==(int)TonalitySourceType.Unfavourable),
NeutralCount = b.TonalityItems.Count(i => i.Lookup_TonalityScoreTypes.ID ==(int)TonalitySourceType.Neutral)
}).ToList();
}
return new List<TonalityBatchModel>();
}
catch (Exception ex)
{
Logger.Error(ex);
throw;
}
}
You'll need to reduce your query to a simpler query, and then start building it back up again until the NullReferenceException occurs. Looking at your code, here are some likely places (I'm making some assumptions since I don't know everything about your model):
Competitor = brief.BriefCompetitors.Where(i=>i.ID == b.BriefCompetitorID).Select(c=>c.Organisation.Name).First()
BriefCompetitors could be null. c.Organisation could be null.
IsPublished = b.Lookup_TonalityBatchStatus.ID == (int)TonalityBatchStatus.Published?"Yes":"No",
(and other similar lines) b.Lookup_TonalityBatchStatus might be null.
ImportantCount = b.TonalityItems.Count(i=> i.IsImportant),
(and other similar lines) b.TonalityItems might be null.
I believe this is because your count is returning null records. I could be wrong but the SQL that's being produced here is something like:
INNER JOIN TonalityItems i on i.Lookup_TonalityScoreTypes == x
Where x is the value of (int)TonalitySourceType.Favourable. Because this join has no matching results there is nothing to do a count on. You could try adding ?? 0 to the end of the query:
FavourableCount = b.TonalityItems.Count(i => i.Lookup_TonalityScoreTypes.ID ==(int)TonalitySourceType.Favourable) ?? 0,

Entity Framework lambda query with groupby, sum and average

I have a entity called StockDetails using Entity Framework, see picture below
I want to fetch a list IEnumerable<StockDetail>, summarized by Reels, Qtyton, average date from Days (datetime) and grouping by the rest of the properties.
I'm building a datalayer (WCF Services) with Entity Framework as ORM, some of the services are old SQL queries I'm trying to convert to linq/lamdba expression. But I'm pretty new to how to write and want some help.
This is how I started the query in lambda, but I got stuck on the groupby/sum/average part.
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var CustNo = customerNumber.ToString();
return _entities.StockDetails
.Where(x => x.Custno == CustNo)
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.Reels,
x.Days,
x.Qtyton
})
.ToList();
}
Question solved:
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var stockDetailsList = new List<StockDetail>();
var custNo = customerNumber.ToString();
var list = _entities.StockDetails
.Where(x => x.Custno == custNo )
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.UpdDte
})
.Select(x => new
{
x.Key.Millcd,
x.Key.Matercd,
x.Key.Proddesc,
x.Key.Grammage,
x.Key.Reelwidth,
x.Key.Ordercode,
x.Key.Buyordno,
Reels = x.Sum(p => p.Reels),
Qtyton = x.Sum(p => p.Qtyton),
Day = x.Max(p => p.Days),
//Day = x.Average(p => p.Days.Ticks), // Want to calculate average datetime of date but linq dosn't support datetime.ticks
x.Key.Whsedesc,
x.Key.Co,
x.Key.Finished,
x.Key.Pm,
x.Key.PurchaseOrder,
x.Key.Diameter,
x.Key.Rtadate,
x.Key.Custno,
x.Key.UpdDte
});
foreach (var s in list)
{
stockDetailsList.Add(new StockDetail
{
Millcd = GetFriendlyNameForKey(s.Millcd),
Matercd = s.Matercd,
Proddesc = s.Proddesc,
Grammage = s.Grammage,
Reelwidth = s.Reelwidth,
Ordercode = s.Ordercode,
Buyordno = s.Buyordno,
Reels = s.Reels,
Qtyton = s.Qtyton,
Days = s.Day,
Whsedesc = s.Whsedesc,
Co = s.Co,
Finished = s.Finished,
Pm = s.Pm,
PurchaseOrder = s.PurchaseOrder,
Diameter = s.Diameter,
Rtadate = s.Rtadate,
Custno = s.Custno,
UpdDte = s.UpdDte
});
}
return stockDetailsList;
}
This is how the query looks in T-SQL
SELECT
Millcd, Matercd,
Proddesc, Grammage,
Reelwidth, Ordercode,
Buyordno,
SUM(Reels) as Reels,
SUM(Qtyton) as Qtyton,
Whsedesc, Co,
(cast(FLOOR(avg(cast(DateProd as float))) as datetime)) As Days,
Finished, Pm,
PurchaseOrder,
Diameter, Rtadate,
Custno, UpdDte
FROM StockDetail
WHERE custno = #custcode
GROUP BY Millcd, Matercd, Proddesc, Grammage, Reelwidth, Ordercode, Buyordno,
Whsedesc, Co, Finished, Pm, PurchaseOrder, Diameter, Rtadate, Custno, UpdDte
not sure if this will help you but you can add
Reels = (_entities.StockDetails
.Where(x => x.Custno == CustNo).Sum(x=>x.Reels))
instead of x.Reels in your select , and do the same with Qtyton
For your average use the average extension
your select will look something like .Select(x=>new {...}) after your where statement then the group by