How to return FirstOrDefaultAsync EF Linq to entity List query - linq

I have this Linq to entity query that returns a list of visitors with joins.
I want a similar query to return a single record without the query being a List collection, but changing it to a basic select with FirstOrDefaultAsync gets the error "A query body must end with a select clause or a group clause"
public async Task<List<VisitorDetail>> GetOneVisitor(int visitorId)
{
var query = await (from v in _context.Visitors
where v.Id == visitorId
join d in _context.Categories on v.VisitCategoryId equals d.Id
join c in _context.Counters on v.AssignedCounter equals c.Host into counterGroup
from cg in counterGroup.DefaultIfEmpty()
select new
{
FirstName = v.FirstName,
LastName = v.LastName,
CounterDescription = cg.Description,
VisitCategory = d.Description
}).ToListAsync();
List<VisitorDetail> visitors = new();
foreach (var p in query)
{
visitors.Add(new VisitorDetail
{
FirstName = p.FirstName,
LastName = p.LastName,
CounterDescription = p.CounterDescription,
CategoryDescription = p.VisitCategory
});
}
return visitors;
}

If you want to keep DRY with LINQ to Entities, return IQueryable for your common queries and invoke materialization only when it is needed.
Your method can be rewritten in the following way:
public IQueryable<VisitorDetail> GetVisitorDetails(int visitorId)
{
var query =
from v in _context.Visitors
where v.Id == visitorId
join d in _context.Categories on v.VisitCategoryId equals d.Id
join c in _context.Counters on v.AssignedCounter equals c.Host into counterGroup
from cg in counterGroup.DefaultIfEmpty()
select new VisitorDetail
{
FirstName = v.FirstName,
LastName = v.LastName,
CounterDescription = cg.Description,
CategoryDescription = d.Description
};
return query;
}
var many = await GetVisitorDetails(visitorId).ToListAsync();
var one = await GetVisitorDetails(visitorId).FirstOrDefaultAsync();

Related

How to convert LINQ query to work with EF Core

I'm converting from EF6 to EF Core 3.1 and this LINQ query is failing with a runtime exception stating 'The LINQ expression ... could not be translated.
The group by is what is causing the issue, but I'm not sure how to rewrite it to work with EF Core and keep the result in a nested list.
Notification notification = new Notification()
{
ProductReminders = new List<List<ProductNotification>>(),
ProductStats = new List<StatResult>()
};
var profileCode = 123;
notification.ProductReminders =
(from ng in ProductNotification
where ng.UserProfileCode == profileCode
orderby ng.EndDate ?? DateTime.MaxValue
group ng by ng.GroupGUID into groupG
select (from pn in ProductNotification
join p in Product on pn.ProductID equals p.ProductID
where pn.UserProfileCode == profileCode
&& pn.GroupGUID == groupG.Key
orderby pn.EndDate ?? DateTime.MaxValue
select new ProductNotification()
{
ProductDetail = new ProductDetail()
{
ProductId = pn.ProductID ?? 0,
Upc = p.UPC,
Brand = p.Description,
Manufacturer = p.Name,
ProfileCode = p.ProfileCode,
},
EndDate = pn.EndDate,
NotificationId = pn.NotificationID,
Status = pn.Status,
GroupGuid = pn.GroupGUID
})
.ToList())
.ToList();
Since grouping operator has limitations, I would suggest to read all needed data and provide grouping on the client side. Query in your case will be much effective:
// select only needed data from database
var minimalRequiredData =
from pn in ProductNotification
join p in Product on pn.ProductID equals p.ProductID
where pn.UserProfileCode == profileCode
select new ProductNotification
{
ProductDetail = new ProductDetail
{
ProductId = pn.ProductID ?? 0,
Upc = p.UPC,
Brand = p.Description,
Manufacturer = p.Name,
ProfileCode = p.ProfileCode,
},
EndDate = pn.EndDate,
NotificationId = pn.NotificationID,
Status = pn.Status,
GroupGuid = pn.GroupGUID
};
// materialize result
var materialized = minimalRequiredData.ToList();
// form required result shape using IEnumerable<T>
var resultQuery =
from m in materialized
orderby ng.EndDate ?? DateTime.MaxValue
group m by new m.GroupGUID into g
select g.Orderby(x => x.EndDate ?? ng.EndDate).ToList();
notification.ProductReminders = resultQuery.ToList();

JPQL - Joins - MultipleTables and empty ResultList

