Project on multiple fields in C# MongoDB 2.0 - c#

How do you project on fields in the new MongoDB C# drivers when the fields are given in the form of a String array ?.
I could find ways to project on a single field by doing
collection.find(filter).Project(Builders<Category>.Projection.Include(fieldName)
How do I extend this to take an array of fields ?.

There is also extension method Include
var projection = Builders<Category>.Projection.Include(fieldList.First());
foreach (var field in fieldList.Skip(1))
{
projection = projection.Include(field);
}
var result = await collection.Find(filter).Project(projection).ToListAsync();

another way, assuming fieldList is a string enumerable is:
var project = Builders<BsonDocument>.Projection.Combine(fieldList.Select(x => Builders<BsonDocument>.Projection.Include(x)).ToList());

A better way than mofenko's, you don't have to include the first column :
ProjectionDefinition<BsonDocument> project = null;
foreach (string columnName in columnsToInclude)
{
if (project == null)
{
project = Builders<BsonDocument>.Projection.Include(columnName);
}
else
{
project = project.Include(columnName);
}
}
This is for loosely typed data. If you're using classes replace BsonDocument with your class

Related

CSV to Cascading Model using Entity Framework

I need to turn an automatic CSV file into multiple database columns using Entity Framework. It is set up so that each model has children. So that Animal contains a list of Types which contain a list of Classification. In this way Classification is a grandchild of Animal
Right now I have these three models that need to be filled by the CSV file. The file is formatted in the following way:
They are then pulled from the API into a Windows Desktop App as a cascading dropdown box. So far I've tried adding them to separate lists however that did not upload when using Entity Framework. The current way is to try to cascade down the list however I get an error
Sequence contains no events
Here is the portion of the code that I am having a problem with (had to edit due to work rules so classes are different):
var Animal = new List<AnimalModel>();
var lines = await ReadStreamAsync(new StreamReader(uploadModel.File.OpenReadStream()));
foreach(string l in lines)
{
Animal.Add(new AnimalModel
{
AnimalName = cells[0],
});
Animal.Last().Type.Add(new TypeModel
{
TypeName = cells[1],
});
Animal.Last().Type.Last().Classification.Add(new ClassificationModel
{
Type = Type.Last(),
ClassificationName = cells[2],
Color = cells[3],
Age = cells[4]
});
}
I resolved the issue. I needed to initialize the list within the code as I am not doing so within the model. The following worked:
var Animal = new List<AnimalModel>();
var lines = await ReadStreamAsync(new StreamReader(uploadModel.File.OpenReadStream()));
foreach(string l in lines)
{
Animal.Add(new AnimalModel
{
AnimalName = cells[0],
Type = new List<TypeModel>()
{
new TypeModel()
{
TypeName = cells[1]
}
}
});
And so on for the grandchild. I will have to clean this up as it is quite messy but this works for now.

Couchbase Lite 2 + JsonConvert

The following code sample writes a simple object to a couchbase lite (version 2) database and reads all objects afterwards. This is what you can find in the official documentation here
This is quite a lot of manual typing since every property of every object must be transferred to the MutableObject.
class Program
{
static void Main(string[] args)
{
Couchbase.Lite.Support.NetDesktop.Activate();
const string DbName = "MyDb";
var db = new Database(DbName);
var item = new Item { Name = "test", Value = 5 };
// Serialization HERE
var doc = new MutableDocument();
doc.SetString("Name", item.Name);
doc.SetInt("Value", item.Value);
db.Save(doc);
using (var qry = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(db)))
{
foreach (var result in qry.Execute())
{
var resultItem = new Item
{
// Deserialization HERE
Name = result[DbName].Dictionary.GetString("Name"),
Value = result[DbName].Dictionary.GetInt("Value")
};
Console.WriteLine(resultItem.Name);
}
}
Console.ReadKey();
}
class Item
{
public string Name { get; set; }
public int Value { get; set; }
}
}
From my research Couchbase lite uses JsonConvert internally, so there might be a way to simplify all that with the help of JsonConvert.
Anything like:
var json = JsonConvert.SerializeObject(item);
var doc = new MutableDocument(json); // No overload to provide raw JSON
or maybe
var data = JsonConvert.SerializeToDict(item); // JsonConvert does not provide this
var doc = new MutableDocument(data);
Is there or is this some kind of optimization and the preferred approach is by intend?
People ask about this quite often, but Couchbase Lite does not actually store JSON strings in the database. They are stored in a different format so this would not give the benefit that you think (the JSON would need to be reparsed and then broken down into the other format). I'd been pushing for a way to serialize classes directly instead of going through dictionary objects (which seems like the ultimate goal here) but our priority is on things that enterprise clients want and this doesn't seem to be one of them. Note that for it to make it in, it needs to be implemented in C# Java and Objective-C / Swift.
I don't know about JsonConvert but there seems to be a constructor that takes IDictionary<string, object> as argument. So I would try something like this (brain-compiled):
MutableDocument CreateDocument(object data)
{
if (data == null) return null;
var propertyValues = new Dictionary<string, object>();
foreach (var property in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
propertyValues[property.Name] = property.GetValue(data);
}
return new MutableDocument(propertyValues);
}
See if this works.

Unable to save lists to MongoDB using UpdateDefinitionBuilder in C#

I have the following helper function that uses reflection to look at the supplied model, check if any fields are non-null, and overwrite those with new values in MongoDB.
It works fine until I try updating a List<string> field. Instead of saving the list as an array to MongoDB, it saves a string value of "(Collection)" in MongoDB. What am I missing to make this work?
I don't want to hardcode that any List should default to List<string> either. It's possible I could have lists of ints.
public override MyObj Update(MyObj model)
{
var builder = Builders<MyObj>.Update;
var builder_def = builder.Set(x => x.Id, model.Id);
foreach (PropertyInfo prop in model.GetType().GetProperties())
{
var value = model.GetType().GetProperty(prop.Name).GetValue(model, null);
if (value != null)
{
builder_def = builder_def.Set(prop.Name, value); // Not setting lists correctly
}
}
var filter = Builders<MyObj>.Filter;
var filter_def = filter.Eq(x => x.Id, model.Id);
Connection.Update(filter_def, builder_def);
return model;
}
Edit
I think my problem is related to this open bug https://jira.mongodb.org/browse/CSHARP-1984. Given that, is there any way for me to make this work in that context?

System.Linq.Dynamic.Core : How do I make a select many correctly?

I am new to the System.Linq.Dynamic.Core. I have this:
Let's say we have:
Packs = new List<Pack>
{
new Pack()
{
IdAtSource="Pack1",
Equipments= new List<Equipment>()
{
new Equipment
{
Id=1,
GenericEquipment = new GenericEquipment()
{
Id=7
}
}
}
},
new Pack()
{
IdAtSource="Pack2",
Equipments= new List<Equipment>()
{
new Equipment
{
Id=2,
GenericEquipment = new GenericEquipment()
{
Id=1
}
},
new Equipment
{
Id=2,
GenericEquipment = new GenericEquipment()
{
Id=2
}
}
}
}
}
I would like to select the Packs with Equipments, but in the selected Equipments I need to have only the one with Id=2 for Generic Equipment.(the result should contain a list of packs with list of equipments).
I've tried this:
querable.Where("Packs.Equipments.Select((GenericEquipment.Id)=1)");
but I feel I am waaay of target here. Also is there any documentation page on how to use this library?
Thanks a lot
The most recent version of Dynamic LINQ appears to be the project here, with the documentation here.
Generally, the only reason why you should require Dynamic LINQ over standard LINQ is when the types are not known at compile time.
If you know at compile time that the query will be against a List<Pack>, you can use stanadard LINQ, as in the following code (note that this modifies the original instances of Pack):
var usefulPacks = Packs.Select(pack => {
pack.Equipments = pack.Equipments.Where(equipment =>
equipment.GenericEquipment.Id == 1
).ToList();
}).Where(pack => pack.Equipments.Any()).ToList();
If you need code that doesn't modify the original instances, this code creates copies of the original instances:
var usefulPacks = Packs.Select(pack => {
return new Pack() {
IDAtSource = pack.IDAtSource,
Equipments = pack.Equipments.Where(equipment =>
equipment.GenericEquipment.Id == 1
).ToList();
};
}).Where(pack => pack.Equipments.Any()).ToList();
Note that this isn't using .SelectMany — .SelectMany is used to create a single enumerable from nested enumerables; here each Pack in the final list corresponds to a Pack in the original list.
Dynamic LINQ doesn't support modifying or initializing properties as part of the expression:
The expression language permits getting (but not setting) the value of any reachable public field, property, or indexer.
so the Equipments property cannot be changed / initialized to include only instances of Equipment that match the criteria.
In order to set the Equipments, you have two choices:
Add a constructor to Pack which takes the appropriate arguments
Write a static method on any class which takes the appropriate arguments
Add a constructor to Pack
You can add a constructor to Pack with the appropriate arguments, that sets Equipments:
Pack(int IDAtSource, IEnumerable<Equipment> equipments) {
this.IDAtSource = IDAtSource;
this.Equipments = equipments.ToList();
}
Then you could use the following:
IQueryable qry = Packs.AsQueryable();
qry = qry
.Select("Pack(IDAtSource, Equipments.Where(GenericEquipment.ID=1))")
.Where("Equipments.Any");
Define a static method
public static class MyPackMethods {
public static Pack Create(int IDAtSource, IEnumerable<Equipment> equipments) {
return new Pack() {
IDAtSource = IDAtSource,
Equipments = equipments.ToList()
};
}
}
and call:
IQueryable qry = Packs.AsQueryable();
qry = qry
.Select("MyPackMethods.Create(IDAtSource, Equipments.Where(GenericEquipment.ID=1))")
.Where("Equipments.Any");
These should work:
var equipments= from pack in Packs where pack.Equipments.Any() select pack.Equipments;
var secondEquipments = from pac in equipments where
pac.GenericEquipment.Id == 2 select pac;
//I could use one variable instead of 2 but that would look a little bit complex
Msdn Link(Sorry I'm at mobile so I can't rename it):
https://msdn.microsoft.com/en-us/library/bb397927.aspx

C# Linq to CSV Dynamic Object runtime column name

I'm new to using Dynamic Objects in C#. I am reading a CSV file very similarly to the code found here: http://my.safaribooksonline.com/book/programming/csharp/9780321637208/csharp-4dot0-features/ch08lev1sec3
I can reference the data I need with a static name, however I can not find the correct syntax to reference using a dynamic name at run time.
For example I have:
var records = from r in myDynamicClass.Records select r;
foreach(dynamic rec in records)
{
Console.WriteLine(rec.SomeColumn);
}
And this works fine if you know the "SomeColumn" name. I would prefer to have a column name a a string and be able to make the same type refrence at run time.
Since one has to create the class which inherits from DynamicObject, simply add an indexer to the class to achieve one's result via strings.
The following example uses the same properties found in the book example, the properties which holds the individual line data that has the column names. Below is the indexer on that class to achieve the result:
public class myDynamicClassDataLine : System.Dynamic.DynamicObject
{
string[] _lineContent; // Actual line data
List<string> _headers; // Associated headers (properties)
public string this[string indexer]
{
get
{
string result = string.Empty;
int index = _headers.IndexOf(indexer);
if (index >= 0 && index < _lineContent.Length)
result = _lineContent[index];
return result;
}
}
}
Then access the data such as
var csv =
#",,SomeColumn,,,
ab,cd,ef,,,"; // Ef is the "SomeColumn"
var data = new myDynamicClass(csv); // This holds multiple myDynamicClassDataLine items
Console.WriteLine (data.OfType<dynamic>().First()["SomeColumn"]); // "ef" is the output.
You will need to use reflection. To get the names you would use:
List<string> columnNames = new List<string>(records.GetType().GetProperties().Select(i => i.Name));
You can then loop through your results and output the values for each column like so:
foreach(dynamic rec in records)
{
foreach (string prop in columnNames)
Console.Write(rec.GetType().GetProperty (prop).GetValue (rec, null));
}
Try this
string column = "SomeColumn";
var result = rec.GetType().GetProperty (column).GetValue (rec, null);

Categories