Is there any way to refactor this code so that it can omit unnecessary WHEREs and JOINs if the values passed into the function are null (this code works just fine right now if all parameters are passed in)? The "SearchItems" and "ItemDistance" functions are table-level functions for performing fulltext search and distance calculations, respectively.
public IQueryable<ItemSearchResult> Search(string keywords, int? category, float? latitude, float? longitude)
{
return from item in _db.Items
join searchItems in _db.SearchItems(keywords)
on item.ItemId equals searchItems.ItemId
join itemDistance in _db.ItemDistance(latitude.Value, longitude.Value)
on item.ItemId equals itemDistance.ItemId
where item.Category == category.Value
select new ItemSearchResult()
{
Item = item,
Distance = itemDistance.Distance
};
}
I'm making the assumption that if either of latitude or longitude is not provided, you don't calculate the distance (I'd probably encapsulate both into a class and pass that instead of passing separately to avoid confusion). If one or the other is not provided, the default Distance is used for the search result.
public IQueryable<ItemSearchResult> Search(string keywords, int? category, float? latitude, float? longitude)
{
IEnumerable<ItemSearchResult> result = null;
var query = _db.Items.AsEnumerable();
if (category.HasValue)
{
query = query.Where( i => i.Category == category.Value );
}
if (!string.IsNullOrEmpty(keywords))
{
query = query.Where( i => _db.SearchItems(keywords)
.Any( s => s.ItemId == i.ItemId ));
}
if (latitude.HasValue && longitude.HasValue)
{
result = from item in query
join distance in _db.ItemDistance( latitude.Value, longitude.Value )
on item.ItemId equals distance.ItemId
select new ItemSearchResult
{
Item = item,
Distance = distance.Distance
};
}
else
{
result = query.Select( i => new ItemSearchResult { Item = i } );
}
return result != null
? result.AsQueryable()
: new List<ItemSearchResult>().AsQueryable();
}
For starters, the only value passed into that function that can be null is keywords, as all others are non-nullable value types. So I'll consider that only (if you need it, others can be treated similarly).
Just don't use joins in the first place:
return from item in _db.Items
where keywords == null || _db.SearchItems(keywords).Select(si => si.ItemId).Contains(item.ItemId) &&
...
where item.Category == category
select new ItemSearchResult()
{
Item = item,
Distance = itemDistance.Distance
};
Related
I have a query table like this, people let me ask how can I get the Positions data of the current user when I get the userid to query with the Document table.
var claimsIdentity = _httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier)?.Value.ToString();
var query = from c in _context.Documents
join u in _context.Users on c.UserID equals u.Id
join p in _context.Positions on u.Id equals p.UserID
where c.UserID.ToString() == userId
select new { c, u, p };
The data you query is almost enough, but it contains duplicate entries of Document and Position. If you want the final query to be put in a single object like this:
{
User = ...,
Documents = ...,
Positions = ...
}
You just need to project it using Linq-to-object (because all the data is loaded and ready for projection on the client):
var result = (from document in _context.Documents
join user in _context.Users on document.UserID equals user.Id
join position in _context.Positions on user.Id equals position.UserID
where document.UserID.ToString() == userId
select new { document, user, position }).AsEnumerable()
.GroupBy(e => e.user.Id)
.Select(g => new {
User = g.First().user,
Documents = g.GroupBy(e => e.document.Id)
.Select(e => e.First().document),
Positions = g.GroupBy(e => e.position.Id)
.Select(e => e.First().position)
}).FirstOrDefault();
If you don't want to fetch the user info, you don't need to join that DbSet but instead join the two Document and Position directly like this:
var result = (from document in _context.Documents
join position in _context.Positions on document.UserID equals position.UserID
where document.UserID.ToString() == userId
select new { document, position }).AsEnumerable()
.GroupBy(e => e.document.UserID)
.Select(g => new {
Documents = g.GroupBy(e => e.document.Id)
.Select(e => e.First().document),
Positions = g.GroupBy(e => e.position.Id)
.Select(e => e.First().position)
}).FirstOrDefault();
Note that I suppose your Document and Position both have its own primary key property of Id (adjust that to your actual design).
Finally, usually if your User entity type exposes navigation collection properties to the Document and Position. We can have a better (but equal) query like this:
var user = _context.Users
.Include(e => e.Documents)
.Include(e => e.Positions)
.FirstOrDefault(e => e.Id.ToString() == userId);
It's much simpler because all the joining internally translated by the EFCore. The magic is embedded right into the design of navigation collection properties.
I would like to talk about the important note of the condition UserID.ToString() == userId or Id.ToString() == userId. You should avoid that because it would be translated into a query that breaks the using of index for filtering. Instead try parsing for an int userId first (looks like it's a string in your case) and use that parsed int directly for comparison in the query, like this:
if(!int.TryParse(userId, out var intUserId)){
//return or throw exception
}
//here we have an user id of int, use it directly in your query
var user = _context.Users
.Include(e => e.Documents)
.Include(e => e.Positions)
.FirstOrDefault(e => e.Id == intUserId);
That applies similarly to other queries as well.
How can I handle null values in some of the fields when populating the IQueryable table from the db? PetIDTag can have nulls.
IQueryable<PetTable> petIQ= from s in _context.PetT select s;
if (!String.IsNullOrEmpty(searchString))
{
petIQ = _context.PetT
.Where(x => x.PetName.ToString() == searchString)
.Select(s => new PetTable
{
PetName = s.PetName,
PetAddress = s.PetAddress,
PetIDTag = s.PetIDTag.Where(x => s.PetIDTag != null)
});
}
Calling .Where() on a single value doesn't make sense.
If you want to filter your entire query, add that to the query's Where().
I need to create a table in View by this View Model:
public class ApplicationContentViewModel
{
public BPMSPARS.Models.MySql.application application {get; set;}
public BPMSPARS.Models.MySql.content content { get; set; }
public BPMSPARS.Models.MySql.app_delegation app_delegation { get; set; }
}
But the query for creating new Table is very complex.
I use this query in MySQL, and I can get correct results by using it.
SELECT APP_UID, (SELECT CON_VALUE FROM content WHERE CON_CATEGORY = 'PRO_TITLE' AND CON_ID =
(SELECT PRO_UID from app_delegation WHERE del_thread_status='open' and USR_UID = '00000000000000000000000000000001' AND APP_UID = '9134216305aaaea1b67c4e2096663219')) AS TASK_NAME,
(SELECT CON_VALUE FROM content WHERE CON_CATEGORY = 'TAS_TITLE' AND CON_ID =
(SELECT TAS_UID from app_delegation WHERE del_thread_status='open' and USR_UID = '00000000000000000000000000000001' AND APP_UID = '9134216305aaaea1b67c4e2096663219')) AS PROCESS_NAME FROM app_delegation
WHERE del_thread_status='open' and USR_UID = '00000000000000000000000000000001' AND APP_UID = '9134216305aaaea1b67c4e2096663219'
But, I have to convert this query in linq or EF in MVC.
How Can I write This Query in Entity Framework query?
And How Can I display results in View?
Your SQL query seems (very) peculiar to me, as it is quite redundant. I am going to assume the sub-queries return a single value and enforce it with LINQ.
First I pulled out the common sub-query over app_delegation:
var USR_APP_Delegation = from a in app_delegation
where a.del_thread_status == "open" &&
a.USR_UID == "00000000000000000000000000000001" &&
a.APP_UID == "9134216305aaaea1b67c4e2096663219"
select a;
In LINQ it is easy to combine the two UID queries into one query:
var UIDs = (from a in USR_APP_Delegation
select new { a.PRO_UID, a.TAS_UID })
.Single();
Now you can do the name subqueries:
var TASK_NAME = (from c in content
where c.CON_CATEGORY == "PRO_TITLE" &&
c.CON_ID == UIDs.PRO_UID
select c.CON_VALUE)
.Single();
var PROCESS_NAME = (from c in content
where c.CON_CATEGORY == "TAS_TITLE" &&
c.CON_ID == UIDs.TAS_UID
select c.CON_VALUE)
.Single();
Then you can put all the queries together for the final result:
var ans = (from a in USR_APP_Delegation
select new {
a.APP_UID,
TASK_NAME,
PROCESS_NAME
})
.Single();
Again, this makes it obvious that your e.g. returning APP_UID when you know exactly what it is, and you are combining TASK_NAME and PROCESS_NAME into a query for no real advantage.
I would suggest using join against content makes a much more understandable query (even in SQL) and makes it clearer what is being returned:
var names = from a in app_delegation
join cpro in content on new { CON_ID = a.PRO_UID, CON_CATEGORY = "PRO_TITLE" } equals new { cpro.CON_ID, cpro.CON_CATEGORY }
join ctas in content on new { CON_ID = a.PRO_UID, CON_CATEGORY = "TAS_TITLE" } equals new { ctas.CON_ID, ctas.CON_CATEGORY }
where a.del_thread_status == "open" &&
a.USR_UID == "00000000000000000000000000000001" &&
a.APP_UID == "9134216305aaaea1b67c4e2096663219"
select new {
a.APP_UID,
Task_Name = ctas.CON_VALUE,
Process_Name = cpro.CON_VALUE
};
I have this query and i wanted to only select distinct value from Charges table(Port Name must only display once).
public List<Port> GetPortsByCountryOrigin(int countryId, TransportDirection transdirection, TransportType transtype)
{
using (var ctx = CreateDbContext())
{
return (from item in ctx.Ports
join s in ctx.Charges
on item.PortId equals s.PortId
where (s.TransportDirection == transdirection &&
s.TransportType == transtype
&& item.CountryId == countryId)
select item).ToList();
}
}
Currently, the Ports.Name are repeating values.
Try .Distinct() before your ToList()
I have this:
var q = (from order in db.Orders
from payment in db.Payments
.Where(x => x.ID == order.paymentID)
.DefaultIfEmpty()
from siteUser in db.SiteUsers
.Where(x => x.siteUserID == order.siteUserID)
.DefaultIfEmpty()
where siteUser.siteUserID != null
select new
{
order.orderID,
order.dateCreated,
payment.totalAmount,
siteUser.firstName,
siteUser.lastName
});
I want to add on to it like this:
switch (_qs["sort"])
{
case "0":
q = q.OrderByDescending(x => x.dateCreated);
break;
case "1":
q = q.OrderBy(x => x.dateCreated);
break; ...
I've done this before with a single table, but the multiple tables in the first code block force me to specify a select statement which causes it to be an anonymous type. How can this be done?
Note: I even tried to make a class with the properties that i'm selecting and casting the query to this type, still a no go.
Not sure I understand the question but the code you pasted looks valid to me.
I checked:
var q = (
from order in db.Orders
join payment in db.Payments on
order.paymentID equals payment.ID into payments
from payment in payments.DefaultIfEmpty()
join siteUser in db.SiteUsers on
order.siteUserID equals siteUser.siteUserID into siteUsers
from siteUser in siteUsers.DefaultIfEmpty()
where siteUser.siteUserID != null
select
new
{
order.orderID,
order.dateCreated,
payment.totalAmount,
siteUser.firstName,
siteUser.lastName
});
switch (sort)
{
case "0":
q = q.OrderByDescending(x => x.dateCreated);
break;
case "1":
q = q.OrderBy(x => x.dateCreated);
break;
}
var restult = q.ToList();
This works.