I have a query in JPQL through 5 tables, but if one of the table is NULL, the whole query fails and resultList is empty and it throws the exception. On the other hand if all tables do not contain null, it works as expected.
How to implement it to return all objects where table is not null and objects where is null as null? So I would get - Object1[], null, null, null for example... and not only empty result list...
Thank you very much :)
public List<Object[]> getAdditionalInformation(String ppin) {
Query query = em.createQuery("SELECT p, pl, r, d, do FROM Patient p JOIN p.placements pl JOIN pl.room r"
+ " JOIN r.department d JOIN d.doctors do where p.pin = :ppin");
query.setParameter("ppin", ppin);
return query.getResultList();
}
#Transactional
public AdditionalPD getAdditional(String ppin) {
List<Object[]> list = hr.getAdditionalInformation(ppin);
AdditionalPD adp = new AdditionalPD();
Patient patient = null;
Placement placement = null;
Room room = null;
Department department = null;
Doctor doctor = null;
for(Object[] object : list) {
patient = (Patient) object[0];
placement = (Placement) object[1];
room = (Room) object[2];
department = (Department) object[3];
doctor = (Doctor) object[4];
}
adp.setPatientFirstName(patient.getFirstName());
adp.setPatientLastName(patient.getLastName());
adp.setAge(countAge(ppin));
adp.setFrom(placement.getFrom());
adp.setTo(placement.getTo());
adp.setRoomName(room.getName());
adp.setDepartmentName(department.getName());
adp.setDoctorFirstName(doctor.getFirstName());
adp.setDoctorLastName(doctor.getLastName());
return adp;
}
#GetMapping("/additional/pin/{ppin}")
public String additionalInformation(#PathVariable String ppin, Model model) {
AdditionalPD adp = has.getAdditional(ppin);
model.addAttribute("adp", adp);
return "additional";
}
Use LEFT JOIN instead of JOIN. JPQL joins are INNER JOINs by default.

LINQ Update query with Join condition

trying to do LINQ Update query using join with on condition also And condition
Following my SQL Query
UPDATE tbl_gs_related_orders
SET RelatedCOI = sop.ServiceOrderNo
FROM tbl_service_order_progress sop
JOIN tbl_gs_related_orders ro ON sop.parentid = ro.RelatedOrderParentId
AND ro.RelatedCOIIdentifier collate database_default = sop.InstanceIdentifier collate database_default
WHERE ro.ParentId = #PARENTID
AND ro.relatedorderparentid IS NOT NULL
This Query i want to convert into LINQ SO following my update LINQ query but getting some error like "the type of one of the expressions in the join clause is incorrect type inference failed in the call to join"
var updateQuery = (from c in ctxParser.TBL_SERVICE_ORDER_PROGRESS
join o in ctxParser.tbl_GS_Related_Orders on new { ParentId = c.ParentId, InstanceIdentifier = c.InstanceIdentifier }
equals new { RelatedOrderParentId = o.RelatedOrderParentId, RelatedCOIIdentifier = o.RelatedCOIIdentifier }
where ro.RelatedOrderParentId == sParentId && ro.RelatedOrderParentId != null
select new
{
ServiceOrderNo = c.ServiceOrderNo,
Order = o
});
foreach (var item in updateQuery)
{
item.Order.RelatedCOI = item.ServiceOrderNo;
}
ctxParser.SaveChanges();
Use same aliases when creating Anonymous type for equals clause:
from c in ctxParser.TBL_SERVICE_ORDER_PROGRESS
join o in ctxParser.tbl_GS_Related_Orders on new { ParentId = c.ParentId, InstanceIdentifier = c.InstanceIdentifier }
equals new { ParentId = o.RelatedOrderParentId, InstanceIdentifier = o.RelatedCOIIdentifier }

how to pass string variable to linq select new {} section

