Linq pass null to Where clause - linq

I have method which accept expression for Linq Where clause. Sometimes I would like to ignore Where clause and do not use it.
I have tried to pass null to the method like this
GetUsersView(null)
but got exception. How correctly do this?
private IQueryable<UserView> GetUsersView(Expression<Func<User, bool>> expression)
{
return _userRepository.GetAll().
Where(expression).
Select(p => new UserView
{
Id = p.Id,
Active = p.Orders.Any(c => c.Active && (c.TransactionType == TransactionType.Order || c.TransactionType == TransactionType.Subscription)),
DateStamp = p.DateStamp,
Email = p.Email,
FirstName = p.FirstName,
LastName = p.LastName,
Message = p.Message,
UsersManager = p.Orders.Select(o => o.Product).Any(w => w.UsersManager && w.Active)
});
}

Passing nulls to methods is a horrible idea. Passing u => true is not very readable either. Create two methods instead - one which has parameter, and other, which don't have. Also I see your method have two responsibilities - it filters users, and converts them to UserViews. I think filtering users by predicate should occur in repository.
You can also create extension method IQueryable<UserView> ToViews(this IQueryable<User> source).
public static IQueryable<UserView> ToViews(this IQueryable<User> source)
{
return source.Select(u => new UserView
{
Id = u.Id,
Active = u.Orders.Any(o => o.Active &&
(o.TransactionType == TransactionType.Order ||
o.TransactionType == TransactionType.Subscription)),
DateStamp = u.DateStamp,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName,
Message = u.Message,
UsersManager = u.Orders.Select(o => o.Product)
.Any(p => p.UsersManager && p.Active)
});
}
In this case code will look like:
private IQueryable<UserView> GetUserViews()
{
return _userRepository.GetAll().ToViews();
}
private IQueryable<UserView> GetUserViews(Expression<Func<User, bool>> predicate)
{
// move filtering to repository
return _userRepository.GetAll(predicate).ToViews();
}

Try using
GetUsersView(u=>true);
or if you would prefer not to type the expression all the time, you can create an overloaded function that provides a default expression.
IQueryable<UserView> GetUsersView()
{
return GetUsersView(u=>true);
}

Related

Group by query not executing as expected

I have query like this for TheEntity type:
var source = await _repository.Queryable.AsNoTracking()
.Where(Condition1())
.Where(Condition2(params))
.GroupBy(GroupByFunction)
.Select(SelectFunction)
.OrderBy(o => o.Field1)
.ToAsyncEnumerable().ToList();
This query select all records which fulfill conditions: Condition1, Condition2, but does not group them as I expected.
SelectFunction and GroupByFunction looks like below:
private readonly Expression<Func<IGrouping<TheEntityGroupByData, TheEntity>, TheEntitySelectData>> SelectFunction =
e => new TheEntitySelectData()
{
Field1 = e.Key.Field1,
Field2 = e.Key.Field2,
...
FieldN = e.Key.FieldN,
Field(N+1) = e.Sum(x=>x.Field(N+1)),
...
FieldK = e.Sum(x=>x.FieldK),
};
private readonly Expression<Func<TheEntity, TheEntityGroupByData>> GroupByFunction =
e => new TheEntityByData()
{
Field1 = e.Field1,
Field2 = e.Field2,
...
FieldN = e.Key.FieldN,
};
TheEntityGroupByData, TheEntitySelectData are helper DTO/PO_Os types.
I intendent to fire grouping on database rather than server, but this behavior does not work even in server memory.
I use .Net Core 2.0.5 and EntityFrameworkCore 2.0.2.
My question is where is the problem in this approach?
edit - What I mean query not work as I expected is that: If I will have two the same records in db (same by grouping key) that fulfill Condition1 and Condition2, query will return 2 instead of one records.
Filtering conditions looks like below:
private static Expression<Func<TheEntity, bool>> Condition1()
{
return (e) => e.Field1 == SOME_CONSTANT;
}
private static Expression<Func<TheEntity, bool>> Condition2(RequestParam param)
{
Expression<Func<TheEntity, bool>> whereSth;
if (param.some.HasValue)
whereSth = (e) => e.Field2 <= param.some.Value;
else
whereSth = (e) => true;
return whereSth;
}

