Linq select returning string instead of object - c#

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;
})

Related

Linq one to many with filter

I have an Entity Framework database that I'm querying, so I'm using linq-to-entities.
Here's my query:
// 'Find' is just a wrapper method that returns IQueryable
var q = r.Find(topic =>
topic.PageId != null &&
!topic.Page.IsDeleted &&
topic.Page.IsActive)
// These are standard EF extension methods, which are used to include
linked tables. Note: Page_Topic has a one-to-many relationship with topic.
.Include(topic => topic.Page.Route)
.Include(topic => topic.Page_Topic.Select(pt => pt.Page.Route))
// HERE'S THE QUESTION: This select statement needs to flatten Page_Topic (which it does). But it seems to do it in the wrong place. To explain, if I were to include another column that depended on Page_Topic (for example: 'PillarRoutName2', I'd have to apply the same flattening logic to that column too. Surely the filtering of Page_Topic should be done higher up the query in a DRY way.
.Select(x => new
{
TopicName = x.Name,
HubRouteName = x.Page.Route.Name,
PillarRouteName = x.Page_Topic.FirstOrDefault(y => y.IsPrimary).Page.Route.Name
}).ToList();
Surely the filtering of Page_Topic should be done higher up the query in a DRY way.
Correct! And it's easy to do this:
.Select(x => new
{
TopicName = x.Name,
HubRouteName = x.Page.Route.Name,
FirstTopic = x.Page_Topic.FirstOrDefault(y => y.IsPrimary)
})
.Select(x => new
{
TopicName = x.TopicName,
HubRouteName = x.HubRouteName,
PillarRouteName = x.FirstTopic.Page.Route.Name,
PillarRoutName2 = x.FirstTopic. ...
}).ToList();
Depending on where you start to get properties from FirstTopic you can also use x.Page_Topic.FirstOrDefault(y => y.IsPrimary).Page or .Page.Route in the first part.
Note that you don't need the Includes. They will be ignored because the query is a projection (Select(x => new ...).

Linq avoid calling function twice

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);

Retain default order for Linq Contains

I want to retain the default order that comes from sql, after processing by Linq also.I know this question has been asked before. Here is a link Linq Where Contains ... Keep default order.
But still i couldn't apply it to my linq query correctly. could anyone pls help me with this? Thanks!
Here is the query
var x = db.ItemTemplates.Where(a => a.MainGroupId == mnId)
.Where(a => a.SubGruopId == sbId)
.FirstOrDefault();
var ids = new List<int> { x.Atribute1, x.Atribute2, x.Atribute3, x.Atribute4 };
var y = db.Atributes.Where(a => ids.Contains(a.AtributeId))
.Select(g => new
{
Name = g.AtributeName,
AtType = g.AtributeType,
Options = g.atributeDetails
.Where(w=>w.AtributeDetailId!=null)
.Select(z => new
{
Value=z.AtributeDetailId,
Text=z.AtDetailVal
})
});
Your assumption is wrong. SQL server is the one that is sending the results back in the order you are getting them. However, you can fix that:
var x = db.ItemTemplates.Where(a => a.MainGroupId == mnId)
.Where(a => a.SubGruopId == sbId)
.FirstOrDefault();
var ids = new List<int> { x.Atribute1, x.Atribute2, x.Atribute3, x.Atribute4 };
var y = db.Atributes.Where(a => ids.Contains(a.AtributeId))
.Select(g => new
{
Id = g.AtributeId,
Name = g.AtributeName,
AtType = g.AtributeType,
Options = g.atributeDetails
.Where(w=>w.AtributeDetailId!=null)
.Select(z => new
{
Value=z.AtributeDetailId,
Text=z.AtDetailVal
})
})
.ToList()
.OrderBy(z=>ids.IndexOf(z.Id));
Feel free to do another select after the orderby to create a new anonymous object without the Id if you absolutely need it to not contain the id.
PS. You might want to correct the spelling of Attribute, and you should be consistent in if you are going to prefix your property names, and how you do so. Your table prefixes everything with Atribute(sp?), and then when you go and cast into your anonymous object, you remove the prefix on all the properties except AtributeType, which you prefix with At. Pick one and stick with it, choose AtName, AtType, AtOptions or Name, Type, Options.

how to do a contains on an array of strings?

I am using a predicate builder class and I need to invoke the contains method on an array of strings so in the code below instead of radio I would be passing in an array of strings:
wherePredicate = wherePredicate.Or(m => m.MediaType.Contains("Radio"));
the full code section:
if (param.iMediaGroupID > 0)
{
var wherePredicate = PredicateBuilder.False<MediaChannelModel>();
var ss = new NeptuneRepository<Lookup_MediaTypes>();
var mediagroups = ss.FindWhere(m => m.MediaGroupID == param.iMediaGroupID).Select(m => m.Name);
//problem area
wherePredicate = wherePredicate.Or(m => mediagroups.Contains(m.MediaType));
predicate = predicate.And(wherePredicate);
}
mediaGroups is: ["Radio","Tv","Magazine"]
If m.MediaType is any of these values then the predicate is true.
Is there a way to do this in C#?
I suspect you want something like:
wherePredicate = wherePredicate.Or(m => array.Contains(m.MediaType));
Or perhaps:
wherePredicate = wherePredicate.Or(m => array.Any(x => m.MediaType.Contains(x)));
If neither of those are what you're after, please clarify your requirements.
EDIT: The problem you're now facing is that you're not actually asking whether an array contains the value. You're asking whether a query contains a value. If you change it to an actual array, you may well find it works:
var mediagroups = ss.FindWhere(m => m.MediaGroupID == param.iMediaGroupID)
.Select(m => m.Name)
.ToArray();
However, if these are querying the same database, you'd be better off trying to do this in some kind of join.
Jon Skeet's answer worked perfectly for me. I had been struggling to make the .Contains search for a substring in a string array against the database, rather than try to find a substring in a single C# string object. Thank you!
Here's the modified code that worked for me:
var predicate = PredicateBuilder.False<ClientXMemberDetail>();
predicate = predicate.Or(x => strArrselectedCustomMemberNumbers.Any<string>(y => x.MemberID.Contains(y)));
CustomSearchMembersAlreadyMatched = ClientXContext.ClientXMemberDetails
.AsExpandable()
.Where(predicate)
.ToList()
.Select(r => r.MemberID.ToString()).ToList();
(ClientXContext above is an instance of the ObjectContext class, strArrselectedCustomMemberNumbers is a string array, ClientXMemberDetails is ObjectSet, where ClientXMemberDetail is the EntityObject)
Edit: Anonymized my client's name

Why does .Equals not work in this LINQ example?

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.

Categories