I have to join the collections Location and LocationInfo using linq and getting the following exception
System.NotSupportedException: '$project or $group does not support {document}.'
and tried the following C# code
IMongoClient client = new MongoClient();
MongoUrl url = new MongoUrl("mongodb://localhost:27017/Database");
MongoClientSettings settings = MongoClientSettings.FromUrl(url);
client = new MongoClient(settings);
IMongoDatabase database = client.GetDatabase(url.DatabaseName);
var location = database.GetCollection<Location>("Location").AsQueryable();
var locationInfo = database.GetCollection<LocationInfo>("LocationInfo").AsQueryable();
var res = (from loc in locationInfo
from li in loc.ServiceLocation
join locs in location
on li.LocationId equals locs._id
select new LocationInfo
{
LocationName = loc.LocationName,
ServiceLocation = loc.ServiceLocation
});;
var ret = res.ToList();
Location
{
"_id" : "c1828bf1-1ea0-4c48-932f-9d8ba4a19003",
"LocationNumber" : 12345
}
LocationInfo
{
"_id" : "35cfd485-b1eb-4724-a07d-9b0885b6fb6c",
"LocationName" : "AA Country",
"IsActive" : true,
"ServiceLocation" : [
{
"LocationId" : "c1828bf1-1ea0-4c48-932f-9d8ba4a19003",
"Addresses" : [
{
"AddressId" : "9235bb19-cdbf-46a8-af96-cd519d081380",
"Line1" : "xx",
"Line2" : null,
"City" : "xx",
"State" : "OH",
"IsActive" : true
}
]
}
]}
Tell me how to achieve this operation using c# Linq.
you can do lookups/joins in nested properties as shown below. following code uses MongoDB.Entities for brevity but the query is exactly the same for collection.AsQueryable() interface of the official driver.
using MongoDB.Entities;
using MongoDB.Driver.Linq;
using System.Linq;
namespace StackOverflow
{
public class Location : Entity
{
public string LocationNumber { get; set; }
}
public class LocationInfo : Entity
{
public string LocationName { get; set; }
public ServiceLocation[] ServiceLocation { get; set; }
}
public class ServiceLocation
{
public string LocationId { get; set; }
public Address[] Addresses { get; set; }
}
public class Address
{
public string AddressId { get; set; }
}
public class Program
{
private static void Main(string[] args)
{
new DB("Location");
var res =
DB.Queryable<LocationInfo>()
.SelectMany(li => li.ServiceLocation, //unwind the service locations
(li, sl) => new { locName = li.LocationName, serLoc = sl }) //project needed data in to anonymous type
.Join(DB.Queryable<Location>(), //foregin collection
x => x.serLoc.LocationId, //local field in anonymous type from unwind above
l => l.ID, //foreign field
(x, l) => new { LocationName = x.locName, ServiceLocation = x.serLoc }) //project in to final anonymous type
.ToList();
}
}
}
Related
This is what my classes look like:
public class TestA : MongoDocument
{
public string ProjectID { get; set; }
public TestB Content { get; set; }
}
public class TestB
{
public string Id { get; set; }
public List<TestC> CustInfo { get; set; }
}
public class TestC
{
public string Id { get; set; }
public CustomerComment CustomerComment { get; set; }
}
public class CustomerComment
{
public string Id { get; set; }
public string notes { get; set; }
}
I would like to return only the CustomerComment based on TestA.ProjectID && TestC.Id
var projection = Builders<TestA>.Projection
.Include(x => x.Content.CustInfo);
var options = new FindOptions<TestA>
{
Projection = projection
};
var find = await Collection.FindAsync(p => p.ProjectID == "555" &&
p.Content.CustInfo.Any(l => l.Id == "123"), options).ConfigureAwait(false);
var result = await find.ToListAsync().ConfigureAwait(false);
This works but it will return everything. In this case, I would only want to return CustomerComments.
So for me to only retrieve the customerComments, I do another query based on the results from mongo. The code below gives me the correct data but I would rather do the filtering through the database.
var test = result.SelectMany(x => x.Content.CustInfo.Where(l => l.Id == "123").Select(y => y.CustomerComment)).ToList();
I think Aggregation Query meets your requirements for querying the data in the database and returning desired output.
$match - Filtering data.
$unwind - Deconstruct array fields to multiple documents.
$match - Filtering data for Content.CustInfo.
$replaceWith - Replace the current document with the new document for output.
db.collection.aggregate([
{
$match: {
"ProjectID": "555",
"Content.CustInfo.Id": "123"
}
},
{
$unwind: "$Content.CustInfo"
},
{
$match: {
"Content.CustInfo.Id": {
$eq: "123"
}
}
},
{
"$replaceWith": "$Content.CustInfo.CustomerComment"
}
])
Sample Mongo Playground
Solution 1: With AggregateFluent
Pre-requisite: Create unwind classes.
public class UnwindTestA
{
public ObjectId _id { get; set; }
public string ProjectID { get; set; }
public UnwindTestB Content { get; set; }
}
public class UnwindTestB
{
public string Id { get; set; }
public TestC CustInfo { get; set; }
}
var result = await Collection.Aggregate()
.Match(
p => p.ProjectID == "555"
&& p.Content.CustInfo.Any(l => l.Id == "123")
)
.Unwind<TestA, UnwindTestA>(x => x.Content.CustInfo)
.Match<UnwindTestA>(x => x.Content.CustInfo.Id == "123")
.ReplaceWith<CustomerComment>("$Content.CustInfo.CustomerComment")
.ToListAsync();
Solution 2: With BsonDocument
Sometimes, writing a query with AggregateFluent is quite complex. You can use the MongoDB query which is converted to BsonDocument.
BsonDocument[] aggregate = new BsonDocument[]
{
new BsonDocument("$match",
new BsonDocument
{
{ "ProjectID", "555" },
{ "Content.CustInfo.Id", "123" }
}),
new BsonDocument("$unwind", "$Content.CustInfo"),
new BsonDocument("$match",
new BsonDocument("Content.CustInfo.Id",
new BsonDocument("$eq", "123"))),
new BsonDocument("$replaceWith", "$Content.CustInfo.CustomerComment")
};
var result = await Collection.Aggregate<CustomerComment>(aggregate)
.ToListAsync();
Output
I have a structure like:
Client
ClientId
Name
Address
...etc...
Asset[]
AssetId
name
Enabled
I want to get all Asset at ounce as a Asset object and not get all Client and then filter to get the Asset (because mu client object has 50 properties... some big arrays).
I endd up with:
var filter = Builders<Client>.Filter.Eq("Id", clientId);
var u = _coll.Find(filter)
.ToList()
.Where(w=> w.Id == clientId)
.SelectMany(ss=> ss.Asset);
This is doing what I dont want to do, I am getting the full client object and then filtering... I tried all things like unwind, Project, etc... couldn't make any to work.
How can I get the Asset in the fastest way and clean way possible. I would like only to fetch the data I need, so brind the client is not a option..
thanks.
this is quite simple with MongoDAL, which is a convenient wrapper for the c# driver. see the code below for my approach.
using System;
using System.Linq;
using MongoDAL;
namespace Example
{
class Client : Entity
{
public string Name { get; set; }
public Asset[] Assets { get; set; }
}
class Asset
{
public string Name { get; set; }
public bool Enabled { get; set; }
}
class Program
{
static void Main(string[] args)
{
new DB("assets");
var client = new Client
{
Name = "Marco Polo",
Assets = new Asset[]
{
new Asset{ Name = "asset one", Enabled = true},
new Asset{ Name = "asset two", Enabled = true},
new Asset{ Name = "asset three", Enabled = true}
}
};
client.Save();
var clientID = client.ID;
var result = client.Collection()
.Where(c => c.ID == clientID)
.SelectMany(c => c.Assets)
.ToArray();
Console.ReadKey();
}
}
}
generated mongo query:
aggregate([{
"$match" : { "_id" : ObjectId("5cc0643744effe2fa482648e") } },
{ "$unwind" : "$Assets" },
{ "$project" :
{ "Assets" : "$Assets", "_id" : 0 } }])
First of all, I am using NEST 5.5.0.
I have the following use of a remote elasticsearch-index:
var node = new Uri("http://distribution.virk.dk/cvr-permanent");
var settings = new
ConnectionSettings(node).DefaultIndex("virksomhed");
settings.BasicAuthentication("username", "password");
var client = new ElasticClient(settings);
var searchResponse = client.Search<Company>(s => s
.AllTypes().Query(q => q
.Match(m => m
.Field(f => f.cvrNumber)
.Query("35954716")
)
)
);
The mappings in the index (without a bunch of other properties besides cvrNummer) are as follows:
{
"cvr-permanent-prod-20170205" : {
"mappings" : {
"virksomhed" : {
"_size" : {
"enabled" : true
},
"properties" : {
"Vrvirksomhed" : {
"properties" : {
"type" : "long"
},
"cvrNummer" : {
"type" : "string"
},
}
}
},
}
}
}
}
}
I also have the following class which the result is supposed to be mapped to:
[ElasticsearchType(Name = "virksomhed")]
public class Company
{
[Text(Name = "Vrvirksomhed.cvrNummer")]
public string cvrNumber { get; set; }
}
Now, the search (searchResponse) holds the expected results (1 result), where the part concerning cvrNummer looks as follows:
"hits": {
"total": 1,
"max_score": 17.34601,
"hits": [
{
"_index": "cvr-permanent-prod-20170205",
"_type": "virksomhed",
"_id": "4000333383",
"_score": 17.34601,
"_source": {
"Vrvirksomhed": {
"cvrNummer": 35954716,
"regNummer": [
{
"regnummer": "A/S35855",
"periode": {
"gyldigFra": "1956-06-01",
"gyldigTil": "1999-10-18"
},
"sidstOpdateret": "2015-02-10T00:00:00.000+01:00"
}
],
"brancheAnsvarskode": null,
"reklamebeskyttet": false,
"navne": [
...
However, when i look in searchResponse.Documents, I have the correct type (Company), but the value of cvrNumber is null.
Any ideas what I'm doing wrong, since the value of cvrNummer is not mapped into cvrNumber on the instance of Company in searchResponse.Documents?
Thanks in advance for your input!
UPDATE
I tried the following without success, still got the expected result, but cvrNumber is still null (in searchResponse.Documents):
[ElasticsearchType(Name = "virksomhed")]
public class Company
{
[Object(Name = "Vrvirksomhed")]
public Vrvirksomhed Vrvirksomhed { get; set; }
}
public class Vrvirksomhed
{
[Text(Name = "cvrNummer")]
public string cvrNumber { get; set; }
}
With the query:
var searchResponse = client.Search<Vrvirksomhed>(s => s
.AllTypes().Query(q => q
.Match(m => m
.Field(f => f.cvrNumber)
.Query("35954716")
)
)
);
UPDATE
It works with the following modifications to the query:
var searchResponse = client.Search<Company>(s => s
.AllTypes().Query(q => q
.Match(m => m
.Field(f => f.Vrvirksomhed.cvrNumber)
.Query("35954716")
)
)
);
[ElasticsearchType(Name = "virksomhed")]
public class Company
{
[Text(Name = "Vrvirksomhed.cvrNummer")]
public string cvrNumber { get; set; }
}
Vrvirksomhed looks like it should be a POCO property on Company mapped either as an object datatype or nested datatype (take a look at nested objects in the Definitive Guide for the differences), where that POCO has a property called cvrNumber, similar to
[ElasticsearchType(Name = "virksomhed")]
public class Company
{
[Object(Name = "Vrvirksomhed")]
public Vrvirksomhed Vrvirksomhed { get; set; }
}
public class Vrvirksomhed
{
[Text(Name = "cvrNummer")]
public string cvrNumber { get; set; }
}
I am trying to map Student with StudentDto, this is what I am doing but it is complaining about the nested property which is of type List<StudentContact>
Both the objects, StudentDto and Student have exactly the same properties, this is what i am using to try to map the objects.
var config = new MapperConfiguration(
cfg => cfg.CreateMap<StudentDto, Student>());
var mapper = config.CreateMapper();
var driverActivationResponse = mapper.Map <List<Student> > (studentDto);// "studentDto" is List<StudentDto>
my classes
public class StudentDto
{
public StudentDto()
{
if(StudentContacts==null) StudentContacts=new List<StudentContact>();
}
public string Id { get; set; }
public List<StudentContact> StudentContacts { get; set; }
}
public class Student
{
public Student()
{
if(StudentContacts==null) StudentContacts=new List<StudentContact>();
}
public string Id { get; set; }
public List<StudentContact> StudentContacts { get; set; }
}
public class StudentContact
{
public string ContactName { get; set; }
public string PrimaryContactNo { get; set; }
}
This should help -
AutoMapper.Mapper.CreateMap<Student, StudentDto>()
.ForMember(a => a.StudentContacts, b => b.ResolveUsing(c => c.StudentContacts));
var map = Mapper.Map<StudentDto>(new Student
{
Id = "100",
StudentContacts = new List<StudentContact>
{
new StudentContact{ContactName = "test",PrimaryContactNo = "tset"}
}
});
you cannot map like mapper.Map <List<Student>>(studentDto);. The top level member cannot be a list when using automapper.
Does it help to specify the source collection type and destination collection type in your Map call?
var driverActivationResponse = mapper.Map<List<Student>, List<StudentDto>>(studentDto);
It looks like the AutoMapper code you have is correct. If you're still getting an error, something else must be wrong. Perhaps your studentDto is not really a List<StudentDto>?
In any case, here's an entire example that works without error:
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
namespace ConsoleSandbox
{
class Program
{
public static void Main()
{
var config = new MapperConfiguration(
cfg => cfg.CreateMap<StudentDto, Student>());
var mapper = config.CreateMapper();
var studentDtos = new[]
{
new StudentDto
{
Id = "1",
StudentContacts = new[]
{
new StudentContact { ContactName = "Dan", PrimaryContactNo = "123" },
new StudentContact { ContactName = "Stan", PrimaryContactNo = "456" },
}.ToList()
},
new StudentDto
{
Id = "2",
StudentContacts = new[]
{
new StudentContact { ContactName = "Foo", PrimaryContactNo = "789" },
new StudentContact { ContactName = "Bar", PrimaryContactNo = "101112" },
}.ToList()
},
}.ToList();
var driverActivationResponse = mapper.Map<List<Student>>(studentDtos);
Console.WriteLine($"Contacts Count: {driverActivationResponse.Count}");
Console.ReadKey();
}
}
public class StudentDto
{
public string Id { get; set; }
public List<StudentContact> StudentContacts { get; set; }
public StudentDto()
{
if (StudentContacts == null) StudentContacts = new List<StudentContact>();
}
}
public class Student
{
public string Id { get; set; }
public List<StudentContact> StudentContacts { get; set; }
public Student()
{
if (StudentContacts == null) StudentContacts = new List<StudentContact>();
}
}
public class StudentContact
{
public string ContactName { get; set; }
public string PrimaryContactNo { get; set; }
}
}
This is my data :
{
"-JxsJFiGBqQz1KQmmR0i" : {
"bizcardData" : {
"company" : "Tesla",
"designation" : "Developer",
"email" : "phani#tesla.com",
"name" : "Phani",
"phone" : "5135921241"
},
"transData" : {
"date" : "15-08-29",
"location" : "39.1395996,-84.5295417",
"tag" : "sender",
"time" : "03:17:00"
}
},
"-JxsJKnJIVTFQWE1aSOr" : {
"bizcardData" : {
"company" : "Spotify",
"designation" : "Designer",
"email" : "komarapa#spotify.com",
"name" : "Phani Komaravolu",
"phone" : "5135921241"
},
"transData" : {
"date" : "15-08-29",
"location" : "39.1395996,-84.5295417",
"tag" : "sender",
"time" : "03:17:21"
}
}
}
This is my Transactions class :
public class Transactions
{
public BizcardData bizcardData { get; set; }
public TransData transData { get; set; }
}
public class BizcardData
{
public string company { get; set; }
public string designation { get; set; }
public string email { get; set; }
public string name { get; set; }
public string phone { get; set; }
}
public class TransData
{
public string date { get; set; }
public string location { get; set; }
public string tag { get; set; }
public string time { get; set; }
}
That I deserialized using RestSharp :
var resultList = SimpleJson.DeserializeObject<Dictionary<string, Transactions>>(content);
Console.WriteLine ("Deserialized resultList"+resultList);
foreach(var item in resultList)
{
var key = item.Key;
var value = item.Value;
/* foreach(Transactions go in item.Value)
{
var bizcardData = go.bizcardData;
var transData = go.transData;
}*/
}
This code gives me the Key as the unique values and the value as Transaction.
If I try to iterate over the Transaction class, I am getting an error saying that, must implement IEnumerable. How can I iterated over the transaction class and get the values.
Thanks!
If you don't care about the keys you can just iterate over the dictionary's values (Transactions).
var resultList = SimpleJson.DeserializeObject<Dictionary<string, Transactions>>(content);
Console.WriteLine ("Deserialized resultList"+resultList);
foreach(var transaction in resultList.Values)
{
var bizcardData = transaction.bizcardData;
var transData = transaction.transData;
}
If you do care about the keys, just get rid of your second loop.
var resultList = SimpleJson.DeserializeObject<Dictionary<string, Transactions>>(content);
Console.WriteLine ("Deserialized resultList"+resultList);
foreach(var pair in resultList)
{
var key = pair.Key;
var transaction = pair.Value;
var bizcardData = transaction.bizcardData;
var transData = transaction.transData;
}
It's not a list of two classes. What you've got is an Object (Value) of type Transactions which contains two properties:
item.Value.bizcardData
item.Value.transData