Ii just want to make search functionality with linq with multiple ColumnNames that stored to session variable. I'm using one method:
public void FillGrid(string CommandName,string ColumnName, string SearchText)
That has three string variable that stores session value.
Now I just want to pass ColumnName with this query:
var query1 = (from p in db.Posts
join c in db.Categories on p.Category_id equals c.Id
join u in db.Users on p.User_id equals u.Id
where (p.ToUser_id == user_id || p.ToUser_id == null) && p.User_id != user_id
orderby p.Sent_Datetime descending
select new
{
Id = p.Id,
Title = p.Title,
Publisher = u.First_name + " " + u.Last_name,
ToUser = p.ToUser_id,
PublishDate = p.Sent_Datetime,
IsFile = p.IsFileAttached,
CategoryName = c.Category_name,
status_name = (from s in db.Status where (s.Id == p.status_id) select s.status_name).FirstOrDefault(),
Group_name = (from g in db.Groups where (g.Id == p.group_id) select g.Group_name).FirstOrDefault(),
FileSize = p.TotalFileSize,
ColumnName = Sesssion["ColumnName"].ToString()
}).Where(q => q.ColumnName.Contains(SearchText));
However, ColumnName does not give any text or it may be not part of this query i have to manually give column name because.
for multiple column i have, so i can not use this statement like:
.Where(q => q.Tile.Contains(SearchText));
this query works fine with single column. but there is multiple column i have so i have to set q.ColumnName from outer side.
I would do an extension method for that kind of things, building an expression for your predicate.
public static class Helper
{
public static IQueryable<T> FilterForColumn<T>(this IQueryable<T> queryable, string colName, string searchText)
{
if (colName != null && searchText != null)
{
var parameter = Expression.Parameter(typeof(T), "m");
var propertyExpression = Expression.Property(parameter, colName);
var searchExpression = Expression.Constant(searchText);
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var body = Expression.Call(propertyExpression, containsMethod, searchExpression);
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
return queryable.Where(predicate);
}
else
{
return queryable;
}
}
}
usage in your case
var query1 = (from p in db.Posts
join c in db.Categories on p.Category_id equals c.Id
join u in db.Users on p.User_id equals u.Id
where (p.ToUser_id == user_id || p.ToUser_id == null) && p.User_id != user_id
orderby p.Sent_Datetime descending
select new
{
Id = p.Id,
Title = p.Title,
Publisher = u.First_name + " " + u.Last_name,
ToUser = p.ToUser_id,
PublishDate = p.Sent_Datetime,
IsFile = p.IsFileAttached,
CategoryName = c.Category_name,
status_name = (from s in db.Status where (s.Id == p.status_id) select s.status_name).FirstOrDefault(),
Group_name = (from g in db.Groups where (g.Id == p.group_id) select g.Group_name).FirstOrDefault(),
FileSize = p.TotalFileSize,
}).FilterForColumn(Sesssion["ColumnName"].ToString(), SearchText);

LINQ anonym object with result to delimited string (LINQ to Entities does not recognize the method 'System.String ToString()' method)

I am trying to get a ; demlimited string of all the relatives that a person has.
Four tables are involved:
USERTAB
PERSON
PERSON_RELATION
RELATION_TAB
Query
from u in USERTAB
select new
{
Person = from p in PERSON where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives = (from r in PERSON_RELATION where r.PSEQ == p.PSEQ select new
{
Description = (from rel in RELATION_TYPE where rel.TYPE_SEQ == r.TYPE_SEQ select rel.DESCRIPTION).ToArray() //(or also tried .ToString())
})
}
}
I'd like the Description field to be a ";" delimited list of all the relatives a user (person) has.
Using a ToString on my Relatives object it only fails runtime with. LINQ to Entities does not recognize the method 'System.String ToString()' method
Example: Description = "Father, Brother, Cousin"
You need to be clear about what parts of your query get translated to SQL to run on the server, and what parts run in your local application. The key is to construct a simple query to retrieve all the data, then use .AsEnumerable() to ensure that the remaining transformations don't get translated to SQL, and finally transform the data into a form useful for you. Something like
var query =
from u in USERTAB
select new
{
Person =
from p in PERSON
where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives =
from r in PERSON_RELATION
where r.PSEQ == p.PSEQ
select new
{
Description =
from rel in RELATION_TYPE
where rel.TYPE_SEQ == r.TYPE_SEQ
select pos.DESCRIPTION
}
}
}
};
var enumerable =
from u in query.AsEnumerable()
select new
{
Person =
from p in u.Person
select new
{
PNo = p.PNo,
Name = p.Name
Relatives =
string.Join(", ",
from r in p.Relatives
from d in r.Description
select d.Description)
}
}
};
should do the trick.
This should work: write an extension method for strings of array, like so:
public static ToCsv(this string[] strings)
{
return String.Join("," strings);
}
Then just tack .ToCsv() at the end of your .ToArray() call above, and it should do the trick!

Resources