Using Linq to Objects a query might need to filter based on the result of a function and return the value of that function.
For example
files.Where(x => string.IsNullOrWhiteSpace(x.getProperty(propName)))
.GroupBy(x => x.getProperty(propName));
Does the compiler recognize that the value is going to be required for grouping and keep it?
If it doesn't then there must be a way to select to an anonymous type and query the Where and GroupBy statements against that. Is it possible to do this with an anonymous type?
I am able to declare a class and use that.
class fileSelector
{
internal string prop;
internal myFile file;
}
var groups = files
.Select(x => new fileSelector() { prop = x.getProperty(propName), file = x })
.Where(x => !string.IsNullOrWhiteSpace(x.prop))
.GroupBy(x => x.prop);
But is there a way to do this with an anonymous type?
This is what I tried for an anonymous type
var groups = files.Select(x => new { x.getProperty(propName), x })
.Where(x => !string.IsNullOrWhiteSpace(x.prop))
.GroupBy(x => x.prop);
But this gives the error
Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.
Final answer
var groups = files
.Select(x => new { prop = x.getProperty(propName), file = x })
.Where(x => !string.IsNullOrWhiteSpace(x.prop))
.GroupBy(x => x.prop, x => x.file);
Does the compiler recognize that the value is going to be required for grouping and keep it?
No, since getProperty might have an intended side effect.
If it doesn't then there must be a way to select to an anonymous type and query the Where and GroupBy statements against that. Is it possible to do this with an anonymous type?
Yes. Your code should work as-it-is by just replacing new fileSelector() {...} with new {...}. Note, though, that in your code (and in the modified version using the anonymous type), the elements of the grouping are fileSelector and the anonymous type, not myFile. See Scott Chamberlain's solution for how to fix that.
Alternatively, you could use the let clause to store intermediary values:
var groups = from file in files
let prop = file.getProperty(propName)
where !string.IsNullOrWhiteSpace(prop)
group file by prop;
Does the compiler recognize that the value is going to be required for grouping and keep it?
No, it will touch the value twice.
You where actually quite close with your final example, you can do it with a annonamous type, just give names for each of the members of the anonymous type then add a element selector to make the body of the grouping the file property.
var groups = files
.Select(x => new { prop = x.getProperty(propName), file = x })
.Where(x => !string.IsNullOrWhiteSpace(x.prop))
.GroupBy(x => x.prop, x => x.file);
Related
The first query returns a list of string, and I am passing them into another table to find corresponding items, but nothing happens. no error message or nothing
var classIds = _contextSpecRepo.Get(x => x.cId.Equals(cId)).Select(x => x.classNames).Distinct().ToList();
// issue happens in the following query
var classes= Repository.Get(x => x.Id.Equals(classIds)).ToList();
The call to Equals, which takes object, hides the problem: you are comparing a single Id to a list of Ids, rather than checking if the Id is present in a collection. This compiles, but yields no result.
Here is how you can fix it:
var classes= Repository.Get(x => classIds.Any(y => y == x.Id)).ToList();
or
var classes= Repository.Get(x => classIds.Contains(x.Id)).ToList();
If you must do it in 2 queries then you have to use contains
var classes= Repository.Get(x => classIds.Contains(x.Id)).ToList();
A better solution would be to use a join on the tables.
you can also skip .ToList()
var classes= Repository.Get(x => classIds.Contains(x.Id));
I have the following code:
var languages = _languageService
.GetAll()
.Select(x => (((LanguageViewModel) new LanguageViewModel().InjectFrom(x))))
.ToList();
When executing this, languages becomes, as expected, a collection of LanguageViewModel objects:
What I am trying to do is, when selecting, also convert the object's Code property to uppercase, as so:
var languages = _languageService
.GetAll()
.Select(x => (((LanguageViewModel) new LanguageViewModel().InjectFrom(x)).Code = x.Code.ToUpper()))
.ToList();
I'm expecting the languages object to have multiple LanguageViewModels in it but it looks like this:
My guess is the fact that I'm using a statement like Select(x => (new Object().Property = Value)) it selects the Property. But then, how can I return an object with one of its properties changed? Using object initializer before inject is not an option as it gets overriden, using it after the Inject is not possible, as it is not casted yet, so I got to the solution here which does not seem to work. Any advice greatly appreciated.
You can't write the lambda body as a single expression that does what you want, but you don't need to. You can put multiple statements in a lambda:
var languages = _languageService
.GetAll()
.Select(x => {
var lvm = (LanguageViewModel)new LanguageViewModel().InjectFrom(x);
lvm.Code = x.Code.ToUpper();
return lvm;
})
.ToList();
Your Select line could be rewritten to
.Select(x =>
{
var vm = new LanguageViewModel().InjectFrom(x);
vm.Code = vm.Code.ToUpper();
return vm;
})
I have two entities, which are related, ActiveContract and BudgetSource. I'm trying to grab all the BudgetSource which are marked as isActive = true, along with all associated ActiveContracts which are also marked isActive = true. I've tired:
var d = budgetSourceRep.All.Where(x => x.isAcitve)
.OrderBy(x => x.SourceName)
.Include(z => z.ActiveContracts.Where(q => q.isActive))
.Select(y => new EditSelectItemViewModel
{
Id = y.Id,
SourceName = y.SourceName,
DisplayOnNew = y.DisplayOnNew,
NumberOfTimesUsed = y.ActiveContracts.Count()
}).ToList();
but that gives me an error
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties. Parameter name: path
I then changed it to put the filter in the projection:
var d = budgetSourceRep.All.Where(x => x.isAcitve)
.OrderBy(x => x.SourceName)
.Include(z => z.ActiveContracts)
.Select(y => new EditSelectItemViewModel
{
Id = y.Id,
SourceName = y.SourceName,
DisplayOnNew = y.DisplayOnNew,
NumberOfTimesUsed = y.ActiveContracts.Count(a => a.isActive)
}).ToList();
that works, but I'm assuming it's going to make a second query to do that? If so, is there a way to do it in one shot.
that works, but i'm assuming it's going to make a second query to do that?
No, it is not. You can see for yourself by looking at the generated SQL for this query.
It is within the bounds of theoretical possibility for the query provider to have successfully translated your first query into SQL, but doing so is...hard. This simply isn't a feature that the developers of EF chose to put into their query provider. Instead you are forced to project out a collection of related entities in some way to filter them, you cannot use Include to do it for you.
This is related to my other question here. James World presented a solution as follows:
// idStream is an IObservable<int> of the input stream of IDs
// alarmInterval is a Func<int, TimeSpan> that gets the interval given the ID
var idAlarmStream = idStream
.GroupByUntil(key => key, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
<edit 2:
Question: How do I start the timers immediately without waiting for the first events to arrive? That's the root problem in my question, I guess. For that end, I planned on sending off dummy objects with the IDs I know should be there. But as I write in following, I ended up with some other problems. Nevertheless, I'd think solving that too would be interesting.
Forwards with the other interesting parts then! Now, if I'd like to group a complex object like the following and group by the key as follows (won't compile)
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
then I get into trouble. I'm unable to modify the part about SelectMany, Concat and Observable.Return so that the query would work as before. For instance, if I make query as
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key.First())))
.Subscribe(i => Console.WriteLine(i.Id + "-" + i.IsTest);
Then two events are needed before an output can be observed in the Subscribe. It's the effect of the call to First, I gather. Furthermore, I woul like to use the complex object attributes in the call to alarmInterval too.
Can someone offer an explanation what's going on, perhaps even a solution? The problem in going with unmodified solution is that the grouping doesn't look Ids alone for the key value, but also the IsTest field.
<edit: As a note, the problem probably could be solved firsly by creating an explicit class or struct and then that implements a custom IEquatable and secondly then using James' code as-is so that grouping would happen by IDs alone. It feels like hack though.
Also, if you want to count the number of times you've seen an item before the alarm goes off you can do it like this, taking advantage of the counter overload in Select.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.Select((count, alarm) => new { count, alarm }).TakeLast(1));
Note, this will be 0 for the first (seed) item - which is probably what you want anyway.
You are creating an anonymous type in your Select. Lets call it A1. I will assume your idStream is an IObservable. Since this is the Key in the GroupByUntil you do not need to worry about key comparison - int equality is fine.
The GroupByUntil is an IObservable<IGroupedObservable<int, A1>>.
The SelectMany as written is trying to be an IObservable<A1>. You need to just Concat(Observable.Return(grp.Key)) here - but the the type of the Key and the type of the Group elements must match or the SelectMany won't work. So the key would have to be an A1 too. Anonymous types use structural equality and the return type would be stream of A1 - but you can't declare that as a public return type.
If you just want the Id, you should add a .Select(x => x.Id) after the Throttle:
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)
.Select(x => x.Id))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
If you want A1 instead - you'll need to create a concrete type that implements Equality.
EDIT
I've not tested it, but you could also flatten it more simply like this, I think this is easier! It is outputing A1 though, so you'll have to deal with that if you need to return the stream somewhere.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.TakeLast(1));
Why does this yield an empty set?
Object[] types = {23, 234, "hello", "test", true, 23};
var newTypes = types.Select(x => x.GetType().Name)
.Where(x => x.GetType().Name.Equals("Int32"))
.OrderBy(x => x);
newTypes.Dump();
When you do your select you're getting an IEnumerable<String>. Then you're taking the types of each string in the list (which is all "String") and filtering them out where they aren't equal to "Int32" (which is the entire list). Ergo...the list is empty.
Equals works just fine, it's your query that isn't correct. If you want to select the integers in the list use:
var newTypes = types.Where( x => x.GetType().Name.Equals("Int32") )
.OrderBy( x => x );
Reverse the order of the operations:
var newTypes = types.Where(x => x is int)
.OrderBy(x => x)
.Select(x => x.GetType().Name);
(Notice this also uses a direct type check instead of the rather peculiar .GetType().Name.Equals(…)).
The thing with LINQ is you've got to stop thinking in SQL terms. In SQL we think like this:-
SELECT Stuff
FROM StufF
WHERE Stuff
ORDER BY Stuff
That is what your code looks like. However in LINQ we need to think like this :-
FROM Stuff
WHERE Stuff
SELECT Stuff
ORDER BY Stuff
var newTypes = types.Select(x => x.GetType().Name)
.Where(x => x.Equals("Int32"))
.OrderBy(x => x);
This doesn't work because the Select statement will convert every value in the collection to the name of the underlying type of that value. The resulting collection will contain only string values and hence they won't ever have the name Int32.