How to get all items with the same value in list of lists c# LINQ? - 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();

Related

automatically expand the result of an odata function

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("%", ".*") + "$";
}

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.

Use method in entity framework query

Is there anyway around this error? I'd like to reuse the same lamba expression in other queries instead of having duplication. Can LinqKit or other linq expression do this?
Error
LINQ to Entities does not recognize the method 'Boolean GetEvent(Tournaments.Data.Entities.Event, System.String)' method, and this method cannot be translated into a store expression.
Code
public MobileEventDetailModel GetDetails(string applicationId)
{
var #event = (from e in _eventsRepository.DataContext.Events.Include(q => q.Assets.Select(a => a.Asset))
where GetEvent(e, applicationId)
select new
{
e.Id,
e.EventParent.Name,
LogoId = (from a in e.Assets
where a.Type == EventAssetType.Logo
select a.AssetId).FirstOrDefault()
}).FirstOrDefault();
return new MobileEventDetailModel
{
Id = #event.Id,
Name = #event.Name,
Logo = string.Format("{0}{1}{2}", Config.BaseUrl, Config.ImagesPath, #event.LogoId)
};
}
public bool GetEvent(Event #event, string applicationId)
{
return #event.Active && #event.Visible && #event.MobileEventApplications.Any(m =>
m.MobileApplication.ApplicationId == applicationId &&
(!m.MobileApplication.ActivationLength.HasValue || EntityFunctions.AddDays(DateTime.Now, 1) < EntityFunctions.AddMonths(m.MobileApplication.DateActivated, m.MobileApplication.ActivationLength.Value)));
}
You need to use an Expression:
public MobileEventDetailModel GetDetails(string applicationId)
{
var event = _eventsRepository.DataContext.Events.Include(q => q.Assets.Select(a => a.Asset))
.Where(GetEvent(applicationId))
.Select(a => new
{
a.Id,
a.EventParent.Name,
LogoId = (from b in a.Assets
where b.Type == EventAssetType.Logo
select b.AssetId).FirstOrDefault()
}).FirstOrDefault();
return new MobileEventDetailModel
{
Id = event.Id,
Name = event.Name,
Logo = string.Format("{0}{1}{2}", Config.BaseUrl, Config.ImagesPath, event.LogoId)
};
}
public Expression<Func<Event, bool>> GetEvent(int applicationId)
{
return = a => a.Active
&& a.Visible
&& a.MobileEventApplications
.Any(m => m.MobileApplication.ApplicationId == applicationId
&& (!m.MobileApplication.ActivationLength.HasValue
|| EntityFunctions.AddDays(DateTime.Now, 1)
< EntityFunctions
.AddMonths(m.MobileApplication.DateActivated, m.MobileApplication.ActivationLength.Value)
)
);
}
Update: Sorry it was late the other night, the changed version is hopefully more what you were looking for.

How to Search through all fields in a LINQ table?

in LINQ how do i search all fields in a table, what do i put for ANYFIELD in the below?
Thanks
var tblequipments = from d in db.tblEquipments.Include(t => t.User).Include(t => t.ChangeLog).Include(t => t.AssetType)
where d."ANYFIELD" == "VALUE" select d;
You can't. You must compare each field individually. It doesn't make sense to compare all fields, given a field may not even be of the same type as the object you're comparing to.
You can, using reflection. Try this:
static bool CheckAllFields<TInput, TValue>(TInput input, TValue value, bool alsoCheckProperties)
{
Type t = typeof(TInput);
foreach (FieldInfo info in t.GetFields().Where(x => x.FieldType == typeof(TValue)))
{
if (!info.GetValue(input).Equals(value))
{
return false;
}
}
if (alsoCheckProperties)
{
foreach (PropertyInfo info in t.GetProperties().Where(x => x.PropertyType == typeof(TValue)))
{
if (!info.GetValue(input, null).Equals(value))
{
return false;
}
}
}
return true;
}
And your LINQ query:
var tblequipments = from d in db.tblEquipments.Include(t => t.User).Include(t => t.ChangeLog).Include(t => t.AssetType)
where CheckAllFields(d, "VALUE", true) select d;
The third parameter should be true if you want to check all fields and all properties, and false if you want to check only all fields.
EDIT: Someone already built this...see here.
Not a full answer, but I don't agree with assertion that you simply can't...
You could come up with an extension method that dynamically filtered the IQueryable/IEnumerable (I'm guessing IQueryable by the db variable) based on properties of a similar type for you. Here's something whipped up in Linqpad. It references PredicateBuilder and is by no means complete/fully accurate, but I tested it out in Linq-to-SQL on some of my tables and it worked as described.
void Main()
{
YourDbSet.WhereAllPropertiesOfSimilarTypeAreEqual("A String")
.Count()
.Dump();
}
public static class EntityHelperMethods
{
public static IQueryable<TEntity> WhereAllPropertiesOfSimilarTypeAreEqual<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
{
var param = Expression.Parameter(typeof(TEntity));
var predicate = PredicateBuilder.True<TEntity>();
foreach (var fieldName in GetEntityFieldsToCompareTo<TEntity, TProperty>())
{
var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
Expression.Equal(
Expression.PropertyOrField(param, fieldName),
Expression.Constant(value)), param);
predicate = predicate.And(predicateToAdd);
}
return query.Where(predicate);
}
// TODO: You'll need to find out what fields are actually ones you would want to compare on.
// This might involve stripping out properties marked with [NotMapped] attributes, for
// for example.
private static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
{
Type entityType = typeof(TEntity);
Type propertyType = typeof(TProperty);
var fields = entityType.GetFields()
.Where (f => f.FieldType == propertyType)
.Select (f => f.Name);
var properties = entityType.GetProperties()
.Where (p => p.PropertyType == propertyType)
.Select (p => p.Name);
return fields.Concat(properties);
}
}
Useful resources for the unresolved part:
Finding the relevant properties
if this help some one.
first find all properties within Customer class with same type as query:
var stringProperties = typeof(Customer).GetProperties().Where(prop =>
prop.PropertyType == query.GetType());
then find all customers from context that has at least one property with value equal to query:
context.Customer.Where(customer =>
stringProperties.Any(prop =>
prop.GetValue(customer, null) == query));

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

Resources