Using linq with PropertyValueCollection

I'd like to know if there is a way or more efficient way using Linq. Instead of using the while loop, is it possible to do a select where using Linq query?
public UserPrincipal GetUser(string sUserName, string spwd, string domain, string ou)
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, domain, ou, sUserName, spwd);
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, sUserName);
DirectoryEntry user = (DirectoryEntry)oUserPrincipal.GetUnderlyingObject();
PropertyCollection pc = user.Properties;
IDictionaryEnumerator ide = pc.GetEnumerator();
ide.Reset();
while (ide.MoveNext())
{
PropertyValueCollection pvc = ide.Entry.Value as PropertyValueCollection;
if (ide.Entry.Key.ToString() == "XYZ")
{
//Response.Write(string.Format("name: {0}", ide.Entry.Key.ToString()));
//Response.Write(string.Format("Value: {0}", pvc.Value));
}
}
.......;
.......;
}
Thanks!
The reason you can't use Where() on a PropertyCollection is because it implements the non-generic IEnumerable, when Where() is a method of only the generic version. You can convert a PropertyCollection to a generic IEnumerable by using Cast<T>().
var matches = pc.Cast<DictionaryEntry>().Where(p => p.Key.ToString() == "XYZ");
foreach( var match in matches )
{
Response.Write(string.Format("name: {0}", match.Key));
Response.Write(string.Format("Value: {0}", match.Value));
}
This way is doubtfully any more efficient.
Try this:
foreach (PropertyValueCollection pvc in pc.OfType<PropertyValueCollection>().Where(v => v.PropertyName == "XYZ"))
{
Response.Write(string.Format("name: {0}", pvc.PropertyName));
Response.Write(string.Format("Value: {0}", pvc.Value));
}
Besides, you can try to use ForEach:
pc.OfType<PropertyValueCollection>()
.Where(v => v.PropertyName == "XYZ")
.ToList()
.ForEach(pvc =>
{
Response.Write(string.Format("name: {0}", pvc.PropertyName));
Response.Write(string.Format("Value: {0}", pvc.Value));
});
This is a pretty old thread, but I was searching for a way to work with PropertyCollection using LINQ. I tried the suggested methods, but I always get an invalid cast exception when casting to DictionaryEntry. And with a DictionaryEntry, things like FirstOrDefault are funky. So, I simply do this:
var directoryEntry = adUser.GetUnderlyingObject() as DirectoryEntry;
directoryEntry.RefreshCache();
var propNames = directoryEntry.Properties.PropertyNames.Cast<string>();
var props = propNames
.Select(x => new { Key = x, Value = directoryEntry.Properties[x].Value.ToString() })
.ToList();
With that in place, I can then easily query for any property directly by Key. Using the coalesce and safe navigation operators allows for defaulting to an empty string or whatever..
var myProp = props.FirstOrDefault(x => x.Key == "someKey"))?.Value ?? string.Empty;
Note that the "adUser" object is the UserPrincipal object.

Distinct by Attribute inside the item on my collection

