I have the following class definition.
public class people
{
public string first_name { get; set; }
public string last_name { get; set; }
public DateTime date_of_birth { get; set; }
}
I've then created an array of people as follows:
people[] the_people = new people[3];
the_people[0].first_name="Tony";
the_people[0].last_name="Carrot";
the_people[0].date_of_birth=new DateTime(1959-03-16);
the_people[1].first_name="Joe";
the_people[1].last_name="Tomato";
the_people[1].date_of_birth=new DateTime(1963-06-2);
the_people[2].first_name="Tarina";
the_people[2].last_name="Wends";
the_people[2].date_of_birth=new DateTime(1982-11-22);
How can I store the first_names of the_people object in a new string array such that an output like the following is obtained. Is this possible via linq
string[] the_peoples_first_names=new string[3] {"Tony","Joe","Tarina"};
Similarly how would I obtain an array of date times to store the date of births of all people in a separate DateTime array.
What you are trying to do can be done with LINQ. What you are basically asking for is a projection.
MSDN describes a projection as this:
Projection refers to the operation of transforming an object into a new form that often consists only of those properties that will be subsequently used. By using projection, you can construct a new type that is built from each object. You can project a property and perform a mathematical function on it. You can also project the original object without changing it.
So we want to project the objects in your the_people array into a new array. The documentation recommends using the Select LINQ operator:
var the_people_names = the_people.Select(p => p.first_name);
What goes inside of the Select is a delegate, often in the form of a lambda expression or anonymous delegate.
But we aren't quite there yet. Select is just a deferred evaluation that creates an enumerable sequence. It does not return an array. To create an array, we use .ToArray():
var the_people_names_array = the_people.Select(p => p.first_name).ToArray();
You can use this approach to any property of people class, including the date of birth.
You can learn more about LINQ on MSDN on the LINQ about page.
var firstNames = the_people.Select(p => p.first_name).ToArray();
var dates_of_birth = the_people.Select(p => p.date_of_birth).ToArray();
Related
Scenario:
I have to export an excel file which will contain list of Parts. We have enabled the user to select the columns and get only selected columns' data in the exported file. Since this is a dynamic report, I am not using any concrete class to map the report as this will result in exporting empty column headers in the report, which is unnecessary. I am using Dynamic Linq to deal with this scenario.
I have a list of dynamic objects fetched from dynamic linq.
[
{"CleanPartNo":"Test","Description":"test","AliasPartNo":["258","145","2313","12322"]},
{"CleanPartNo":"Test1","Description":"test1","AliasPartNo":[]}
]
How can I get 4 rows out of this json like
Please note that I cannot use a strongly typed object to deserialize/ Map it using JSON.Net
Update
Following is the code:
public class Part
{
public int Id { get; set; }
public string CleanPartNo { get; set; }
public string Description { get; set; }
public List<PartAlias> AliasPartNo { get; set; }
}
public class PartAlias
{
public int PartId { get; set; }
public int PartAliasId { get; set; }
public string AliasPartNo { get; set; }
}
var aliases = new List<PartAlias> {
new PartAlias{AliasPartNo="258" },
new PartAlias{AliasPartNo="145" },
new PartAlias{AliasPartNo="2313" },
new PartAlias{AliasPartNo="12322" }
};
List<Part> results = new List<Part> {
new Part{CleanPartNo="Test", Description= "test", PartAlias=aliases },
new Part{CleanPartNo="Test1", Description= "test1" }
};
var filters = "CleanPartNo,Description, PartAlias.Select(AliasPartNo) as AliasPartNo";
var dynamicObject = JsonConvert.SerializeObject(results.AsQueryable().Select($"new ({filters})"));
in the dynamicObject variable I get the json mentioned above
Disclaimer: The following relies on anonymous classes, which is not exactly the same as dynamic LINQ (not at all), but I figured that it may help anyway, depending on your needs, hence I decided to post it.
To flatten your list, you could go with a nested Select, followed by a SelectMany (Disclaimer: This assumes that every part has at least one alias, see below for the full code)
var flattenedResult = result.Select(part => part.AliasPartNumber.Select(alias => new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
})
.SelectMany(part => part);
You are first projecting your items from result (outer Select). The projection projects each item to an IEnumerable of an anonymous type in which each item corresponds to an alias part number. Since the outer Select will yield an IEnumerable<IEnumerable> (or omething alike), we are using SelectMany to get a single IEnumerable of all the items from your nested IEnumerables. You can now serialize this IEnumerable of instances of an anonymous class with JsonConvert
var json = sonConvert.SerializeObject(flatResults);
Handling parts without aliases
If there are no aliases, the inner select will yield an empty IEnumerable, hence we will have to introduce a special case
var selector = (Part part) => part.AliasPartNumber?.Any() == true
? part.AliasPartNumber.Select(alias => new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
})
: new[]
{
new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
}
};
var flattenedResult = result.Select(selector).SelectMany(item => item);
From json you provided you can get values grouped by their name in this way:
var array = JArray.Parse(json);
var lookup = array.SelectMany(x => x.Children<JProperty>()).ToLookup(x => x.Name, x => x.Value);
then this is just a manner of simple loop over the lookup to fill the excel columns.
However, I would suggest to do the flatenning before JSON. I tried for some time to make it happen even without knowing the names of the columns that are arrays, but I failed, and since it's your job, I won't try anymore :P
I think the best way here would be to implement custom converter that would just multiply objects for properties that are arrays. If you do it well, you would get infinite levels completely for free.
I have a strong typed list string collection consistenting of root level sites sometimes represented at the domain level, however sometimes not. For example:
http://x.com
http://x.com/y
http://x.com/y/w
http://x.com/y/z
http://a.com/b/c
http://a.com/b/c/d
http://a.com/b/c/e
I need to convert the string collection to a collection of strongly typed custom objects, such as the below:
public class UrlObject
{
public string url { get; set; }
public List<UrlObject> subUrls { get; set; }
}
The amount of characters and slashes in the "Parent" url can vary, I am curious if there is a way that building a collection that allows flexible for the size of the "Parent".
The expected output of this method would be a list collection with two objects, represented below:
object 1
Parent - http://x.com
subUrls- http://x.com/y,
http://x.com/y/w,
http://x.com/y/z
object 2
Parent: http://a.com/b/c
subUrls-http://a.com/b/c/d,
http://a.com/b/c/e
There is a class that already exists in .NET that will make your life a lot easier: System.Uri. Instead of your custom class, you can create a "conatiner" class like this:
public class UriContainer
{
public Uri Parent { get; set; }
public List<Uri> Children { get; set; }
}
You can easily turn a collection of string urls into Uri objects with a little bit of LINQ:
var urlStringList = new List<string>()
{
"http://x.com",
"http://x.com/y",
"http://x.com/y/w",
"http://x.com/y/z",
"http://a.com/b/c",
"http://a.com/b/c/d",
"http://a.com/b/c/e"
};
IEnumerable<Uri> uris = urlStringList.Select(x => new Uri(x));
From there you can GroupBy() the Host property, then inside each group, OrderBy() the Segments.Length property. Then take the First() one (this is the one with the least segments aka the "parent" per your example), then you take the rest of them as the "children":
var containerList = new List<UriContainer>();
foreach(var groupedUri in uris.GroupBy(x => x.Host))
{
var sorted = groupedUri.OrderBy(x => x.Segments.Length);
containerList.Add(new UriContainer()
{
Parent = sorted.First(),
Children = sorted.Skip(1).ToList()
});
}
The above code will give you a structure like this:
http://x.com/
http://x.com/y
http://x.com/y/w
http://x.com/y/z
----------------------
http://a.com/b/c
http://a.com/b/c/d
http://a.com/b/c/e
----------------------
This code has very little error prevention, you might want to make sure items are not null and such but it at least gives you a starting point.
Fiddle here
This is my MongoDB document structure:
{
string _id;
ObservableCollection<DataElement> PartData;
ObservableCollection<DataElement> SensorData;
...
other ObservableCollection<DataElement> fields
...
other types and fields
...
}
Is there any possibility to retrieve a concatenation of fields with the type ObservableCollection<DataElement>? Using LINQ I would do something like
var query = dbCollection
.AsQueryable()
.Select(x => new {
data = x
.OfType(typeof(ObservableCollection<DataElement>))
.SelectMany(x => x)
.ToList()
});
or alternatively
data = x.Where(y => typeof(y) == typeof(ObservableCollection<DataElement>)
.SelectMany(x => x).ToList()
Unfortunately .Where() and .OfType() do not work on documents, only on queryables/lists, so is there another possibility to achieve this? The document structure must stay the same.
Edit:
After dnickless answer I tried it with method 1b), which works pretty well for getting the fields thy way they are in the collection. Thank you!
Unfortunately it wasn't precisely what I was looking for, as I wanted to be all those fields with that specific type put together in one List, at it would be returned by the OfType or Where(typeof) statement.
e.g. data = [x.PartData , x.SensorData, ...] with data being an ObsverableCollection<DataElement>[], so that I can use SelectMany() on that to finally get the concatenation of all sequences.
Sorry for asking the question unprecisely and not including the last step of doing a SelectMany()/Concat()
Finally I found a solution doing this, but it doesn't seem very elegant to me, as it needs one concat() for every element (and I have more of them) and it needs to make a new collection when finding a non-existing field:
query.Select(x => new
{
part = x.PartData ?? new ObservableCollection<DataElement>(),
sensor = x.SensorData ?? new ObservableCollection<DataElement>(),
}
)
.Select(x => new
{
dataElements = x.part.Concat(x.sensor)
}
).ToList()
In order to limit the fields returned you would need to use the MongoDB Projection feature in one way or the other.
There's a few alternatives depending on your specific requirements that I can think of:
Option 1a (fairly static approach): Create a custom type with only the fields that you are interested in if you know them upfront. Something like this:
public class OnlyWhatWeAreInterestedIn
{
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// ...
}
Then you can query your Collection like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<OnlyWhatWeAreInterestedIn>("test");
var result = collection.Find(FilterDefinition<OnlyWhatWeAreInterestedIn>.Empty);
Using this approach you get a nicely typed result back without the need for custom projections.
Option 1b (still pretty static): A minor variation of Option 1a, just without a new explicit type but a projection stage instead to limit the returned fields. Kind of like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
var result = collection.Find(FilterDefinition<Test>.Empty).Project(t => new { t.PartData, t.SensorData }).ToList();
Again, you get a nicely typed C# entity back that you can continue to operate on.
Option 2: Use some dark reflection magic in order to dynamically create a projection stage. Downside: You won't get a typed instance reflecting your properties but instead a BsonDocument so you will have to deal with that afterwards. Also, if you have any custom MongoDB mappings in place, you would need to add some code to deal with them.
Here's the full example code:
First, your entities:
public class Test
{
string _id;
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// just to have one additional property that will not be part of the returned document
public string TestString { get; set; }
}
public class DataElement
{
}
And then the test program:
public class Program
{
static void Main(string[] args)
{
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
// insert test record
collection.InsertOne(
new Test
{
PartData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
SensorData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
TestString = "SomeString"
});
// here, we use reflection to find the relevant properties
var allPropertiesThatWeAreLookingFor = typeof(Test).GetProperties().Where(p => typeof(ObservableCollection<DataElement>).IsAssignableFrom(p.PropertyType));
// create a string of all properties that we are interested in with a ":1" appended so MongoDB will return these fields only
// in our example, this will look like
// "PartData:1,SensorData:1"
var mongoDbProjection = string.Join(",", allPropertiesThatWeAreLookingFor.Select(p => $"{p.Name}:1"));
// we do not want MongoDB to return the _id field because it's not of the selected type but would be returned by default otherwise
mongoDbProjection += ",_id:0";
var result = collection.Find(FilterDefinition<Test>.Empty).Project($"{{{mongoDbProjection}}}").ToList();
Console.ReadLine();
}
}
public Class SomeModel
{
public int Id { get; set;}
public string Name {get; set;}
}
this is the method I'm binding values to above model, it contains inline SQL Queries
Public ActionResult GetData()
{
IEnumarable<SomeModel> alltheListValues = ....
}
using above method, I want to filter all the record that containing ids in following string.
string filterIds = "12,13,14,15"
SomeModel model class Id values are in Integer type, but I have string type id set, without going for looping this, I decided to use WHERE IN query to filter this.
since WHERE IN query we can get in Linq as contains
I wrote down follwoing function to filter values
IEnumarable<SomeModel> filterValues = GetData.Where(Id=> Id.ToString().Contains(filterIds));
but this is not selecting any value always zero filter result, how can I wrote this properly
This could be a way to go:
string filterIds = "12,13,14,15";
//Convert filterIds string into a integers collection
var ids=filterIds.Split(',').Select(int.Parse);
IEnumarable<SomeModel> filterValues = GetData().Where(sm=> ids.Contains(sm.Id));
One thing I noticed now is the signature of your GetData method, I think what you mean is:
public IEnumarable<SomeModel> GetData()
{
IEnumarable<SomeModel> alltheListValues = ....
//...
return alltheListValues;
}
Have a array/collection instead of a list and check if the id is in it.
IEnumarable<SomeModel> filterValues = ActualGetData().Where(n => filterIds.Contains(n.Id))
Where ActualGetData() returns you an IEnumarable.
In your current code GetData() returns an ActionResult as Drew Kennedy mentioned
If you are sure that all the elements of collection of "Id" will be convertible to number then you can use following approach.
var models = new List<SomeModel>(); //Consider that this collection is coming from some service or repository.
var Ids = filterIds.Split(',').Select(x => int.Parse(x));
IEnumarable<SomeModel> filterValues = models.Where(n => Ids.Contains(n.Id));
Your code wound't compile so I am posting here just the logical code here. You can use it as per your requirement.
I'm new to MongoDB so this might be a naive question, yet I have not found any relevant/up to date information by googling around: I am trying to use the MongoDB C# driver (version 2.2.4) to compose a LINQ-based query, one piece at a time, from a received filter POCO object, like this:
IQueryable<BsonDocument> parts = collection.AsQueryable();
if (filter.Name != null)
parts = parts.Where(d => d["Name"].Equals(filter.Name));
// ... etc; I'll then call ToList() to get results ...
Now, one of my filter properties is a string array, meaning that I should match any document whose field Vendor (a string property in the MongoDB document) is equal to any of the strings in the array (like MongoDB native $in: https://docs.mongodb.com/manual/reference/operator/query/in/).
To this end, I tried with Contains (the special case for a 1-sized array is just an optimization):
if (filter.Vendors != null && filter.Vendors.Length > 0)
{
parts = filter.Vendors.Length == 1
? parts.Where(d => d["Vendor"].Equals(filter.Vendors[0]))
: parts.Where(d => filter.Vendors.Contains(d["Vendor"].AsString));
}
This compiles, but throws an ArgumentException: "Expression of type 'MongoDB.Bson.BsonValue' cannot be used for parameter of type 'System.String' of method 'Boolean Contains[String](System.Collections.Generic.IEnumerable`1[System.String], System.String)'".
Looking at http://mongodb.github.io/mongo-csharp-driver/2.2/reference/driver/crud/linq/, there is nothing about Contains or $in; yet, from https://jira.mongodb.org/browse/CSHARP-462 it seems that the driver should now be capable of handling that method.
BTW, the same happens if I slightly change the code to:
parts.Where(d => filter.Vendors.Any(s=> d["Vendor"].Equals(s)));
which does not involve Contains at all. The exception message complains about not being able to use BsonValue for string, yet that BsonValue is right a string. Could anyone suggest a solution?
The exception messages dance around the idea of fully embracing BsonValue to let mongo handle the types instead of trying to cast to string. I got it to work having Vendors as type List<BsonValue>.
class Filter
{
public List<BsonValue> Vendors { get; set; }
}
...
var list = parts.Where(d => filter.Vendors.Contains(d["Vendor"]));
foreach (var document in list)
{
Console.WriteLine(document["Name"]);
}
Another alternative is to map your documents to a C# class instead of using BsonDocument as the collection type.
class MyDocument
{
public ObjectId Id { get; set; }
public string Name { get; set; }
public string Vendor { get; set; }
}
...
var collection = db.GetCollection <MyDocument> ("parts");