I have two collections Product and Categories. A product can have multiple categories. Product is have a string array for keep category ids (as name Product.Categories).
I want to select products with category details.
Note: I'm using MongoDB .Net Driver. Can I do this with Linq query?
Products Collection:
`
{
_id : "product_1",
title : "Product Title 1",
categories : ["category_1", "category_2"]
},
{
_id : "product_2",
title : "Product Title 2",
categories : ["category_2"]
}
Categories Collection:
{
_id: "category_1",
name : "Category 1 Name",
},
{
_id: "category_2",
name : "Category 2 Name",
}
I want to result like below:
{
_id : "product_1",
title :"Product Title 1",
categories : [
{_id = "category_1", name="Category 1 Name"},
{_id = "category_2", name="Category 2 Name"},
]
},
{
_id : "product_2",
title :"Product Title 2",
categories : [
{_id = "category_2", name="Category 2 Name"},
]
}
It's basically a join. Which is a Lookup aggregate in C# side. I believe you want the following>
public class Category
{
public string _id { get; set; }
public string name { get; set; }
}
public class Product
{
public string _id { get; set; }
public string title { get; set; }
public string[] categories { get; set; }
}
public class AggregatedProduct
{
[BsonElement("_id")]
public string Id { get; set; }
[BsonElement("title")]
public string Title { get; set; }
[BsonElement("categories")]
public Category[] Categories { get; set; }
}
string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("test");
var products = db.GetCollection<Product>("Products");
var categories = db.GetCollection<Category>("Categories");
var resultOfJoin = products.Aggregate().Lookup(foreignCollection: categories, localField: x => x.categories,
foreignField: x => x._id, #as: (AggregatedProduct pr) => pr.Categories).ToList();
Related
I have a class with collection class inside
public class SearchResult {
public int Id { get; set; }
public int Total { get; set; }
public IEnumerable<Book> Books { get; set; }
}
public class Book {
public int BookId { get; set; }
public string BookName { get; set; }
public string Publisher { get; set; }
public string ISBNCode { get; set; }
public IList<catagory> Catagories { get; set; }
}
I have a question , if I create the other object , with same structure of SearchResult and I want to copy SearchResult to SearchResultClone, which inside Books only copy BookId and BookName remain is empty.
Just like below
{
"Id": 0,
"Total": 3,
"Books": [
{
"BookId": 1,
"BookName": "Book A",
"Publisher": "",
"ISBNCode": "",
"Catagories": []
},
{
"BookId": 2,
"BookName": "Book B",
"Publisher": "",
"ISBNCode": "",
"Catagories": []
},
{
"BookId": 3,
"BookName": "Book C",
"Publisher": "",
"ISBNCode": "",
"Catagories": []
}
]
}
Event the original result have value of Publisher, ISBNCode ..etc
How to do it in LINQ ?
My second question is , if I want to make a fluent assertions as above object
var result = await sut.search(query);
result.Should().BeEquivalentTo ({the SearchResultClone })
How to write this fluent assertion ?
You need to create new instances of the classes based on the old instances:
var ans = result.Select(sr => new SearchResult {
Id = sr.Id,
Total = sr.Total,
Books = sr.Books.Select(b => new Book { BookId = b.BookId, BookName = b.BookName }).ToList()
}).ToList();
result.Should().BeEquivalentTo ({the SearchResultClone })
How to write this fluent assertion ?
If your expectation (the object you pass into BeEquivalentTo) is of the type SearchResult, then FA will try to compare the empty values of ISBN to the same property on the sut. You can solve that by doing something like:
sut.Should().BeEquivalentTo(new
{
Id = "some value",
Total = 123,
Books = new[]
{
new
{
BookId = 123,
BookName = "some book"
}
}
});
I have a List of Incident object (List) with the following way:
Incident
-Title
-Category
-Name
-Subcategory
-Name
Im looking a Linq eficient way, to get the count of incidents related to each category and subcategories
expected obj result -
CategoryName
Count
Subcategories
SubcategoryName
Count
var categoryGroup = incidentsModel.GroupBy(i => i.Category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Category.Subcategories).GroupBy(item => item.Name).Select(item => new IncidentSubcategoriesCount
{
SubcategoryName = item.Key,
SubcategoryCount = item.Count()
});
return new IncidentCategoriesCount
{
CategoryName = group.Key,
catagoryCount = group.Count(),
Subcategories = subcategories
};
});
return categoryGroupAndSubcategoryGroup;
testing #hannan answer the code above return:
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 3
},
{
"subcategoryName": "Robo",
"subcategoryCount": 3
}
]
}
]
CategoryName and CategoryCount are OK, the error is in SubcategoriesCount, "Narcotrafico" SubcategoryCount must be 2 and "Robo" SubcategoryCount must be 1 so the Total is 3.
Expected result
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 2
},
{
"subcategoryName": "Robo",
"subcategoryCount": 1
}
]
}
]
Use Select Many then you can group any way you want
class Program
{
static void Main(string[] args)
{
List<Incident> indcidents = new List<Incident>();
var results = indcidents.SelectMany(x => x.catogories
.SelectMany(y => y.subcategories.Select(z =>
new { title = x.Title, CategoryName = y.Name, Subcategory = z.Name }
))).ToList();
}
}
public class Incident
{
public string Title { get; set; }
public List<Category> catogories { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> subcategories { get; set; }
}
public class Subcategory
{
public string Name { get; set; }
}
if u are looking for all categories and subcategories I'm afraid u have to do nested groupBy
public class Subcategory
{
public string Name { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> Categories { get; set; } = new List<Subcategory>()
}
public class Incident {
public List<Category> Categories { get; set; } = new List<Category>()
}
var categoryGroup = new Incident().Categories.GroupBy(category => category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Categories).GroupBy(item => item.Name).Select(item => new
{
Name = item.Key,
count = item.Count()
})
return
new
{
Name = group.Key,
Count = group.Count()
Subcategories = subcategories
}
})
I have an ASP.Net MVC5 site and using EF 6.0
One to Many relationship
Here are my models
public class Singer
{
[Key]
public int SingerID { get; set; }
public string SingerName { get; set; }
public virtual List<Album> Albums { get; set; }
}
public class Album
{
[Key]
public int AlbumID { get; set; }
public string AlbumName { get; set; }
public string AlbumDate { get; set; }
[ForeignKey("Singer")]
public int SingerID { get; set; }
public virtual Singer Singer { get; set; }
}
Now my Linq is as below
public IEnumerable<T> GetAlbums()
{
using (dbContext db = new dbContext())
{
IQueryable<T> query = (from c in db.Albums
group c.AlbumId by c.SingerId into albums
select new AlbumMapper()
{
AlbumID = albums.Key,
Total = albums.Count()
})
}
}
In the current scenario I get all the albums grouped by albumId and the count of the albums.
But my need is to form JSON string as below
[
{
"SingerID":1,
"Albums":[
{
"AlbumName":"This is Album 1",
"AlbumDate":"Dec 30,2015"
},
{
"AlbumName":"This is Album 2",
"AlbumDate":"Dec 30 2015"
}
]
},
{
"SingerID":2,
"Albums":[
{
"AlbumName":"This is Album 1",
"AlbumDate":"Dec 30,2015"
},
{
"AlbumName":"This is Album 2",
"AlbumDate":"Dec 30 2015"
}
]
}
]
Adding Mapper Classes
public class AlbumDetails
{
public DateTIme AlbumDate
public string AlbumName
}
public class AlbumMapper
{
public int AlbumID
public IEnumerable<AlbumDetails> Albums
}
Just put all the Singers into a list and serialize it using Json.NET (http://www.newtonsoft.com/json)
If you want to leave out SingerName be sure to add a [JsonIgnore] data attribute to the property
Then you want just this, combination of GroupBy with Select using anonymous object.
Using lambda
public IEnumerable<AlbumMapper> GetAlbums()
{
using(dbContext db = new dbContext())
{
return db.Albums.GroupBy(a => a.SingerID)
.Select(g => new AlbumMapper
{
SingerID = g.Key,
Albums = g.Select(a => new AlbumDetails { AlbumName = a.AlbumName, AlbumDate = a.AlbumDate })
});
}
}
You have to modify your map classes to fit this:
public class AlbumDetails
{
public DateTime AlbumDate { get; set; }
public string AlbumName { get; set; }
}
public class AlbumMapper
{
public int SingerID { get; set; }
public IEnumerable<AlbumDetails> Albums { get; set; }
}
Using linq syntax
public IEnumerable<T> GetAlbums()
{
using(dbContext db = new dbContext())
{
return from a in db.Albums
group a by a.SingerID into albums
select new AlbumMapper
{
SingerID = albums.Key,
Albums = albums.Select(album => new AlbumDetails { AlbumName = album.AlbumName, AlbumDate = album.AlbumDate })
};
}
}
With this sample data:
var albums = new List<Album>();
var singer = new Singer(1, "Singer 1");
var singer2 = new Singer(2, "Singer 2");
albums.Add(new Album(1,"This is Album 1", "Dec 30,2015", singer));
albums.Add(new Album(2,"This is Album 2", "Dec 30,2015", singer));
albums.Add(new Album(1,"This is Album 1", "Dec 30,2015", singer2));
albums.Add(new Album(2,"This is Album 2", "Dec 30,2015", singer2));
The result of this
albums.GroupBy(a => a.SingerID)
.Select(g => new
{
SingerID = g.Key,
Albums = g.Select(a => new { a.AlbumName, a.AlbumDate })
})
Is
[
{
"SingerID": 1,
"Albums": [
{
"AlbumName": "This is Album 1",
"AlbumDate": "Dec 30,2015"
},
{
"AlbumName": "This is Album 2",
"AlbumDate": "Dec 30,2015"
}
]
},
{
"SingerID": 2,
"Albums": [
{
"AlbumName": "This is Album 1",
"AlbumDate": "Dec 30,2015"
},
{
"AlbumName": "This is Album 2",
"AlbumDate": "Dec 30,2015"
}
]
}
]
I have two classes
public class MyObjects{
public bool Active {get; set;}
public List<OtherObject> OtherObjects {get; set;}
}
public class OtherObject {
public int Id {get; set;}
public bool Enabled {get; set;}
public string Address {get; set;}
public string Name {get; set;}
}
My current result is
MyObject { Active = true; },
OtherObjects: [OtherObject: { Id: 1, Name: 'First'},
OtherObject{Id: 2, Name: 'First'},
OtherObject{Id: 3, Name: 'Second'}];
I want to group them by Name so I would still have Active property and those OtherObjects inside would be grouped by OtherObject Name property. Is it possible to do so only using LINQ?
EDIT:
Final result should be json, that I will use in angular, so it should be something like this:
{
""Active"": true,
""OtherObjects"": [
{
""ObjectName"": ""Second"",
""ObjectOtherProperties"": [
{
""Id"": 1,
""Enabled"": false
},
{
""Id"": 2,
""Enabled"": true
}
],
""ObjectName"": ""Second"",
""ObjectOtherProperties"": [
{
""Id"": 1,
""Enabled"": false
}
],
]
}
}
Any suggestions how to achieve this? Maybe I must make other classes and somehow map them by grouping?
This is how I would do it, keeping it simple:
// 1. Add OtherObjectsDictionary
// 2. Block OtherObjects in the json serialization
public class MyObjects
{
public bool Active { get; set; }
[Newtonsoft.Json.JsonIgnore]
public List<OtherObject> OtherObjects { get; set; }
public Dictionary<string, List<OtherObject>> OtherObjectsDictionary { get; set; }
}
// 3. Block Name in the json serialization
public class OtherObject
{
public int Id { get; set; }
public bool Enabled { get; set; }
public string Address { get; set; }
[Newtonsoft.Json.JsonIgnore]
public string Name { get; set; }
}
// 4. Linq queries to achieve the grouped result
// 5. Serialize to Json
static void Main(string[] args)
{
var myObjects = new MyObjects() { Active = true, OtherObjects = new List<OtherObject>() };
myObjects.OtherObjects.Add(new OtherObject { Id = 1, Name = "First" });
myObjects.OtherObjects.Add(new OtherObject { Id = 2, Name = "First" });
myObjects.OtherObjects.Add(new OtherObject { Id = 3, Name = "Second" });
myObjects.OtherObjectsDictionary = new Dictionary<string, List<OtherObject>>();
var distinctNames = myObjects.OtherObjects.Select(otherObject => otherObject.Name).Distinct();
foreach(var distinctName in distinctNames)
{
var groupedObjectsList = myObjects.OtherObjects.Where(otherObject => otherObject.Name == distinctName).ToList();
myObjects.OtherObjectsDictionary.Add(distinctName, groupedObjectsList);
}
var outputJson = Newtonsoft.Json.JsonConvert.SerializeObject(myObjects);
}
This is the json result:
{
"Active": true,
"OtherObjectsDictionary": {
"First": [
{
"Id": 1,
"Enabled": false,
"Address": null
},
{
"Id": 2,
"Enabled": false,
"Address": null
}
],
"Second": [
{
"Id": 3,
"Enabled": false,
"Address": null
}
]
}
}
I hope it helps.
You may also use the System.Web.Extensions .dll as Add References for framework 4.0 projects (not 4.0 Client Profile).
Then add using inside your class.
I also applied a different approach, a more-or-less DB like normalization.
List of classes
public class MyObjects
{
public bool Active { get; set; }
public List<ObjectName> OtherObjects { get; set; }
}
public class ObjectName
{
public string Name { get; set; }
public List<OtherObject> OtherObjectProperties { get; set; }
}
public class OtherObject
{
public int Id { get; set; }
public bool Enabled { get; set; }
[ScriptIgnore]
public string Address { get; set; }
[ScriptIgnore]
public string Name { get; set; }
}
populate the records..
List<OtherObject> oList = new List<OtherObject>();
oList.Add(new OtherObject() { Id = 2, Name = "First" });
oList.Add(new OtherObject() { Id = 3, Name = "Second" });
// each name with objects
List<ObjectName> oNames = new List<ObjectName>();
oNames.AddRange(oList.Select(p => new ObjectName() {
Name = p.Name
, OtherObjectProperties = oList.Where(p1 => p1.Name == p.Name).ToList()
}).Distinct()
);
// parent object with with object names
MyObjects mo = new MyObjects() { Active = true, OtherObjects = oNames };
and finally, the javascript serializer..
JavaScriptSerializer jss = new JavaScriptSerializer();
string b = jss.Serialize(mo);
string b should give you the output like below..
{
"Active":true
,"OtherObjects":[
{
"Name":"First"
,"OtherObjectProperties":[
{
"Id":2
,"Enabled":false}
]},
{
"Name":"Second"
,"OtherObjectProperties":[
{
"Id":3
,"Enabled":false}
]
}]
}
Please advise if you're confused about any of the following.. :)
i have 2 class:
class Employee
{
string name;
string age;
}
class Departments
{
string branch;
Employee A;
}
Declare new list:
List<Departments> lstDp = new List<Departments>();
after get/set and Add Employee into the list ... i have a LIST of Departments include Employee info. And then:
string json = JsonConvert.SerializeObject(lstDp, Newtonsoft.Json.Formatting.Indented);
but the output JSON string only contain element "branch". What's wrong with this? I want the output like this:
[
{
"branch": "NY",
"Employee": {
"name": "John Smith",
"age": "29",
}
}
]
The problem can be that some of class members is private. Just tested:
class Employee
{
public string Name { get; set; }
public string Age { get; set; }
}
class Departments
{
public string Branch { get; set; }
public Employee Employee { get; set; }
}
And
var lstDp = new List<Departments> {
new Departments {
Branch = "NY",
Employee = new Employee { Age = "29", Name = "John Smith" }
}
};
var json = JsonConvert.SerializeObject(lstDp, Formatting.Indented);
Works fine.
The Departmentshould contain an IEnumerable<Employee> not just an Employee