How to save sub-document/sub-collection with C# MongoDb.Driver - c#

I'm using MongoDb.Driver in C# to save information to my backend.
I've created a simple Repository to store a generic object:
public abstract class MongoRepository<TDocument> : IRepository<TDocument>
where TDocument : Entity
{
#region Private Fields
private readonly IMongoCollection<TDocument> _collection;
#endregion
#region Protected Properties
protected abstract String CollectionName { get; }
#endregion
#region Constructors
public MongoRepository(IMongoDbSettings settings)
{
IMongoDatabase? database = new MongoClient(settings.ConnectionString).GetDatabase("default");
_collection = database.GetCollection<TDocument>(CollectionName);
}
#endregion
#region Public Methods
public IQueryable<TDocument> AsQueryable()
{
return _collection.AsQueryable();
}
public IEnumerable<TDocument> FilterBy(
Expression<Func<TDocument, bool>> filterExpression)
{
return _collection.Find(filterExpression).ToEnumerable();
}
public IEnumerable<TProjected> FilterBy<TProjected>(
Expression<Func<TDocument, bool>> filterExpression,
Expression<Func<TDocument, TProjected>> projectionExpression)
{
return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
}
public async Task<TDocument> FindOne(Expression<Func<TDocument, bool>> filterExpression)
{
return await _collection.Find(filterExpression).FirstOrDefaultAsync();
}
public async Task<IEnumerable<TDocument>> Get()
{
IAsyncCursor<TDocument>? asyncCursor = await _collection.FindAsync(_ => true);
return await asyncCursor.ToListAsync();
}
public async Task<TDocument> Get(Guid id)
{
FilterDefinition<TDocument>? filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
return await _collection.Find(filter).SingleOrDefaultAsync();
}
public async Task Create(TDocument document)
{
await _collection.InsertOneAsync(document);
}
public async Task CreateMany(ICollection<TDocument> documents)
{
await _collection.InsertManyAsync(documents);
}
public async Task Update(TDocument document)
{
FilterDefinition<TDocument>? filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, document.Id);
await _collection.FindOneAndReplaceAsync(filter, document);
}
public async Task Delete(Expression<Func<TDocument, bool>> filterExpression)
{
await _collection.DeleteManyAsync(filterExpression);
}
public async Task Delete(Guid id)
{
FilterDefinition<TDocument>? filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
await _collection.FindOneAndDeleteAsync(filter);
}
public async Task Delete(TDocument element)
{
await Delete(element.Id);
}
#endregion
}
Up until now it was working fine, but I first encounter the case of saving an object that has sub-objects and collections:
public class Dashboard : Entity//Entity has just a public Guid Id {get;set;}
{
public Guid OrganizationLevelId { get; set; }
public string Name { get; set; }
public bool IsShared { get; set; }
public Guid? OwnerId { get; set; }
public List<Widget> Widgets { get; } = new();
}
And now, when I'm trying to save a document that has a collection, the array of widgets isn't saved:
await _dashboardsRepository.Create(new Dashboard()
{
Name = "Default Dashboard",
IsShared = true,
Id = Guid.NewGuid(),
OrganizationLevelId = rootLevel.Id,
Widgets =
{
new Widget() {Columns = 2, Rows = 1, Y = 0, X = 0, Title = "Component 1"},
new Widget() {Columns = 1, Rows = 1, Y = 1, X = 0, Title = "Comp 2"},
new Widget() {Columns = 1, Rows = 1, Y = 1, X = 1, Title = "Comp 3"},
new Widget() {Columns = 2, Rows = 2, Y = 0, X = 1, Title = "Max 2x2", MaxItemsColumns = 2, MaxItemsRows = 2},
new Widget() {Columns = 2, Rows = 3, Y = 0, X = 4, Title = "Min 2x2", MinItemColumns = 2, MinItemsRows = 2},
new Widget() {Columns = 4, Rows = 2, Y = 2, X = 0, Title = "Component 6"},
new Widget() {Columns = 2, Rows = 2, Y = 3, X = 4, Title = "Component 7"}
}
}
)
I'm trying to keep some kind of separation of concern, so my business objects doesn't reference MongoDb.
I guess there is something to indicate that I want the child properties to be persisted but I can't find how, any idea?