I have an IEnumerable<MyObject> collection, with N MyObject elements.
MyObject is a class with a Title, a Description and an ID (as string).
I'd like to have my collection with distinct list of MyObject, due to the ID field.
So if 2 MyObject have got the same ID, one should be deleted (don't care which, I need unique ID).
How can I do it with LINQ?
Tried :
myList = myList.GroupBy(o => o.ID);
but seems I need a cast?
You can implement a custom IEqualityComparer<MyObject>. Then you can use Enumerable.Distinct to filter out duplicates.
class DistinctIdComparer : IEqualityComparer<MyObject> {
public bool Equals(MyObject x, MyObject y) {
return x.Id == y.Id;
}
public int GetHashCode(MyObject obj) {
return obj.Id.GetHashCode();
}
}
Now it's simple:
IEnumerable<MyObject> distinct = myObjects.Distinct(new DistinctIdComparer());
Or you can use Enumerable.GroupBy what is even simpler:
distinct = myObjects.GroupBy(o => o.ID)
.Select(g => g.First());
If you wants only unique id then you can try.
var uniqueIds = myObjects.Select(x=>x.ID).Distinct();
Or for Unique ID Objects
List<MyObject> objs = new List<MyObject> ();
myObjects.ForEach(x=>{
if(objs.Find(y=>y.ID == x.ID)== null)
objs.Add(x);
});

Entity Framework - LINQ selection in POCO generic List property

I'm having some issues setting a generic list property of a POCO object when from an EF context. For instance I have a very simple object that contains the following:
public class foo
{
public string fullName;
public Entity entity;
public List<SalesEvent> eventList;
}
My code to populate this object from looks something like this:
.Select(x => new foo()
{
fullName = x.vchFirstName + " " + x.vchLastName,
entity = new EntityVo()
{
address1 = x.vchAddress1,
entityId = x.iEntityId,
emailAddress = x.vchEmailAddress,
firstName = x.vchFirstName,
lastName = x.vchLastName,
city = x.vchCity,
state = x.chState,
workNumber = x.vchWorkNumber,
mobileNumber = x.vchMobileNumber,
siteId = x.iSiteId
}
eventList = _context.Events
.Where(e => e.iEntityId == x.iEntityId
&& e.iStatusId >= eventStatusMin
&& e.iStatusId <= eventStatusMax)
.Select(e => new List<SalesEventMatchVo>
{
new SalesEventMatchVo()
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
}
}).FirstOrDefault()
}).ToArray();
This issue I'm having is that I'm unable to populate the eventList property with all of the events, it's only grabbing the first record(which makes sense looking at the code). I just cant seem to figure out to populate a the entire list.
Is there a reason simply removing the FirstOrDefault at the end isn't the solution here? I feel like I might be misunderstanding something.
EDIT:
I think I see what you are trying to do. The issue is that you are creating a list in the select statement, when the select statement works only over one thing at a time. It is basically mapping an input type to a new output type.
Try something like this instead:
eventList = _context.Events.Where(e => e.iEntityId == x.iEntityId && //FILTER EVENTS
e.iStatusId >= eventStatusMin &&
e.iStatusId <= eventStatusMax)
.Select(e => new SalesEventMatchVo() //MAP TO SALESEVENT
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
})
.ToList() //CONVERT TO LIST
As a side note, unless you actually need a List for some reason, I would store foo.eventList as IEnumerable<SalesEvent> instead. This allows you to skip the List conversion at the end, and in some scenarios enables neat tricks like delayed and/or partial execution.
Also, I'm not sure what the point of your .Select(q=>q) statements are in several lines of the SalesEventMatchVo initializer, but I'm pretty sure you can chop them out. If nothing else, you should Select after Where, as Where can reduce the work performed by all following statements.

Linq one to many insert when many already exists

So I'm new to linq so be warned what I'm doing may be completely stupid!
I've got a table of caseStudies and a table of Services with a many to many relasionship
the case studies already exist and I'm trying to insert a service whilst linking some case studies that already exist to it. I was presuming something like this would work?
Service service = new Service()
{
CreateDate = DateTime.Now,
CreatedBy = (from u in db.Users
where u.Id == userId
select u).Take(1).First(),
Description = description,
Title = title,
CaseStudies = (from c in db.CaseStudies
where c.Name == caseStudy
select c),
Icon = iconFile,
FeatureImageGroupId = imgGroupId,
UpdateDate = DateTime.Now,
UpdatedBy = (from u in db.Users
where u.Id == userId
select u).Take(1).First()
};
But This isn't correct as it complains about
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Data.Objects.DataClasses.EntityCollection'
Can somebody please show me the correct way.
Thanks in advance
Yo have to add the query result to the case studies collection instead of trying to replace it.
var service = new Service { ... };
foreach (var caseStudy in db.CaseStudies.Where(s => s.Name == caseStudyName)
{
service.CaseStudies.Add(caseStudy);
}
You can wrap this in an extension method and get a nice syntax.
public static class ExtensionMethods
{
public static void AddRange<T>(this EntityCollection<T> entityCollection,
IEnumerable<T> entities)
{
// Add sanity checks here.
foreach (T entity in entities)
{
entityCollection.Add(entity);
}
}
}
And now you get the following.
var service = new Service { ... };
service.CaseStudies.AddRange(db.CaseStudies.Where(s => s.Name == caseStudyName));

Resources