I have two user objects, Student and Professor. Each user can have their own settings, which looks like this:
public class PersonSettings
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
public PersonSettingsValues Settings { get; set; }
}
public class PersonSettingsValues
{
public bool NotificationsEnabled { get; set; }
}
public class StudentSettingsValues : PersonSettingsValues
{
public int GymPassTypeId { get; set; }
}
public class ProfessorSettingsValues : PersonSettingsValues
{
public bool AllowOfficeHours { get; set; }
}
Some settings are shared, but Students and Professors also have their own settings, so I have inherited models.
I'm just storing the settings as json in the database by using conversion values on PersonSettings:
builder.Property(ps => ps.Settings).HasConversion(
s => JsonConvert.SerializeObject(s, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
s => JsonConvert.DeserializeObject<PersonSettingsValues>(s, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
So the PersonSettings table is populated with several records with settings, some have the data in the Settings column formatted as StudentSettings and others are ProfessorSettings
What I'd like to do is have an endpoint that will return StudentSettings for a Student. I have:
[HttpGet("{personId}")]
public async Task<IActionResult> GetStudentSettings([FromRoute] Guid personId)
{
var personSettings = await _context.PersonSettings
.FirstOrDefaultAsync(ps => ps.PersonId == id);
return Ok(personSettings);
}
The problem is that the Settings property is populated with only the PersonSettingsValues, I don't get the StudentSettingsValues with it. I'm not sure how to make it do that.
I tried using a DTO instead and mapping to it with AutoMapper:
public class StudentSettingsDTO
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
public StudentSettingsValues Settings { get; set; }
}
...
[HttpGet("{personId}")]
public async Task<IActionResult> GetStudentSettings([FromRoute] Guid personId)
{
var personSettings = await _context.PersonSettings
.FirstOrDefaultAsync(ps => ps.PersonId == id);
return Ok(_mapper.Map<StudentSettingsDTO>(personSettings));
}
...
CreateMap<PersonSettings, StudentSettingsDTO>()
.ForMember(ps => ps.Settings,
o => o.ResolveUsing(s => (StudentSettingsValues)s.Settings));
In my mapping, I'm just trying to cast the Settings as a StudentSettingsValues type, but I get an error saying
Unable to cast object of type 'PersonSettingsValues' to type 'StudentSettingsValues'
Not sure what I'm doing wrong here, how can I just return a PersonSettings object with either StudentSettings or ProfessorSettings on it?
It's because your builder, when parsing the json from database, parses to PersonSettingsValues. Once that's done, you lose all the other data. You have a few options:
Not sure what this builder exactly is and how much custom logic you
can add. But if at the time of deserialization you know if it's
professor or student, you should deserialize to object you need.
Then even if you assign it PersonSettingsValues you will still be
able to cast it later on to a more concrete type.
Instead of different models, make interfaces. IPersonSettingsValues,
IStudentSettingsValues, and IProfessorSettingsValues. And then have
just one model that implements all of the interfaces. Then your
deserializer can serialize to that one model. The values that don't
exist will go to default, but it will always be deserialized. And
then when you want to use it from specific context, you will be able
to cast it to a specific interface.
Deserialize to a dynamic object(thus it just deserializes whatever is in the json), and then use autoMapper to map dynamic object to a concrete type.
If it's not clear, tell me and I will try to come up with a code example.
Related
I am using .NET Core 7.0 and AutoMapper.Dependency 12.
I am sending a JSON object as below to the Company table via Postman.
Automatically "null" from database when some values are empty replaces with.
I have a structure like below, and I want to ignore null values
The companyUpdateDTO object may have some columns blank by the user, so how can I ignore the blank values that come with dto?
I want to do this globally via AutoMapper. But in no way could I ignore the empty values.
The JSON object I sent: I am not submitting the "description" field, so it is changed to "null" in the database.
{
"id": 1002,
"name": "xcv"
}
Company entity:
public class Company
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
CompanyUpdateDTO class:
public class CompanyUpdateDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? UpdatedDate { get; set; }
}
Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfile));
AutoMapper profile:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
AllowNullCollections = true;
#region Company DTO
CreateMap<Company, CompanyDto>().ReverseMap();
CreateMap<Company, CompanyCreateDto>().ReverseMap();
//CreateMap<Company, CompanyUpdateDto>().ReverseMap();
CreateMap<Company, CompanyUpdateDto>().ReverseMap().ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
#endregion
}
}
The problem is two-fold. At first you receive a JSON and you deserialize it into a DTO (by the way, what serialize do you use??). Afterwards you can't distinguish if the value of a property was explictly set or if it still has its default value.
In a second step you convert this DTO into another object by using AutoMapper and send this to your database. In case of an update, how to you read the existing entity from your database and how do you make the update call?
Let's start by trying to distinguish between explicitly set values and omitted values. To solve this problem, you have to define magic values for each type that can be omitted. Really think about these values and try to find values, that will really never be used, because you can't distinguished between the omitted value and an explicitly set value of exact that value! For example:
public static class Omitted
{
public static readonly int Integer = int.MinValue;
public static readonly string String = "{omitted}";
public static readonly DateTime DateTime = DateTime.MaxValue;
}
By having this bunch of defaults you have to slightly adjust your DTO classes and apply these omitted values by default:
public class CompanyUpdateDto
{
public int Id { get; set; } = Omitted.Integer;
public string? Name { get; set; } = Omitted.String;
public string? Description { get; set; } = Omitted.String;
public DateTime? UpdatedDate { get; set; } = Omitted.DateTime;
}
If you have prepared your DTO accordingly and you deserialize your JSON into a new instance you can distinguish between the values has been explicitly set to null or omitted by comparison.
In a next step we need to convert from the DTO to the entity object of the database. Due to the fact, that you make an update I guess you read the entity from database and use AutoMapper to apply a source object onto an existing target object. A rough sketch would be:
// Create DTO from json
var dto = CreateObjectFrom(json);
// Read existing instance from database
var existing = await database.Companies.FirstAsync(c => c.Id == dto.Id);
// Map DTO on existing entity and avoid omitted values
mapper.Map<CompanyUpdateDto, Company>(dto, existing);
// Save changes to database
await database.SaveChangesAsync();
Unfortunately to conditionally omit the update if a property has its magic value we have to explicitly define them all in the mapping profile:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<CompanyUpdateDto, Company>()
.ForMember(c => c.Name, dto => dto.Condition((_, _, value) => NotOmitted.String(value)))
.ForMember(c => c.Description, dto => dto.Condition((_, _, value) => NotOmitted.String(value)));
}
}
We just need the next helper class as already mentioned in code:
public static class NotOmitted
{
public static bool Integer(int value) => value == Omitted.Integer;
public static bool String(string value) => value == Omitted.String;
public static bool DateTime(DateTime value) => value == Omitted.DateTime;
}
With this approach you can distinguish, if a value in JSON was omitted or explicitly set to null and by using the .Condition() call within AutoMapper you can check this value before it is being applied to the destination object.
We are using AutoMapper (9.0.0) in .net core for mapping values between source and destination. Till time this is working fine. However, we need to keep some of the values in destination as it is after mapping.
We have tried to used UseDestinationValue() and Ignore() methods on member, but it is not preserving the existing values. Below is the code for the same.
RequestModel
public class RequestModel
{
public int Id { get; set; }
public int SubmittedById { get; set; }
public string Description { get; set; }
public string Location { get; set; }
}
RequestDto
public class RequestDto
{
public int Id { get; set; }
public int SubmittedById { get; set; }
public string Description { get; set; }
public string Location { get; set; }
public string SubmittedByName { get; set; }
}
We are accepting Dto in API as request parameter
API
[HttpPost]
public IActionResult Save([FromBody] RequestDto requestDto)
{
// Some logic to save records
}
So, before saving the records we are mapping RequestDto to RequestModel and passing that model to DAL layer to save the records like this
var requestModel = MapperManager.Mapper.Map<RequestDto, RequestModel>(RequestDto);
And call to data layer
var requestModel = DAL.Save(RequestModel)
So, after receiving the updated request model we are again mapping it to requestDto, in this case we are loosing the value for SubmittedByName property.
return MapperManager.Mapper.Map<RequestModel, RequestDto>(requestModel);
Mapper Class
public class RequestProfile: Profile
{
public RequestProfile()
{
CreateMap<RequestModel, RequestDto>()
CreateMap<RequestDto, RequestModel>()
}
}
This SubmittedByName column is not present in the Request table, but we want to utilize its value after saving the records.
So, how can we preserve the destination value after mapping.
Any help on this appreciated !
I think you have to use the Map overload that accepts destination.
This works for me, using same model / dto you posted, in a console application:
var config = new MapperConfiguration(cfg => cfg.CreateMap<RequestModel, RequestDto>().ReverseMap());
var mapper = config.CreateMapper();
var source = new RequestDto
{
Id = 1,
SubmittedById = 100,
SubmittedByName = "User 100",
Description = "Item 1",
Location = "Location 1"
};
Console.WriteLine($"Name (original): {source.SubmittedByName}");
var destination = mapper.Map<RequestDto, RequestModel>(source);
Console.WriteLine($"Name (intermediate): {source.SubmittedByName}");
source = mapper.Map<RequestModel, RequestDto>(destination, source);
Console.WriteLine($"Name (final): {source.SubmittedByName}");
The standard Map method creates a new object but the overloaded method uses existing object as destination.
We have tried to used UseDestinationValue() and Ignore() methods on member, but it is not preserving the existing values. Below is the code for the same.
since that didn't work for you
I would suggest creating a generic class like this (assuming you have multiple classes of RequestDto)
class RequesterInfo<T>
{
public string RequesterName { get; set; } // props you want to preserve
public T RequestDto { get; set; } // props to be mapped
}
by keeping the mapping as it is,
and modifying your code to something like this:
var requestModel = MapperManager.Mapper.Map<RequestDto, RequestModel>(RequesterInfo.RequestDto);
so what happens is that you modify the T RequestDto part of the object without modifying other properties.
I created a controller for returning JSON to a mobile application. Given this action:
public JsonResult GetFirm(int id)
{
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm);
return Json(firmString, JsonRequestBehavior.AllowGet);
}
This method(above method) throw self referencing loop error and I write :
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
return Json(firmString, JsonRequestBehavior.AllowGet);
But this method send object with all child and collections and json include "\" before all attribute.
I want to send json object without child or collections.
Analysis and Issue
Your issue is clearly somewhere in your model you have a circular reference. I am highly confident it is due to you are trying to deserialize a database model (an EF model at a guess) directly. Doing that is not recommended.
A database model will contain references typically to other classes, which in themselves reference back to the first class due to representing a database structure. So you likely have something like:
public class ParentItemDataModel
{
[Key]
public Guid Id { get; set; }
public string SomeInfo { get; set; }
public List<ChildItemDataModel> Children { get; set; }
}
public class ChildItemDataModel
{
public ParentItemDataModel Parent { get; set; }
public string SomeInfo { get; set; }
}
So as you can see, a clear reference loop if deserialized.
Recommendation
Separate your API models returned to clients from your underlying database models by creating a new, simple class that contains only the required information such as:
public class ParentItemApiModel
{
public Guid Id { get; set; }
public string SomeInfo { get; set; }
}
Then you can get that information into the model the old-fashioned way:
var parentApiModel = new ParentItemApiModel
{
Id = dataModel.Id,
SomeInfo = dataModel.SomeInfo
};
Or you can do it with an extension method in your data model (so your typically lower core Api model has no reference back up to the database layer):
public static class DataModelExtensions
{
public static ParentItemApiModel ToApiModel(this ParentItemDataModel dataModel)
{
return new ParentItemApiModel
{
Id = dataModel.Id,
SomeInfo = dataModel.SomeInfo
};
}
}
So that now you can do this:
var apiModel = dataModel.ToApiModel();
Or in your example that would then be:
string firmString = JsonConvert.SerializeObject(firm.ToApiModel());
Or any other way you like.
Direct fix
If you do not want to fix it by the recommendation, the quickest, simplest way is to flag your data model Firm properties that you don't want in the JSON model with [JsonIgnore].
Try returing a string instead of JsonResult
public string GetFirm(int id)
{
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
return firmString;
}
I have a repository for a DocumentDb database. My documents all have a set of common properties so all documents implement the IDocumentEntity interface.
public interface IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
}
public class KnownDocument : IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
[JsonProperty("knownProperty")]
string KnownProperty { get; set; }
}
public class BaseDocumentRepository<T> where T : IDocumentEntity {
public Set(T entity) {
// ... stuff
}
}
This works fine with a KnownDocument where I know all of the properties. But, of course, what's great about a Document Db is that I don't need to know all of the properties (and in many cases I won't).
So my client submits something like this-
{unknownProperty1: 1, unknownProperty2: 2}
And I want to upsert this using my document repository.
public OtherDocumentService() {
_otherDocumentService = new OtherDocumentRepository();
}
public UpsertDocument(dynamic entity) {
entity.id = new Guid();
entity.documentClassification = DocumentClassification.Other;
_otherDocumentRepository.Set(entity);
}
But I get an InvalidCastException from dynamic to IDocumentEntity. I assume it's because of the extra properties that exist on the dynamic object but not on the IDocumentEntity interface?
What I'm trying to do is leave my document entities open to be dynamic, but rely on a few properties being there to maintain them.
Entity parameter passed to the UpsertDocument should explicitly implement IDocumentEntity in order do make the code works, it is not enough just have a Id property.
Some options:
1) Proxy may be applied:
public class ProxyDocumentEntity : IDocumentEntity
{
public dynamic Content { get; private set; }
public ProxyDocumentEntity(dynamic #content)
{
Content = #content;
}
public Guid Id
{
get { return Content.Id; }
set { Content.Id = value; }
}
}
... using
public void UpsertDocument(dynamic entity)
{
entity.Id = new Guid();
repo.Set(new ProxyDocumentEntity(entity));
}
The stored document will have nested Object property, which may be not acceptable
2)There is a lib https://github.com/ekonbenefits/impromptu-interface which creates a proxy dynamically
and does not make extra property like solution above.
Drawback will be in performance.
Technically it could be 2 methods:
public void UpsertDocument(IDocumentEntity entity){...}
public void UpsertDocument(dynamic entity){...}
so the first (fast) will work for the objects which implement IDocumentEntity and second(slow) for the rest of the objects.
But this is a speculation a bit , as I dunno the details of the whole code base of the project you have.
If you have some flexibility as to how to name those dynamic properties, you could stuff them into a Dictionary property on your object:
public Dictionary<string, dynamic> extra { get; set; }
Goal: to save ViewModel object by Entity Framework. I have UserViewModel object which has list of UnitViewModel. Then, I have a UserAdapter class which converts UserViewModel into Entity Framework User object (see Convert()below how).
Now, my question is how do I convert this list of UnitViewModel to its corresponding Entity Framework Unit list? - Do I have to get each object from DB Context by calling something like context.Units.Where(u=>myListofUnitIDs.Contains(u.UnitID))?
public class UserViewModel
{
public Guid? UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? CreateTime { get; set; }
public List<UnitViewModel> UserUnits { get; set; }
}
public class UnitViewModel
{
public Guid UnitID { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public DateTime CreateTime { get; set; }
public bool Assigned { get; set; }
}
public class UserAdapter
{
public static User Convert(UserViewModel userView)
{
User user;
if (userView.UserID.HasValue)
{
using (var provider = new CoinsDB.UsersProvider())
{
user = provider.GetUser(userView.UserID.Value);
}
}
else
{
user = new User();
}
user.FirstName = userView.FirstName;
user.LastName = user.LastName;
user.Password = StringHelper.GetSHA1(userView.Password);
user.UserName = user.UserName;
user.CreateTime = DateTime.Now;
// Problem here :)
// user.Units = userView.UserUnits;
return user;
}
}
UPDATE: The main concern here is that I have to retrieve each Unit from database to match (or map) it with ViewModel.Unit objects, right? Can I avoid it?
For your information, this operation is called as Mapping mainly. So, you want to map your view model object to the entity object.
For this, you can either use already existed 3rd party library as AutoMapper. It will map properties by reflection which have same name. Also you can add your custom logic with After method. But, this approach has some advantages and disadvantages. Being aware of these disadvantages could help you to decide whether you must use this API or not. So, I suggest you to read some articles about advantages and disadvantages of AutoMapper especially for converting entities to other models. One of such disadvantages is that it can be problem to change the name of one property in the view model in the future, and AutoMapper will not handle this anymore and you won't get any warning about this.
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit = Mapper.Map<UnitViewModel, UserUnit>(item);
user.Units.Add(userUnit);
}
So, I recommend to write your custom mappers.
For example, I have created a custom library for this and it maps objects lik this:
user.Units = userView.UserUnits
.Select(userUnitViewModel => userUnitViewModel.MapTo<UserUnit>())
.ToList();
And I am implementing these mapping functions as:
public class UserUnitMapper:
IMapToNew<UnitViewModel, UserUnit>
{
public UnitViewModel Map(UserUnit source)
{
return new UnitViewModel
{
Name = source.Name,
...
};
}
}
And then in runtime, I am detecting the types of the objects which will be used during mapping, and then call the Map method. In this way, your mappers will be seperated from your action methods. But, if you want it urgently, of course you can use this:
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit= new UserUnit()
{
Name = item.Name,
...
};
user.Units.Add(userUnit);
}