I've resolved the issue. In fact there was no additional "serialization" required, but I did 2 changes:
My "Widgets" property was a
public List<Widget> Widgets{get;} = new List<Widget>()
After I transformed it to
public List<Widget> Widgets{get;set;}(and initialize the collection when creating a dashboard)
it works.
I'm not sure if it's because of the missing setter or the default initialization of the collection, but it works now.

Related

What is wrong with this Moq unit test that is supposed to (fake) retrieve a list?

I am trying to test this method that is meant to retrieve data out of a database and store them into a List<>. I have made this fake list that is meant to be found by the method. However, instead the method retrieves the actual database and returns the actual amount. What am I missing?
public class GetAllPhonesTests
{
private readonly Mock<IRepository<Phone>> _mockPhoneRepository;
private readonly IPhoneService _phoneService;
private readonly List<Phone> _fakePhones = new()
{
new Phone() { Id = 1, Brand = "Herp", Type = "Police" },
new Phone() { Id = 2, Brand = "Derp", Type = "Fireman" },
new Phone() { Id = 3, Brand = "Zerp", Type = "Nurse" },
new Phone() { Id = 4, Brand = "Flurp", Type = "Doctor" },
new Phone() { Id = 5, Brand = "Terp", Type = "Teacher" }
};
public GetAllPhonesTests()
{
_mockPhoneRepository = new Mock<IRepository<Phone>>();
_phoneService = new PhoneService(_mockPhoneRepository.Object);
}
[Fact]
public void Should_ReturnFullList_When_Called()
{
//Arrange
_mockPhoneRepository.Setup(x => x.GetRecords(It.IsAny<SqlCommand>())).Returns(_fakePhones);
//Act
List<Phone> actual = _phoneService.GetAllPhones();
//Assert
actual.Count.Should().Be(_fakePhones.Count);
}
}
additional from the service:
public List<Phone> GetAllPhones()
{
string query = "SELECT * FROM phones INNER JOIN Brands ON Brands.BrandID=phones.BrandId;";
using (var command = new SqlCommand(query))
{
return (List<Phone>)GetRecords(command);
}
}
additional from the repo:
public IEnumerable<T> GetRecords(SqlCommand command)
{
SqlDataReader reader = null;
List<T> list = new();
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
list.Add(PopulateRecord(reader));
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
}
}
Status(false, "");
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
reader.Close();
_connection.Close();
}
return list;
}
I think the issue might be in the order of your setup, as it should be done before you add the repository object to the mock service. Can you try to change it to this:
public class GetAllPhonesTests
{
private readonly Mock<IRepository<Phone>> _mockPhoneRepository;
private readonly IPhoneService _phoneService;
private readonly List<Phone> _fakePhones = new()
{
new Phone() { Id = 1, Brand = "Herp", Type = "Police" },
new Phone() { Id = 2, Brand = "Derp", Type = "Fireman" },
new Phone() { Id = 3, Brand = "Zerp", Type = "Nurse" },
new Phone() { Id = 4, Brand = "Flurp", Type = "Doctor" },
new Phone() { Id = 5, Brand = "Terp", Type = "Teacher" }
};
public GetAllPhonesTests()
{
_mockPhoneRepository = new Mock<IRepository<Phone>>();
_mockPhoneRepository.Setup(x => x.GetRecords(It.IsAny<SqlCommand>
())).Returns(_fakePhones);
_phoneService = new PhoneService(_mockPhoneRepository.Object);
}
[Fact]
public void Should_ReturnFullList_When_Called()
{
//Act
List<Phone> actual = _phoneService.GetAllPhones();
//Assert
actual.Count.Should().Be(_fakePhones.Count);
}
}
Your service should also be using DI to get the repository implementation used, for example:
private readonly IRepository<Phone> _phoneRepo;
public PhoneService(IRepository<Phone> phoneRepo){
_phoneRepo = phoneRepo;
}
public List<Phone> GetAllPhones()
{
string query = "SELECT * FROM phones INNER JOIN Brands ON Brands.BrandID=phones.BrandId;";
using (var command = new SqlCommand(query))
{
return (List<Phone>)_phoneRepo.GetRecords(command);
}
}

