The code below wont run, as it complains that I am trying to add a type Anon into a type of Clients. How can I store certain results in another variable after projecting them originally and having lost the original Type.
(PS. I have made my example simple but am actually dealing with a more complex case. Not projecting is not an option in my actual case. Edited to provide clarification.)
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new {
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
.ToList();
var certainClients = new List<Clients> { };
foreach (var client in clients)
{
if(client.DateAdded.Date < DateTime.Today) {
certainClients.Add(client);
}
}
certainClients.Dump();
There are two options.
First. Instead of using an anon data type, use Clients datatype. As in effect you are creating Clients object -
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new Clients{
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
Second. Create a list of object and assign whatever custom/anon data type to it -
var certainClients = new List<object> { };
The best way is to project to a custom business entity class.
This means that we actually define the class ourselves. For example.
public class ClientEntity
{
public string LastName;
public DateTime DateAdded;
// etc for custom fields or properties you want
}
Then we can simply project to our custom made class
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new ClientEntity{
LastName = c.LastName.ToUpper(),
DateAdded = c.DateAdded,
etc
})
This way it avoids List <object> which is not type safe and doesn't need to be similar to the original client class for example if we want the length of the original name.
Related
I have a huge object with a lot of attributes and child objects. Because of a poorly designed database which I can't control, I need to find matching objects in allCourses with the attribute CourseType = "SVYE". For those who matches the condition, I want to change all values from "SVYE" to "SVYR" instead and add them to the original object allCourses.
I realized that when you declare svCourse you still have the old references in courses which will cause all objects with the value on CourseType = "SVYR". Instead of every match should be one with CourseType = "SVYE" and one with CourseType = "SVYR".
How could I create a copy of the matching values without having the reference to allCourses in the new var svCourses without declaring every attribute again?
new Course(){
name = a.name
// etc..
}
My code:
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
foreach(var svCourse in svCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;
If I understand you correctly, you want to keep Courses that have Occasions with CourseType.Equals("SVYE") unchanged in your collection and add a copy of that courses with modified only CourseType (CourseType = "SVYR"). Right?
If so, you must somehow deep-clone the svCourses collection with 3-rd party library:
Json.NET NuGet:
var svCourses = allCourses.Select(x => new
{
Course = x,
MatchedOccasions = x.Occasions
.Where(y => string.Equals(y.CourseType, "SVYE"))
.ToArray()
})
.Where(x => x.MatchedOccasions.Length > 0)
.ToList();
var clonedSvCourses = JArray.FromObject(svCourses.Select(x => x.Course).ToList()).ToObject<List<Course>>();
allCourses.AddRange(clonedSvCourses);
foreach(var occasion in svCourses.SelectMany(x => x.MatchedOccasions))
{
occasion.CourseType = "SVYR";
}
return allCourses;
FastDeepCloner NuGet I think, can be used too.
I found a solution on my own!
I used a third-party library called CloneExtensions. This library could be used to create a deep copy of your object without declaring every attribute. This new variable newCourses which is a deep copy of svCourses doesn't have the old reference to allCourses, which solves the problem.
This solution will replace o.CourseType = "SVYR"; for all ocassions in the variable newCourses.
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
var newCourses = svCourses.Select(x =>
CloneExtensions.CloneFactory.GetClone(x));
foreach(var svCourse in newCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;
Any means Determines whether any element of a sequence satisfies a condition.
What you need is
List all occasions of all courses.
Choose occasions that CourseType = SVYE
var svCourses = allCourses
.SelectMany(c => c.Occasions)
.Where(o => o.CourseType.Equals("SVYE"))
.ToList();
This is purely to improve my skill. My solution works fine for the primary task, but it's not "neat". I'm currently working on a .NET MVC with Entity framework project. I know only basic singular LINQ functions which have sufficed over the years. Now I'd like to learn how to fancy.
So I have two models
public class Server
{
[Key]
public int Id { get; set; }
public string InstanceCode { get; set; }
public string ServerName { get; set; }
}
public class Users
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int ServerId { get; set; } //foreign key relationship
}
In one of my view models I was asked to provide a dropdown list for selecting a server when creating a new user. The drop down list populated with text and value Id as an IEnumerable
Here's my original property for dropdown list of servers
public IEnumerable<SelectListItem> ServerItems
{
get { Servers.ToList().Select(s => new selectListItem { Value = x.Id.ToString(), Text = $"{s.InstanceCode}#{s.ServerName}" }); }
}
Update on requirements, now I need to display how many users are related to each server selection. Ok no problem. Here's what I wrote off the top of my head.
public IEnumerable<SelectListItem> ServerItems
{
get
{
var items = new List<SelectListItem>();
Servers.ToList().ForEach(x => {
var count = Users.ToList().Where(t => t.ServerId == x.Id).Count();
items.Add(new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}#{x.ServerName} ({count} users on)" });
});
return items;
}
}
This gets my result lets say "localhost#rvrmt1u (8 Users)" but thats it..
What if I want to sort this dropdown list by user count. All I'm doing is another variable in the string.
TLDR ... I'm sure that someone somewhere can teach me a thing or two about converting this to a LINQ Query and making it look nicer. Also bonus points for knowing how I could sort the list to show servers with the most users on it first.
OK, we have this mess:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(x => {
var count = Users.ToList().Where(t => t.ServerId == x.Id).Count();
items.Add(new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}#{x.ServerName} ({count} users on)" });
});
return items;
Make a series of small, careful, obviously-correct refactorings that gradually improve the code.
Start with: Let's abstract those complicated operations to their own methods.
Note that I've replaced the unhelpful x with the helpful server.
int UserCount(Server server) =>
Users.ToList().Where(t => t.ServerId == server.Id).Count();
Why on earth is there a ToList on Users? That looks wrong.
int UserCount(Server server) =>
Users.Where(t => t.ServerId == server.Id).Count();
We notice that there is a built-in method that does these two operations together:
int UserCount(Server server) =>
Users.Count(t => t.ServerId == server.Id);
And similarly for creating an item:
SelectListItem CreateItem(Server server, int count) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}#{server.ServerName} ({count} users on)"
};
And now our property body is:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(server =>
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
});
return items;
Already much nicer.
Never use ForEach as a method if you're just going to pass a lambda body! There's already a built-in mechanism in the language that does it better! There is no reason to write items.Foreach(item => {...}); when you could simply write foreach(var item in items) { ... }. It's simpler and easier to understand and debug, and the compiler can optimize it better.
var items = new List<SelectListItem>();
foreach (var server in Servers.ToList())
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
Much nicer.
Why is there a ToList on Servers? Completely unnecessary!
var items = new List<SelectListItem>();
foreach(var server in Servers)
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
Getting better. We can eliminate the unnecessary variable.
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server, UserCount(server));
return items;
Hmm. This gives us an insight that CreateItem could be doing the count itself. Let's rewrite it.
SelectListItem CreateItem(Server server) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}#{server.ServerName} ({UserCount(server)} users on)"
};
Now our prop body is
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server);
return items;
And this should look familiar. We have re-invented Select and ToList:
var items = Servers.Select(server => CreateItem(server)).ToList();
Now we notice that the lambda can be replaced with the method group:
var items = Servers.Select(CreateItem).ToList();
And we have reduced that whole mess to a single line that clearly and unambiguously looks like what it does. What does it do? It creates an item for every server and puts them in a list. The code should read like what it does, not how it does it.
Study the techniques I used here carefully.
Extract complex code to helper methods
Replace ForEach with real loops
Eliminate unnecessary ToLists
Revisit earlier decisions when you realize there's an improvement to be made
Recognize when you are re-implementing simple helper methods
Don't stop with one improvement! Each improvement makes it possible to do another.
What if I want to sort this dropdown list by user count?
Then sort it by user count! We abstracted that away into a helper method, so we can use it:
var items = Servers
.OrderBy(UserCount)
.Select(CreateItem)
.ToList();
We now notice that we're calling UserCount twice. Do we care? Maybe. It could be a perf problem to call it twice, or, horrors, it might not be idempotent! If either are a problem then we need to undo a decision we made before. It's easier to deal with this situation in comprehension mode rather than fluent mode, so let's rewrite as a comprehension:
var query = from server in Servers
orderby UserCount(server)
select CreateItem(server);
var items = query.ToList();
Now we go back to our earlier:
SelectListItem CreateItem(Server server, int count) => ...
and now we can say
var query = from server in Servers
let count = UserCount(server)
orderby count
select CreateItem(server, count);
var items = query.ToList();
and we are only calling UserCount once per server.
Why go back to comprehension mode? Because to do this in fluent mode makes a mess:
var query = Servers
.Select(server => new { server, count = UserCount(server) })
.OrderBy(pair => pair.count)
.Select(pair => CreateItem(pair.server, pair.count))
.ToList();
And it looks a little ugly. (In C# 7 you could use a tuple instead of an anonymous type, but the idea is the same.)
The trick with LINQ is just to type return and go from there. Don't create a list and add items to it; there is usually a way to select it all in one go.
public IEnumerable<SelectListItem> ServerItems
{
get
{
return Servers.Select
(
server =>
new
{
Server = server,
UserCount = Users.Count( u => u.ServerId = server.Id )
}
)
.Select
(
item =>
new SelectListItem
{
Value = item.Server.Id.ToString(),
Text = string.Format
(
#"{0}{1} ({2} users on)" ,
item.Server.InstanceCode,
item.Server.ServerName,
item.UserCount
)
}
);
}
}
In this example there are actually two Select statements-- one to extract the data, and one to do the formatting. In an ideal situation the logic for those two tasks would be separated into different layers, but this is an OK compromise.
I am trying to select a few columns from a single row using LINQ to Entities and then split each column into its own point of an array.
I have the LINQ query getting the results I want, but I can't figure out how to split it into the array. I thought using .ToArray() would work but it doesn't split them.
var LinkData = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
}).ToArray();
How can I split the results from the query so I have a single array of two elements: one for Type and one for Route?
Your Select-statement already creates a list of two-value-items which are stored in instances of anonymous type. So there is no need to create a new two-dimensional array for this. Your linkdata already contains the data you want, however if you´re after one specific combination of (Type, Route) simply call linkedData[myIndex].Route or linkedData[myIndex].Type respectively.
EDIT: If you really want arrays then the following should work:
var arr = linkedData.Select(x => new[] { x.Rote, x.Type }).ToArray();
Which will give you an array of arrays where every element itself contains an array of two elements.
var section = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
})
.SingleOrDefault();
var LinkData = new [] {section.Type, section.Route};
Use a List<> instead
Thats what i would do.
Create a public class of Link Data so:
public class LinkData
{
public string type {get; set;}
public string Route {get;set;}
}
Then in your code
create a List of the Link Data:
List<LinkData> LinkDataList = new List<LinkData>();
then create an object
LinkData obj = new LinkData
add stuff to the object
obj.type = db.Section.Where(s => s.ID==SectionID).Select s=> s.Type new s.Type).SingleOrDefault();
obj.Route = db.Section.Where(s => s.ID==SectionID).Select s=> s.Route new s.Type).SingleOrDefault();;
LinkDataList.Add(obj)
This should give you a clear indication of whats what :)
I'm re-writing some of my old NHibernate code to be more database agnostic and use NHibernate queries rather than hard coded SELECT statements or database views. I'm stuck with one that's incredibly slow after being re-written. The SQL query is as such:
SELECT
r.recipeingredientid AS id,
r.ingredientid,
r.recipeid,
r.qty,
r.unit,
i.conversiontype,
i.unitweight,
f.unittype,
f.formamount,
f.formunit
FROM recipeingredients r
INNER JOIN shoppingingredients i USING (ingredientid)
LEFT JOIN ingredientforms f USING (ingredientformid)
So, it's a pretty basic query with a couple JOINs that selects a few columns from each table. This query happens to return about 400,000 rows and has roughly a 5 second execution time. My first attempt to express it as an NHibernate query was as such:
var timer = new System.Diagnostics.Stopwatch();
timer.Start();
var recIngs = session.QueryOver<Models.RecipeIngredients>()
.Fetch(prop => prop.Ingredient).Eager()
.Fetch(prop => prop.IngredientForm).Eager()
.List();
timer.Stop();
This code works and generates the desired SQL, however it takes 120,264ms to run. After that, I loop through recIngs and populate a List<T> collection, which takes under a second. So, something NHibernate is doing is extremely slow! I have a feeling this is simply the overhead of constructing instances of my model classes for each row. However, in my case, I'm only using a couple properties from each table, so maybe I can optimize this.
The first thing I tried was this:
IngredientForms joinForm = null;
Ingredients joinIng = null;
var recIngs = session.QueryOver<Models.RecipeIngredients>()
.JoinAlias(r => r.IngredientForm, () => joinForm)
.JoinAlias(r => r.Ingredient, () => joinIng)
.Select(r => joinForm.FormDisplayName)
.List<String>();
Here, I just grab a single value from one of my JOIN'ed tables. The SQL code is once again correct and this time it only grabs the FormDisplayName column in the select clause. This call takes 2498ms to run. I think we're on to something!!
However, I of course need to return several different columns, not just one. Here's where things get tricky. My first attempt is an anonymous type:
.Select(r => new { DisplayName = joinForm.FormDisplayName, IngName = joinIng.DisplayName })
Ideally, this should return a collection of anonymous types with both a DisplayName and an IngName property. However, this causes an exception in NHibernate:
Object reference not set to an instance of an object.
Plus, .List() is trying to return a list of RecipeIngredients, not anonymous types. I also tried .List<Object>() to no avail. Hmm. Well, perhaps I can create a new type and return a collection of those:
.Select(r => new TestType(r))
The TestType construction would take a RecipeIngredients object and do whatever. However, when I do this, NHibernate throws the following exception:
An unhandled exception of type 'NHibernate.MappingException' occurred
in NHibernate.dll
Additional information: No persister for: KitchenPC.Modeler.TestType
I guess NHibernate wants to generate a model matching the schema of RecipeIngredients.
How can I do what I'm trying to do? It seems that .Select() can only be used for selecting a list of a single column. Is there a way to use it to select multiple columns?
Perhaps one way would be to create a model with my exact schema, however I think that would end up being just as slow as the original attempt.
Is there any way to return this much data from the server without the massive overhead, without hard coding a SQL string into the program or depending on a VIEW in the database? I'd like to keep my code completely database agnostic. Thanks!
The QueryOver syntax for conversion of selected columns into artificial object (DTO) is a bit different. See here:
16.6. Projections for more details and nice example.
A draft of it could be like this, first the DTO
public class TestTypeDTO // the DTO
{
public string PropertyStr1 { get; set; }
...
public int PropertyNum1 { get; set; }
...
}
And this is an example of the usage
// DTO marker
TestTypeDTO dto = null;
// the query you need
var recIngs = session.QueryOver<Models.RecipeIngredients>()
.JoinAlias(r => r.IngredientForm, () => joinForm)
.JoinAlias(r => r.Ingredient, () => joinIng)
// place for projections
.SelectList(list => list
// this set is an example of string and int
.Select(x => joinForm.FormDisplayName)
.WithAlias(() => dto.PropertyStr1) // this WithAlias is essential
.Select(x => joinIng.Weight) // it will help the below transformer
.WithAlias(() => dto.PropertyNum1)) // with conversion
...
.TransformUsing(Transformers.AliasToBean<TestTypeDTO>())
.List<TestTypeDTO>();
So, I came up with my own solution that's a bit of a mix between Radim's solution (using the AliasToBean transformer with a DTO, and Jake's solution involving selecting raw properties and converting each row to a list of object[] tuples.
My code is as follows:
var recIngs = session.QueryOver<Models.RecipeIngredients>()
.JoinAlias(r => r.IngredientForm, () => joinForm)
.JoinAlias(r => r.Ingredient, () => joinIng)
.Select(
p => joinIng.IngredientId,
p => p.Recipe.RecipeId,
p => p.Qty,
p => p.Unit,
p => joinIng.ConversionType,
p => joinIng.UnitWeight,
p => joinForm.UnitType,
p => joinForm.FormAmount,
p => joinForm.FormUnit)
.TransformUsing(IngredientGraphTransformer.Create())
.List<IngredientBinding>();
I then implemented a new class called IngredientGraphTransformer which can convert that object[] array into a list of IngredientBinding objects, which is what I was ultimately doing with this list anyway. This is exactly how AliasToBeanTransformer is implemented, only it initializes a DTO based on a list of aliases.
public class IngredientGraphTransformer : IResultTransformer
{
public static IngredientGraphTransformer Create()
{
return new IngredientGraphTransformer();
}
IngredientGraphTransformer()
{
}
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
Guid ingId = (Guid)tuple[0];
Guid recipeId = (Guid)tuple[1];
Single? qty = (Single?)tuple[2];
Units usageUnit = (Units)tuple[3];
UnitType convType = (UnitType)tuple[4];
Int32 unitWeight = (int)tuple[5];
Units rawUnit = Unit.GetDefaultUnitType(convType);
// Do a bunch of logic based on the data above
return new IngredientBinding
{
RecipeId = recipeId,
IngredientId = ingId,
Qty = qty,
Unit = rawUnit
};
}
}
Note, this is not as fast as doing a raw SQL query and looping through the results with an IDataReader, however it's much faster than joining in all the various models and building the full set of data.
IngredientForms joinForm = null;
Ingredients joinIng = null;
var recIngs = session.QueryOver<Models.RecipeIngredients>()
.JoinAlias(r => r.IngredientForm, () => joinForm)
.JoinAlias(r => r.Ingredient, () => joinIng)
.Select(r => r.column1, r => r.column2})
.List<object[]>();
Would this work?
Assuming I have:
public class Cluster
{
List<Host> HostList = new List<Host>();
}
public class Host
{
List<VDisk> VDiskList = new List<VDisk>();
}
public class VDisk
{
public string Name {get; set}
}
I need all the hosts from a Cluster object that have a VDisk of a given name. I can do it with a foreach but would rather have a LINQ query. I tried a SelectMany() but it is returning the VDisk and not the Hosts. Do I need to implement a custom Comparer to do this?
Here's what I Tried:
Cluster CurrentCluster = new Cluster();
// add some hosts here
VDisk vdisk = new VDisk();
vdisk.Name="foo";
so now I want all the hosts that have a Vdisk named "foo"
this returns the vdisk, not the hosts:
CurrentCluster.Hosts.SelectMany(h => h.VDisks.Where(v => v.Name == vdisk.Name));
SelectMany will indeed return the inner collections, flattened into one large collection. You want your predicate to be on Hosts, not on VDisks, since what you're looking for is a list of Hosts.
This might work:
CurrentCluster.Hosts.Where(h => h.VDisks.Any(v => v.Name == vdisk.Name));
It basically says, "Return all hosts Where any of the VDisks match the condition v.Name == vdisk.Name.
I've also seen developers who don't know about Any write something like this:
CurrentCluster.Hosts.Where(h => h.VDisks.Count(v => v.Name == vdisk.Name) > 0);
Sometimes I feel there's a certain readability advantage to the latter, if one thinks that Count is a more intuitive name than Any. Both should do the job, I just prefer the former.
First you need to declare VDiskList in Host as public or internal.
Then you can use this code:
var hostList = new List<Host>();
var givenVDiskName = "sample name";
var selectedHosts = (from h in hostList
where h.VDiskList.Any(vd => vd.Name == givenVDiskName)
select h).ToList();