Ignore a [BsonIgnore] when using BsonSerializer.Deserialize() - c#

I have a 'User' class with a list of organisations. With [BsonIgnore] I'm not saving the organisations. They are in another collection. That's why 'User' also has a list of organisationId's. This works fine.
public class User
{
public ObjectId _id { get; set; }
public List<ObjectId> OrganisationIds { get; set; }
[BsonIgnore]
public List<Organisation> Organisations { set; get; }
}
Problem
The problem is when I do a query to get the User and his organisations. On deserializing the BsonDocument, It throws an exception. Something like: 'Organisations' on 'User' does not exists.
var aggregation = _userService.GetCollection()
.Aggregate<User>()
.Lookup<User>("Organisations", "OrganisationId", "_id", "Organisation");
var firstUser = aggregation.FirstOrDefault();
var user = BsonSerializer.Deserialize<User>(firstUser);
I don't want to save organisations in a user. How should I approach this?

The [BsonIgnore] caused the trouble. It didn't complain about there is no such field, but it complained when aggregating.
I have the following, and it's working>
public class User
{
public ObjectId _id { get; set; }
public List<ObjectId> OrganisationIds { get; set; }
}
public class UserAggregate
{
public List<ObjectId> OrganisationIds { get; set; }
public ObjectId _id { get; set; }
public List<Organisation> Organisations { set; get; }
}
public class Organisation
{
public ObjectId _id { get; set; }
public string Name { get; set; }
}
string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("test");
var instances = db.GetCollection<User>("Users");
var instances2 = db.GetCollection<Organisation>("Organisations");
var aggregation = instances.Aggregate().Match(...).Lookup(foreignCollection: instances2,
localField: x => x.OrganisationIds, foreignField: x => x._id,
#as: (UserAggregate pr) => pr.Organisations)
.ToList();

Related

Automapper override values which are not in source

Hi have a problem with auomapper where i try to use the Automapper.Mapper(src, dest) without loosing the values in the dest after doing the mapping.
I have a class which has a list of objects like below
public class UpdateShipmentDetailDto
{
public bool IsDocument { get; set; }
public List<UpdateItemDetailDto> ItemDetails { get; set; } = new();
}
which i want to map to
public class SCS_OUT_Manifest
{
public Guid ManifestId { get; set; }
public ICollection<SCS_OUT_ManifestItem> SCS_OUT_ManifestItems { get; set; } = new List<SCS_OUT_ManifestItem>();
}
The UpdateItemDetailDto class looks like this
public class UpdateItemDetailDto
{
public Guid ItemId { get; set; }
public string ItemDescription { get; set; }
public int Qty { get; set; }
public Guid UnitsId { get; set; }
public decimal ItemValue { get; set; }
}
And the SCS_OUT_ManifestItem class looke like
public class SCS_OUT_ManifestItem
{
public Guid ItemId { get; set; }
public Guid ManifestId { get; set; }
public string ItemDescription { get; set; }
public int Qty { get; set; }
public Guid UnitsId { get; set; }
public decimal ItemValue { get; set; }
}
Im performing a maaping like below, which map from ItemDetails (which is a list) to SCS_OUT_ManifestItems (which is also a ICollection).
_mapper.Map(updateShipmentDetailDto.ItemDetails, manifest.SCS_OUT_ManifestItems);
The problem after mapping is done the properties which in the destination collection are set to the default values.
for example the ManifestId inthe SCS_OUT_ManifestItem manifest.SCS_OUT_ManifestItems which is not in updateShipmentDetailDto.ItemDetails is set to its default Guid value 00000000-0000-0000-0000-000000000000.
But if i run this in a loop like below it works.
foreach (var item in manifest.SCS_OUT_ManifestItems)
{
_mapper.Map(updateShipmentDetailDto.ItemDetails.Single(s => s.ItemId == item.ItemId), item);
}
Please help! thanks in advance
Try map your lists and your itens and use the same name ÏtemDetails" in both lists.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<UpdateItemDetailDto, SCS_OUT_ManifestItem>();
cfg.CreateMap<UpdateShipmentDetailDto, SCS_OUT_Manifest>();
});
var _mapper = config.CreateMapper();
var updateShipmentDetailDto = new UpdateShipmentDetailDto();
var updateItemDetailDto = new UpdateItemDetailDto();
var manifest = new SCS_OUT_Manifest();
updateItemDetailDto.ItemId = Guid.NewGuid();
updateItemDetailDto.UnitsId = Guid.NewGuid();
manifest.ManifestId = Guid.NewGuid();
updateItemDetailDto.ItemDescription = "test";
updateItemDetailDto.Qty = 10;
updateItemDetailDto.ItemValue = 25.50M;
updateShipmentDetailDto.ItemDetails = new List<UpdateItemDetailDto>();
updateShipmentDetailDto.ItemDetails.Add(updateItemDetailDto);
_mapper.Map(updateShipmentDetailDto.ItemDetails, manifest.ItemDetails);
Console.WriteLine($"DTO Guid: {updateShipmentDetailDto.ItemDetails[0].ItemId}, Desc: {updateShipmentDetailDto.ItemDetails[0].ItemDescription}");
foreach (var item in manifest.ItemDetails)
{
Console.WriteLine($"Guid: {item.ItemId}, Desc: {item.ItemDescription}");
}
Console.WriteLine($"Guid Manifest: {manifest.ManifestId}");