Some loop with conditions. Specific problem

I have a small problem with mathematics or combinatorics in my C# code. I don't know how to write this easiest.
I have a class Section and TestClass but not a method to return expected result.
public class Section
{
public int Id { get; set; }
public int Pages { get; set; }
public string Name { get; set; }
}
[TestFixture]
public class PermutatorTest
{
private IList<Section> _sections;
private int _targetPage;
[SetUp]
public void SetUp()
{
_targetPage = 30;
_sections = new List<Section>
{
new Section {Id = 1, Pages = 15, Name = "A"},
new Section {Id = 2, Pages = 15, Name = "B"},
new Section {Id = 3, Pages = 10, Name = "C" },
new Section {Id = 4, Pages = 10, Name = "D"},
new Section {Id = 5, Pages = 10, Name = "E"},
new Section {Id = 6, Pages = 5, Name = "F"}
};
}
[Test]
public void GetPermutationsTest()
{
// Code to return list of all combinations
}
}
I want to get each combination which give me 30 as a sum of Pages.
it could be return as a string based on name or Id e.g AA or 11 , AB or 12
Of course, the order is not important ( AB and BA is the same... CCD and CDC and DCC too )
Final result should look like this: (30 correct results)
AA
AB
ACF
ADF
AEF
AFFF
BB
BCF
BDF
BEF
BFFF
CCC
CCD
CCE
CDD
CEE
CDE
CFFFF
CDFF
CCFF
CEFF
DDFF
DEFF
DFFFF
DDD
DDE
EFFFF
EEE
EEFF
FFFFFF
e.g. DDE = 10+10+10 = 30 OK
CFFFF = 10 + 5 +5 +5 +5 = 30 Ok
etc.
I dont have idea for best way to create loops for this, and put records to List
Thank you very much for every attempt to help me.
This was my original idea I was going to post for you, it just returned a list of strings
public List<String> result;
public void GetResultList(int startOffs, String CurNames, int curTotal)
{
for (int newOffs = startOffs; newOffs < _sections.Count; newOffs++)
{
int newTotal = curTotal + _sections[newOffs].Pages;
String newNames = CurNames+ _sections[newOffs].Name;
if (newTotal < _targetPage)
GetResultList(newOffs, newNames, newTotal);
else if (newTotal == _targetPage)
result.Add(newNames);
}
}
called by initialising the result & start parameters :
result = new List<String>();
GetResultList(0,"",0);
This is a version modified to use your Config class
public void GetResultList(int startOffs, Config CurConfig)
{
for (int newOffs = startOffs; newOffs < _sections.Count; newOffs++)
{
Config newConfig = new Config{ Name = CurConfig.Name + _sections[newOffs].Name,
Ids = CurConfig.Ids + _sections[newOffs].Id.ToString(),
Pages = CurConfig.Pages + _sections[newOffs].Pages};
if (newConfig.Pages < _targetPage)
GetResultList(newOffs, newConfig);
else if (newConfig.Pages == _targetPage)
_result.Add(newConfig);
}
}
calling needs the result initialising & a starting Config instance
_result = new List<Config>();
Config s = new Config { Ids = "", Pages=0, Name=""};
GetResultList(0,s);
Only for Information and Searchers.
I know, this code is not so clean
but I put it here as a nUnit Test...
it returns what I wanted ... i think.
using System;
using System.Collections.Generic;
using NUnit.Framework;
[TestFixture]
public class PermutatorTest
{
private IList<Section> _sections;
private int _targetPage;
private IList<Config> _result;
[SetUp]
public void SetUp()
{
_targetPage = 30;
_sections = new List<Section>
{
new Section {Id = 1, Pages = 15, Name = "A"},
new Section {Id = 2, Pages = 15, Name = "B"},
new Section {Id = 3, Pages = 10, Name = "C" },
new Section {Id = 4, Pages = 10, Name = "D"},
new Section {Id = 5, Pages = 10, Name = "E"},
new Section {Id = 6, Pages = 5, Name = "F"}
};
_result = new List<Config>();
}
[Test]
public void GetPermutationsTest()
{
for (var b =0 ; b<=_sections.Count-1; b++)
{
var config = new Config
{
Name = _sections[b].Name,
Ids = _sections[b].Id.ToString(),
Pages = _sections[b].Pages
};
GoDeeperAndAddToResult(config, b);
}
Console.WriteLine(_result.Count);
foreach (var item in _result)
{
Console.WriteLine($"{item.Name} - {item.Ids} - {item.Pages}");
}
}
private void GoDeeperAndAddToResult(Config config, int startIndex)
{
for (var b = startIndex; b <= _sections.Count-1; b++)
{
var section = _sections[b];
var combName = config.Name;
var combIds = config.Ids;
var combPages = config.Pages;
var maxSec = _targetPage / section.Pages;
for (var a = 1; a <= maxSec; a++)
{
combName = combName + section.Name;
combIds = combIds + section.Id.ToString();
combPages = combPages + section.Pages;
var subConfig = new Config
{
Name = combName,
Ids = combIds,
Pages = combPages
};
if (subConfig.Pages == _targetPage)
{
_result.Add(subConfig);
break;
}
else if (subConfig.Pages < _targetPage)
{
GoDeeperAndAddToResult(subConfig, b + 1);
}
else
{
break;
}
}
}
}
public class Config
{
public string Name { get; set; }
public string Ids { get; set; }
public int Pages { get; set; }
}
public class Section
{
public int Id { get; set; }
public int Pages { get; set; }
public string Name { get; set; }
}
}

