automatically expand the result of an odata function - asp.net-web-api

I defined an odata function to mimic $search which is not supported yet in the recent core release. I want to return the core entity plus an expanded entity which would translate into a js object on each Person in the returned json values array.
I tried odata/People/MyNS.Find(text='john', orderby='CreatedOn')?$expand=CurrentWork where CurrentWork is on People, but that did not work.
Thoughts on how to do this?
// my controller code for the function
[HttpGet]
public ActionResult<ICollection<People>> Find([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (text == null || text.Length == 0)
return Get().ToList();
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn";
return _db.People
.Where(p => p.FirstName.Contains(text)
|| p.LastName.Contains(text)
|| p.Nickname.Contains(text))
.OrderBy(orderBy)
.Take(5000)
.ToList();
}
Regular expansion of CurrentWork in a non-function works fine e.g. odata/People?$expand=CurrentWork.

By looking at the Linq query, it's fetching only People data and not any of it's child collections. You should use Include to fetch data for child collections along with parent entity like below. Read more on loading related entities here.
// my controller code for the function
[HttpGet]
public ActionResult<ICollection<People>> Find([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (text == null || text.Length == 0)
return Get().ToList();
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn";
return _db.People
.Where(p => p.FirstName.Contains(text)
|| p.LastName.Contains(text)
|| p.Nickname.Contains(text))
.Include(p => p.CurrentWork) // I have added this line
.OrderBy(orderBy)
.Take(5000)
.ToList();
}
Note: You still need to use $expand=CurrentWork as query string. Without this query string, server will remove child collections before sending response to client.

Here's what I came up with in the end. I noticed that the included entities were pulling in alot of data from the database so I reduced down the pull quite a bit by being specific. Include just pulled everything and I could not reduce the Include down directly so I had to use a Select.
[HttpGet]
public IOrderedQueryable Find2([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn DESC";
if (text == null || text.Length == 0)
return Get().OrderBy(orderBy);
var r = LikeToRegular(text);
return _db.People
.AsNoTracking() // can't use if using lazy loading
.Select(p => new
{
p.FirstName,
p.LastName,
p.Nickname,
p.CreatedOn,
p.CurrentWork.Title,
p.CurrentWork.Company.CompanyName
})
// Forces local computation, so pulls entire people dataset :-(
.Where(x => Regex.IsMatch(x.LastName ?? "", r)
|| Regex.IsMatch(x.FirstName ?? "", r, RegexOptions.IgnoreCase)
|| Regex.IsMatch(x.Nickname ?? "", r, RegexOptions.IgnoreCase)
|| Regex.IsMatch($"{x.FirstName} {x.LastName}", r,
RegexOptions.IgnoreCase))
.OrderBy(orderBy);
}
// Allow some wildcards in the search...
public static String LikeToRegular(String value)
{
return "^" + Regex.Escape(value)
.Replace("_", ".")
.Replace("%", ".*") + "$";
}

Related

Linq To Select All controls where DI contains some text from ControlCollection

Linq has always befuddled me. I am trying to extract all controls from an ASP.Net form page where the ID of the control contains a specific string. The control collection is hierarchical and I want to return any matching controls from all levels. Am I anywhere in the ballpark here? I could really use some help/education. The collection parameter is the collection of controls from the page and controlID is the text I am searching for.
public static Control FindControlsByControlID(ControlCollection collection, string controlID)
{
IEnumerable<Control> controls = collection.Cast<Control>();
IEnumerable<Control> matchedControls = controls
.SelectMany(p => p.Controls.Cast<Control>()
.SelectMany(c => c.Controls.Cast<Control>())
.Where(d => d != null ? d.ID != null ? d.ID.Contains(controlID) : false : false))
.Where(a => a != null ? a.ID != null ? a.ID.Contains(controlID) : false : false);
ConcurrentQueue<Control> cq;
if (matchedControls != null)
cq = new ConcurrentQueue<Control>(matchedControls);
else
return null;
...
Thanks in advance!
Use an extension method to get all child controls:
public static class ControlExt {
public static IEnumerable<Control> AndSubControls(this Control aControl) {
var work = new Queue<Control>();
work.Enqueue(aControl);
while (work.Count > 0) {
var c = work.Dequeue();
yield return c;
foreach (var sc in c.Controls.Cast<Control>()) {
yield return sc;
if (sc.Controls.Count > 0)
work.Enqueue(sc);
}
}
}
}
Now you can test all the subcontrols in your ControlCollection:
IEnumerable<Control> matchedControls = controls.SelectMany(c => c.AndSubControls())
.Where(a => a != null && a.ID != null && a.ID.Contains(controlID));

How to get all items with the same value in list of lists c# LINQ?

I have a to add a specific requirement in a piece of code already implemented.
The data structure is something of this sort:
public class Module
{
public string Type;
public string ID;
public List<Point> Points = new List<Point>();
}
public class Point
{
public string Type;
public string Location;
public string Connection;
}
Originally LINQ was used to return all modules which certain characteristics
List<Module> miList = Modules.Where(m => m.Type != null
&& m.ID == "A"
&& m.Points.Where(t => t.Connection == ""
&& SimilarPoint(t.Type, x).ToList())
.Count() > 0)
.ToList();
with x an input to the function. The new requirement dictates that the modules returned shall all have points with Connection equal to "" and the same value in the Location field.
It seemed to me that the SelectMany could be used to this end, but I am not getting what I expected.
How should the function above be modified?
Thanks in advance
Not exactly sure what the SimilarPoint(t.Type, x) does here.
May be you should try something like this and find out if it works for you -
var resultSet = Modules.Where(m => !string.IsNullOrEmpty(m.Type) && m.ID.Equals("A"))
.Select(n =>
new Module {
Type=n.Type,
ID=n.ID,
Points= n.Points.Where(p => String.IsNullOrEmpty(p.Connection) && String.IsNullOrEmpty(p.Location)).ToList()
})
.ToList();
You said all the returned modules have the same Location, but that doesn't explain how you select which Location so I arbitrarily picked the first matching module's location:
var miQuery1 = Modules.Where(m => m.Type != null
&& m.ID == "A"
&& m.Points.Where(t => t.Connection == ""
&& SimilarPoint(t.Type, x).ToList()).Count() > 0)
.Where(m => m.Points.All(p => p.Connection == ""));
var miQuery2 = miQuery1.Where(m => m.Location == miQuery1.First().Location);
List<Module> miList = miQuery2.ToList();

Can't use == in LINQ Extension Method

I've got the following struct that is the key for my dictionary:
public struct CodeAttribute
{
public int ProcessorId;
public Enums.TransactionType transactionType;
public string ErrorMessage;
}
I've got the following dictionary (one value for now as it's just an example):
var errors = new Dictionary<CodeAttribute, int>
{
{CreateCodeAttributeList(2, Enums.TransactionType.Order, "Invalid ProcessorId sent in the Payment Request"), 100 }
};
And I'm trying to pull out the item in the dictionary that matches on the struct that has a match for both its ProcessorId and TransactionType properties:
private static string GetRelatedMessage(int errorCode, Dictionary<CodeAttribute, int> errorsList)
{
CodeAttribute codeAttribute = errorsList.Where(e => e.Key.ProcessorId == _processorId)
.Where(e => e.Key.transactionType == _transactionType) == errorCode;
return codeAttribute.ErrorMessage;
}
I also want to match on error code as part of the filtering, not just paymentprocessorId and transactionType, just a side note. The item in the dictionary must match all 3 values in order to get the right one in our case.
UPDATE
I tried this as well,and yes I get the error that it can't convert IEnumerable to CodeAtribute
CodeAttribute codeAttributes = errorsList.Where(e => e.Key.ProcessorId == _processorId)
.Where(e => e.Key.transactionType == _transactionType)
.Where(e => e.Value.Equals(errorCode));
UPDATE
with the help of Sam I think this may work
CodeAttribute codeAttribute = errorsList.FirstOrDefault(e => e.Key.ProcessorId ==
_processorId && e.Key.transactionType == _transactionType
&& e.Value == errorCode).Key;
If I understand correctly then you want
var codeAttribute = errorsList.FirstOrDefault(e =>
e.Key.ProcessorId == _processorId
&& e.Key.transactionType == _transactionType
&& e.Value == errorCode);
if(codeAttribute == null)
{
//no item matches in the dictionary.
}
return codeAttribute.Key.ErrorMessage;
Note that codeAttribute will be a KeyValuePair so you will need the codeAttribute.Key.ErrorMessage as your return value.
You don't need to use Where as that will return an IEnumerable so this won't work if you want a single item.
You probably need to go with something like this:
CodeAttribute codeAttribute = errorsList.FirstOrDefault(e => e.Key.ProcessorId == _processorId && e.Key.transactionType ==_transactionType)
While the other answers are correct, I would probably write it like this:
var errorMessage = errorsList
.Where(e => e.Key.ProcessorId == _processorId
&& e.Key.transactionType == _transactionType
&& e.Value == errorCode)
.Select(e => e.Key.ErrorMessage)
.FirstOrDefault();
That is, push the condition to filter earlier on, select the data I want from that result-set, and then take the first result (should one exist) of the transformed data.
Since the IEnumerable queries are lazy then this will still stop on the first sucessfully filtered object.
Since the source is a Dictionary, it may be also prudent to set up a relevant Equals/GetHashCode and structure the code such that it will be used.

How to create a list of child IDs

In my controller I have a method that receives a decimal value (id).
The objective of this method is to recover a list of old revisions from a database table containing work permits. Each record on this table has a WorkPermitID as a primary key and OldRevisionWorkPermitID referencing the ID of the previous version.
I have no problems when collecting the children IDs (old versions), but it raises an exception indicating that LINQ to Entities does not recognize .ToString() method.
What I'm doing wrong? I know that I need to do without converting to string (WorkPermitID is defined as numeric in the database), but I tried several ways with no success.
public ActionResult GetVersions(decimal id){
var model = new PermisosTrabajoModel();
List<string> ChildIDs = new List<string>();
var WP = OtWeb.WorkPermit.Single(q => q.WorkPermitID == id);
while (WP.OldRevisionWorkPermitID != null)
{
var child = WP.OldRevisionWorkPermitID;
ChildIDs.Add(child.ToString());
WP = OtWeb.WorkPermit.Single(q => q.WorkPermitID == child);
}
model.WPs = OtWeb.WorkPermit
.Where(q => q.DeptID == 1
&& ChildIDs.Contains(q.WorkPermitID.ToString())).ToList();
return View (model);
}
Solution1
If both of your fields are decimal... Don't use ToString(), and use a list of decimal
var model = new PermisosTrabajoModel();
var childIDs = new List<decimal>();
var WP = OtWeb.WorkPermit.Single(q => q.WorkPermitID == id);
while (WP.OldRevisionWorkPermitID != null)
{
childIDs.Add(WP.OldRevisionWorkPermitID);
WP = OtWeb.WorkPermit.Single(q => q.WorkPermitID == child);
}
model.WPs = OtWeb.WorkPermit
.Where(q => q.DeptID == 1
&& childIDs.Contains(q.WorkPermitID)).ToList();
Solution2
In linq2entities, you can use SqlFunctions.StringConvert instead of ToString() for a numeric value.
SqlFunctions.StringConvert(q.WorkPermitId)
instead of
q.WorkPermitID.ToString()
for example

How to check for nulls using LINQ

I have this code. How can I check for null values with the SingleOrDefault method?
public static List<ETY.Rol> GetRolesByApplicationAndCompany(this UsuarioContext usuario, int company, int app)
{
List<ETY.Company> lCompanies= usuario.Companies;
var roles = lCompanies.
SingleOrDefault(e => (e.Id == company)).Applications.
SingleOrDefault(a => a.Id == app).Roles;
return roles;
}
You could try looking at a Maybe/IfNotNull extension method (here and here).
Or use Linq syntax something like this (untested):
var q = from company in lCompanies.SingleOrDefault(e => e.Id == company)
where company != null
let application = company.Applications.SingleOrDefault(a => a.Id == app)
where application != null
select application.Roles;
(Greg Beech's answer is better if the Single condition is guaranteed)
Do you mean returning null or an empty list if any of the SingleOrDefault returns null? In that case:
var company = lCompanies.SingleOrDefault(e => (e.Id == company));
if(company != null) {
var application = company.Applications.SingleOrDefault(a => a.Id == app);
if(application!=null) {
return application.Roles;
}
}
return null; //Or: return new List<ETY.Rol>();
Rather than using SingleOrDefault you could write a chained query as follows. You lose the semantics of ensuring that there's only a single application or company, but if you know that to always be the case then it shouldn't be a problem.
return from c in lCompanies where c.Id == company
from a in c.Applications where a.Id == app
select a.Roles;

Resources