AutoMapper MapExpression for sub-entity fails

I'm mapping select expression (projection) of Linq query. This is done to decouple logic layer from data access layer and logic layer should use only DTOs.
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id,
Citizens = c.Citizens.Select(p => new CitizenDto
{
}).ToList()
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
This mapping fails with error Expression of type 'DTOs.CitizenDto' cannot be used for return type 'Entities.Citizen' however in CountyInfoDto property Citizens has type CitizenDto. Please note all mapping profiles are valid and simple objects can be mapped properly.
If I do like this, all works:
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
or this also works:
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id,
Citizens = new List<CitizenDto>
{
new CitizenDto
{
Id = c.Citizens.First().Id
}
}
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
is there any possibility to avoid this error?
Classes:
public class CountyInfo
{
public CountyInfo()
{
Citizens = new HashSet<Citizen>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Citizen> Citizens { get; set; }
}
public class Citizen
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public class CountyInfoDto
{
public CountyInfoDto()
{
Citizens = new List<CitizenDto>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public List<CitizenDto> Citizens { get; set; }
}
public class CitizenDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
Mappings:
CreateMap<CountyInfo, CountyInfoDto>().ReverseMap();
CreateMap<Citizen, CitizenDto>().ReverseMap();
I'm using AutoMapper.Extensions.ExpressionMapping, after update to latest version error is: No coercion operator is defined between types 'Entities.CountyInfo' and 'DTOs.CountyInfoDto'.

MongoDB C# Driver Projection of fields

Hi i have a collection In mongoDB that i want to get only part of the fields from it, i created a class that i'm inserting data with to Mongo
ClassCode:
public class FrameDocument
{
public ObjectId _id { get; set; }
public Nullable<System.DateTime> FrameTimeStamp { get; set; }
public Nullable<int> ActivePick { get; set; }
public Nullable<int> TraderId { get; set; }
public Nullable<int> EventCodeId { get; set; }
public byte[] Frame { get; set; }
public int ServerUserId { get; set; }
public int SesionId { get; set; }
public string TraderName { get; set; }
public string ServerUserName { get; set; }
}
This is the insert code:
FrameDocument frameDoc = new FrameDocument();
frameDoc.Frame = imageBA;
frameDoc.EventCodeId = 1;
frameDoc.SesionId = 1;
frameDoc.FrameTimeStamp = DateTime.Now;
frameDoc.ServerUserId = (int)toMongoDt.Rows[0]["ServerUserId"];
frameDoc.TraderId = (int)toMongoDt.Rows[0]["TraderId"];
frameDoc.ActivePick = (int)toMongoDt.Rows[0]["ActivePick"];
frameDoc.TraderName = (string)toMongoDt.Rows[0]["TraderName"];
frameDoc.ServerUserName = (string)toMongoDt.Rows[0] ["ServerUserName"];
var mongoCon = "mongodb://127.0.0.1";
MongoClient client = new MongoClient(mongoCon);
var db = client.GetDatabase("Video");
var frameCollection = db.GetCollection<FrameDocument>("Frame");
frameCollection.InsertOne(frameDoc);
**For now i get all The fields from the collection with this code, But i want to leave the Frame field out of the class, i tried to build different class without this field but i don't know how to not receive the Frame field **
var collection = db.GetCollection<BsonDocument>("Frame");
var builder = Builders<BsonDocument>.Filter;
var filter = builder.Eq("SesionId", 1)
& builder.Eq("TraderId", 125)
& builder.Eq("ServerUserId", 1)
& builder.Lt("FrameTimeStamp", sing.eDate)
& builder.Gt("FrameTimeStamp", sing.sDate);
var result = collection.Find(filter).ToList();
Can anyone help?
please see this:
_result = _collection.Find(o => o._id == _id)
.Project<FrameDocumentNoFrameField>
(Builders<FrameDocument>.Projection.Exclude(f => f.Frame)).ToList();
where FrameDocumentNoFrameField is a class without Frame field
source here
#Example: Model class
public class Company
{
public string CompanyId { get; set; }
public string CompanyName { get; set; }
public List<CompanySettings>{ get; set; }
}
[BsonIgnoreExtraElements]
public class CompanySettings
{
public CompanySetupType CompanySetupTypeId { get; set; }
public List<string> CompanyEmployee{ get; set; }
}
#Now create a Projection class for which you want to read values
[BsonIgnoreExtraElements]
public class CompanySettingsProjectionModel
{
public List<CompanySettings> CompanySettings { get; set; }
}
#After Creating projection,fetch data from mongo using Builders
public async Task<CompanySettings> GetCompanySettings(string companyId, short CompanySetupTypeId)
{
var filter = BaseFilter(accountId);
var projection = Builders<Company>.Projection
.Include(x => x.Id)
.Include(x => x.CompanySettings);
FindOptions<Company, CompanySettingsProjectionModel> findOptions = new FindOptions<Company, CompanySettingsProjectionModel>()
{
Projection = projection
};
var companySettings = await (await Collection.FindAsync(filter, findOptions)).FirstOrDefaultAsync();
if (companySettings != null && companySettings.CompanySettings != null && companySettings.CompanySettings.Any())
{
return companySettings.CompanySettings .FirstOrDefault(x => (int)x.CompanySetupTypeId == CompanySetupTypeId);
}
return default;
}

Mongo DB query collection by subcollection attribute

I have:
public class Movie : IMongoEntity
{
public ObjectId Id { get; set; }
public string Title { get; set; }
public string Year { get; set; }
public List<Actor> Actors { get; set; }
}
public class Actor : IMongoEntity
{
public ObjectId Id { get; set; }
public string Name { get; set; }
}
If i want to retrieve the entire movie collection I do
var query = this.MongoConnectionHandler.MongoCollection.FindAllAs<Movie>();
No I want to retrieve just the movies that have an actor with a certain name
I've tried something like:
IMongoQuery query = Query<Movie>.Where(m => m.Actors.Select(a => a.Name).Any(n => n.Contains(actorName)));
var exc = this.MongoConnectionHandler.MongoCollection.Find(query);
But that won't work.
I think this should do the trick:
var query = Query<Movie>.ElemMatch(m => m.Actors, builder => builder.EQ(actor => actor.Name, actorName));
var exc = this.MongoConnectionHandler.MongoCollection.Find(query);

Using EF LazyLoading for initializing properties?

UPDATED: I understood I should not use a DbSet so I changed the implementation to an ICollection as suggested by Erenga
Please consider the following classes:
[Table("Tenant")]
public class Tenant : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("User")]
public class User : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
The first test creates a new Tenant and a new User and stores them in the appropriate tables.
[Test]
public void CreateNewUserForNewTenant()
{
var user = _applicationContext.Users.Create();
user.Name = "barney";
user.EmailAddress = "barney#flinstone.com";
var tenant = _applicationContext.Tenants.Create();
tenant.Name = "localhost";
tenant.Guid = Guid.NewGuid().ToString();
tenant.Users.Add(user); // NullReferenceException, I expected the EF would LazyLoad the reference to Users?!
_tenantRepository.Add(tenant);
_applicationContext.SaveChanges();
}
This test will fail on a NullReferenceException since the property Users is not initialized.
How should I change my code that I can rely on LazyLoading provided with EF?
There are 2 problems I see here.
As #SimonWhitehead mentioned, reference types are initialized as null by default. Lazy loading works only on entities created by EF. These are actually sub classes of your class that contain addtional logic to lazy load.
DbSet is not a collection type that is supported on entities. You need to change the type to ICollection, ISet, or IList.
Here's a working example
[Table("Tenant")]
public class Tenant : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("User")]
public class User : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
[Test]
public void CreateNewUserForNewTenant()
{
var user = _applicationContext.Users.Create();
user.Name = "barney";
user.EmailAddress = "barney#flinstone.com";
var tenant = _applicationContext.Tenents.Create();
tenant.Name = "localhost";
tenant.Guid = Guid.NewGuid().ToString();
tenant.Users = new List<User> { user };
_tenantRepository.Add(tenant);
_applicationContext.SaveChanges();
}
var tenant = new Tenant
{
Name = "localhost",
Guid = Guid.NewGuid().ToString(),
Users = new List<User> { user }
};
I think you were expecting something like this (not threadsafe):
[Table("Tenant")]
public class Tenant : IEntity
{
private DbSet<User> _users;
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users
{
get
{
if (_users == null)
_users = new List<Users>();
return _users;
}
set { _users = value; }
}
}
I'm sure the Lazy<T> class could be utilised somehow too but I am not familiar with that class.

Categories

Resources