Unit testing: Sending complex object as a input parameter to my test method using MSTest

I'm trying to send a List<DataStatusItem> as a input parameter to my unit test method using DataRow attribute as below,
[TestClass]
public class UpdateProcessingTestController
{
private List<DataStatusItem> DataStatusItemsTestSetup = new List<DataStatusItem>() {
new DataStatusItem { DataItemID = 1, DataItemCurrentStatusID = 1, DataItemStatusID = 1, DateEffective = DateTime.Now }
};
private readonly Mock<IEmployee> moqEmployee;
public UpdateProcessingTestController()
{
moqEmployee = new Mock<IEmployee>();
}
[TestMethod]
[DataRow(DataStatusItemsTestSetup, 1, 8, 1)] // **This is where it is throwing me compilation error**
public void SetDataItems(List<DataStatusItem> DataStatusItems,int brand, int dataType, int processingStatus)
}
Please let me know how to send the List as a input parameter to my test method.
Use DynamicData Attribute, Here is an example:
public class DataStatusItem
{
public int DataItemID { get; set; }
public int DataItemCurrentStatusID { get; set; }
public int DataItemStatusID { get; set; }
public DateTime DateEffective { get; set; }
}
[TestClass]
public class UpdateProcessingTestController
{
static IEnumerable<object[]> DataStatusItemsTestSetup
{
get
{
return new[]
{
new object[]
{
new List<DataStatusItem>
{
new DataStatusItem { DataItemID = 1, DataItemCurrentStatusID = 1, DataItemStatusID = 1, DateEffective = DateTime.Now },
new DataStatusItem { DataItemID = 2, DataItemCurrentStatusID = 2, DataItemStatusID = 2, DateEffective = DateTime.Now },
},
1, // brand
2, // dataType
3 // processingStatus
}
};
}
}
[TestMethod]
[DynamicData(nameof(DataStatusItemsTestSetup))]
public void SetDataItems(List<DataStatusItem> dataStatusItems, int brand, int dataType, int processingStatus)
{
Assert.AreEqual(2, dataStatusItems.Count);
Assert.AreEqual(1, brand);
Assert.AreEqual(2, dataType);
Assert.AreEqual(3, processingStatus);
}
}

C# - Adding data to list inside list

How can I add the following data on the table into a list called Vehicles?
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public string name { get; set; }
public IList<criterias> criteria = new List<criterias>();
}
public class stepsList
{
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria = new List<movChannels>();
}
public class vehicles
{
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria = new List<stepsList>();
}
Now, how can I add the data that I have in the table shown into a list called Vehicles? I will create other vehicles later...
You had several bad decisions, some were design flaws and some were minor C# naming convention violations.
Couple of worth mentions flaws:
vehID should have been a string and not int (Example "XPT")
Movment has Name, Value and Time. It doesn't have a list of Values and Times.
Creation:
List<Vehicle> vehicles = new List<Vehicle>();
Vehicle vehicle = new Vehicle()
{
Id = "XPT",
Description = "Average Car",
Steps = new List<Step>()
{
new Step() {
Name = "move car",
Movements = new List<Movement>()
{
new Movement("engage 1st gear", 1, 1),
new Movement("reach 10kph", 10, 5),
new Movement("maintain 10kph", 10, 12),
}
},
new Step() {
Name = "stop car",
Movements = new List<Movement>()
{
new Movement("reach 0kph", 10, 4),
new Movement("put in neutral", 0, 1),
new Movement("turn off vehicle", 0, 0),
}
}
}
};
vehicles.Add(vehicle);
Entities:
public class Movement
{
public string Name { get; set; }
public double Values { get; private set; }
public double Time { get; private set; }
public Movement(string name, double values, double time)
{
Name = name;
Values = values;
Time = time;
}
}
public class Step
{
public string Name { get; set; }
public IList<Movement> Movements { get; set; }
}
public class Vehicle
{
public string Id { get; set; } // Should be changed to string
public string Description { get; set; }
public IList<Step> Steps { get; set; }
}
You should create your classes like the following:
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public movChannels
{
criteria = new List<criterias>();
}
public string name { get; set; }
public IList<criterias> criteria { get; set; }
}
public class stepsList
{
public stepsList
{
stepChannelsCriteria = new List<movChannels>();
}
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria { get; set; }
}
public class vehicles
{
public vehicles
{
vehValCriteria = new List<stepsList>();
}
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria { get; set; }
public movChannels movments { get; set; }
}
What about that?
public class VehiclesViewModel
{
public List<vehicles> Vehicles { get; private set; }
public void Initalize()
{
this.Vehicles = new List<vehicles>();
var vehicle = new vehicles
{
vehID = 1,
vehDescription = "firstDescription",
};
var stepsList = new stepsList
{
steps = "firstStep",
};
var movChannel = new movChannels
{
name = "firstChannel",
};
var criteria = new criterias
{
values = 0.5,
time = 0.5
};
movChannel.criteria.Add(criteria);
stepsList.stepChannelsCriteria.Add(movChannel);
vehicle.vehValCriteria.Add(stepsList);
this.Vehicles.Add(vehicle);
}
}
it seems in your table the VehicleId is of type string. Make sure your VehicleId property in Vehicle class also matches the same.
You can use the collection initializers to set the values of child objects like this way:
var data = new vehicles()
{
vehID = 1,
vehDescription = "Average Car",
vehValCriteria = new List<stepsList>()
{
new stepsList()
{
steps = "Move car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "engage firstgear",
criteria = new List<criterias>()
{
new criterias()
{
values = 1,
time = 1
},
}
},
new movChannels()
{
name = "reach 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 5
},
}
},
new movChannels()
{
name = "maintain 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 12
},
}
}
}
},
new stepsList()
{
steps = "stop car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "reach okph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 4
},
}
},
new movChannels()
{
name = "put in neutral",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 1
},
}
},
new movChannels()
{
name = "turn off vehicle",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 0
},
}
}
}
}
}
};
You can fill your list by moving from top to bottom, like
Create Criterias List then Create movChannel object and add that list
to Criterias object and so on
However if you want to avoid this way, there is another way. If you are using Linq To List then follow this
Get a simple flat object to a list object
var TableData = db.Tablename.Tolist();
Then fill your own object like this
Vehicles finalList = TableData.Select(a => new Vehicles()
{
vehID = a.Id,
vehDescription = a.des,
vehValCriteria = TableData.Where(b => b.StepslistId == a.StepslistId)
.Select(c => new StepsList()
{
steps = c.Steps,
stepChannelsCriteria = TableData.Where(d => d.channelId == c.channelId)
.select(e => new MovChannels()
{
name = e.name,
criteria = TableData.Where(f => f.criteriasId = e.criteriasId)
.Select(g => new Criterias()
{
values = g.Values,
time = g.Time
}).ToList()
}).ToList()
}).ToList()
}).ToList();
This is standard way to fill list within list

SelectMany/Select - Flatten a many-to-many relationship

I have a main table called Task and each Task can be connected to zero or more Customers so I also have a Customer_Task many-to-many table. Basically what I want is to flatten the many-to-many relation to get the following result:
Task.Field1, ... ,Task.FieldN, Customer.Name, Customer.Number
So basically I just want the Task entity plus two more fields that should come from the many-to-many relation with the Customer entity.
I have only succesfully used Select/SelectMany once before, and that was a very simple case, so I figured I just ask the experts. Can anyone help me with this?
My guess would be something like this, but that does not work:
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.SelectMany(c=>c.Customer.Name)).ToList();
Requested class structure (I have removed a lot of irrelevant information):
public partial class Customer
{
public Customer()
{
this.Customer_Task = new HashSet<Customer_Task>();
}
public int Id { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public partial class Customer_Task
{
public int CustomerId { get; set; }
public int TaskId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Task Task { get; set; }
}
public partial class Task
{
public Task()
{
this.Customer_Task = new HashSet<Customer_Task>();
}
public int Id { get; set; }
public int Number { get; set; }
public string Header { get; set; }
public string Description { get; set; }
public virtual ICollection<Customer_Task> Customer_Task { get; set; }
}
The exception I get is ArgumentException:
System.ArgumentException was unhandled
Message=DbExpressionBinding requires an input expression with a collection ResultType.
If I understand your requirements, then something like this should do:
var _database = new List<Task>
{
new Task
{
Customer_Task = new List<Customer_Task>
{
new Customer_Task
{
Customer = new Customer {Id = 1, Name = "a"},
Task = new Task {Id = 1, Number = 1}
},
new Customer_Task
{
Customer = new Customer {Id = 1, Name = "b"},
Task = new Task {Id = 1, Number = 1}
},
new Customer_Task
{
Customer = new Customer {Id = 2, Name = "a"},
Task = new Task {Id = 2, Number = 2}
},
new Customer_Task
{
Customer = new Customer {Id = 2, Name = "b"},
Task = new Task {Id = 2, Number = 2}
}
},
}
};
var tasks = _database.SelectMany(t => t.Customer_Task.Select(c => new { Task = c.Task.Id, Name =c.Customer.Name})).ToList();
foreach (var t in tasks)
{
Console.WriteLine(t.Task + " "+t.Name);
}
Console.ReadLine();
Where the result is:
1 a
1 b
2 a
2 b
So you can change your query from
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.SelectMany(c=>c.Customer.Name)).ToList();
to
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.Select(c=>c.Customer.Name)).ToList();

Categories