How to partial update entity - aspnetboilerplate

May someone show an example with a partial update entity? All examples into git/docs/stack have only update method, which replaces all fields, even I sending null.
It looks like I need to use HttpPatch and json-patch. But it's so much extra code...
UserEntity
public class User: AbpUser<User>
{
[Required(AllowEmptyStrings = true)]
[StringLength(MaxNameLength)]
public override string Name { get; set; }
[Required(AllowEmptyStrings = true)]
[StringLength(MaxNameLength)]
public override string Surname { get; set; }
public string FatherName { get; set; }
public DateTime BirthDate { get; set; }
public long? DepartmentId { get; set; }
public long? ManagerId { get; set; }
public long? PositionId { get; set; }
public override string FullName => $"{Surname} {Name} {FatherName}";
}
CreateUserDto
{
"name": "Test",
"surname": "Test",
"emailAddress": "test#test.test"
}
UpdateUserDto
{
"id": 1,
"name": "Василий",
"surname": "Пупкин",
"fatherName": "Иванович",
"birthDate": "1993-02-21",
"emailAddress": "vasiliyp#test.test",
"phonenumber": "+79378889911",
"departmentId": 1,
"positionId" : 1
}
Second UpdateUserDto
{
"id": 1,
"name": "Василий",
"surname": "Пупкин",
"fatherName": "Иванович"
}
After the second update I want to get a partial update, but it updates all fields include those I do not send. For example, PositionId will be null, instead of 1.
UserAppService
public override async Task<UserDto> UpdateAsync(UpdateUserDto input)
{
var user = await _userManager.GetUserByIdAsync(input.Id);
MapToEntity(input, user);
return await GetAsync(input);
}
protected override async void MapToEntity(UpdateUserDto input, User user)
{
ObjectMapper.Map(input, user);
user.SetNormalizedNames();
if (!user.DepartmentId.HasValue || user.DepartmentId == 0) return;
var department = await _departmentRepository.GetAsync((long)user.DepartmentId);
user.ManagerId = department.ManagerId;
}
Update
Yesterday I find a way: it needs to customize automapper that to merge models.
Configuration.Modules.AbpAutoMapper().Configurators.Add(
cfg => cfg.ForAllMaps((obj, cnfg) => cnfg.ForAllMembers(
opts => opts.Condition((src, dest, srcMember) => srcMember != null))));
But there are problems:
All value types into DTO must be nullable because the automapper will get default values.
Even though I have defined DateTime as nullable, the automatic conversion converts it to the default (DateTime). I have not yet found a way to fix this without a crutch.
if(input.BirthDate == null) input.BirthDate = user.BirthDate;
UPDATE 2
[AutoMapTo(typeof(User))]
public class UpdateUserDto : EntityDto<long>
{
[StringLength(AbpUserBase.MaxNameLength)]
public string Name { get; set; }
[StringLength(AbpUserBase.MaxSurnameLength)]
public string Surname { get; set; }
public string FatherName { get; set; }
public DateTime? BirthDate { get; set; }
[EmailAddress]
[StringLength(AbpUserBase.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
public long? DepartmentId { get; set; }
public long? PositionId { get; set; }
}

you can create new Dto with specific fields and create new Update function for that.

As you have pointed out, all values in the update DTO must be declared nullable, or it will be assigned default values.
Then you also need to convert nullable type to using destination value instead.
// example for int, double and DateTime
CreateMap<int?, int>().ConvertUsing((src, dest) => src ?? dest);
CreateMap<double?, double>().ConvertUsing((src, dest) => src ?? dest);
CreateMap<DateTime?, DateTime>().ConvertUsing((src, dest) => src ?? dest);
// ignore if null
CreateMap<UpdateDTO, UpdateEntity>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));

Related

Fluent LINQ EF Core - Select filtered child property

I have the classes below:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ParentEntity
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public ICollection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public Guid Id { get; set; }
public int Vote { get; set; }
public Guid UserId { get; set; }
}
public class ReturnedParentDto
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public int Vote { get; set; }
}
I want to be able to return a full list of ParenEntities, but take an Id of the User class (UserClassId), then filter the ParentEntity's ICollection where UserUid = UserClassId, so only 1 ChildEntity is always returned. Then I would want to extract a specific field from that returned ChildEntity and merge it with the ParentEntity fields. The end result should be like the ReturnedParentDto.
I want to do it in the style like
ParentEntities.Include(v => v.ChildEntities).ToList()
That seems to be possible in EF Core 5, but my project is in 3.1.
You can do this as below
Approach 1:
var result = result = parentEntities.Include(x => x.ChildEntities.Where(y => y.UserId == userId))
.Select(x => new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault()?.Vote // userId is the variable here
});
Approach 2:
var result = parentEntities.Select(x =>
new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault(y => y.UserId == userId)?.Vote // userId is the variable here
});

Xamarin MVVM Fill Model with a Model Property

How do work with a model with a model property inside of it?
I am pulling info from an api successfully but it does not work after I try to change my model from int to model like below:
public class TypeModel
{
[PrimaryKey]
public int pType { get; set; }
public DepartmentModel fDepartment { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
Here is the department model
public class DepartmentModel
{
public int pDepartment { get; set; }
public string Name { get; set; }
}
My ViewModel had this code and was working. Been trying to make changes as I think I need to change it in here somehow.
Types.Clear();
IEnumerable<TypesModel> types = await DataSource.GetTypesAsync(typeinfo.pType, true);
foreach (var column in types)
{
Types.Add(column);
}
Here is the deserialization from the api.
IEnumerable<TypeModel> TypeEnumerator;
public async Task<IEnumerable<TypeModel>> GetTypesAsync(bool r = false)
{
if (r)
{
var j = await HttpConstructor.GetStringAsync($"api/gettypes");
return await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<TypeModel>>(j));
}
return TypeEnumerator; ;
}
Here is the json information being produced from the api for types
{
"pType": 10,
"fDepartment": 1,
"title": "Bigwig",
"description": "For the bigwigs",
"comments": "high priority",
"version": "1.2.3"
},
{
"pType": 11,
"fDepartment": 1,
"title": "Frontdesk",
"description": "front end people",
"comments": "none",
"version": "1.2.4"
}
this is what I would do. There are undoubtedly other ways to approach it
public class TypeModel
{
[PrimaryKey]
public int pType { get; set; }
public int fDepartment { get; set; }
public DepartmentModel Department { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
List<TypesModel> types = await DataSource.GetTypesAsync(typeinfo.pType, true);
foreach (var type in types)
{
type.Department = new DepartmentModel
{
pDepartment = type.fDeparment,
Name = "???"
};
}
Got a solution going by using a Dictionary collection and avoided adjusting the model and mess up the business logic throughout the app.
I kept the original model and created a new one called TypesModel to use for list views.
public class TypesModel
{
[PrimaryKey]
public int pType { get; set; }
public Dictionary<int, string> fDepartment { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
Then I used Linq join to combine the information and also fill in the dictionary values.
var query = from t in types
join d in departments
on t.fDeparment equals d.pDepartment
select new TypesModel
{
pType = t.pType,
fDepartment = new Dictionary<int, string>()
{
{ d.pDepartment, d.Name }
},
Title = t.Title,
Description = t.Description
};

Duplicate id generate

I am creating a project using asp.net core web api and cosmos db. I generate the id as GUID value, I auto generate the id.But it create duplicate value.
work.cs file:
public class work
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string name { get; set; }
public List<Industy> Industy { get; set; }
public work()
{
if (Id == null)
{
Id = Guid.NewGuid();
}
else
{
Id = Id;
}
}
}
Industry.cs file:
public class Industy
{
[JsonProperty("Id")]
public Guid Id { get; set; }
[JsonProperty("IdustryId")]
public int IdustryId { get; set; }
[JsonProperty("IdustryName")]
public string IdustryName { get; set; }
public Industy()
{
if (Id == null)
{
Id = Guid.NewGuid();
}
else
{
Id = Id;
}
}
}
output:
> {
> "id": "00000000-0000-0000-0000-000000000000",
> "Name": "string",
> "industy": {
> "id": "00000000-0000-0000-0000-000000000000",
> "IdustryId": 0,
}
> }
If i enter more than one value without id it show me the error ,id is already exist. please help me to fix it.
Mark public Guid Id { get; set; } as nullable in both models:
public Guid? Id { get; set; }
Guid is a struct and value type. This means you have to compare with its default value instead of null or mark it as nullable.
#mexanich has good solution. Still you can try one more approach. If you don’t want to change your property to nullable then Update condition like below. Also there is no need for else block. You can eliminate it.
if (Id == default(Guid))
{
Id = Guid.NewGuid();
}

Can't get object with ReadAsAsync<T>

I have a Product class, which looks like this:
Public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Barcode { get; set; }
public string InnerCode { get; set; }
public virtual ProductUnit ProductUnit { get; set; }
public int? ProductUnitID { get; set; }
public virtual ProductType ProductType { get; set; }
public int? ProductTypeID { get; set; }
}
In ASP.NET Core Web API Service I have a put method which returns OK(product).
The response in postman looks like this:
{
"result": {
"id": 22,
"name": "Bread",
"productType": {
"id": 4,
"name": "Food",
"remarks": null,
"products": []
},
"productTypeID": 4,
"code": "566",
"barcode": "855",
"innerCode": "145522",
"productUnit": {
"id": 4,
"name": "Box",
"remarks": null,
"products": []
},
"productUnitID": 4
},
"id": 592, ---> //probably this
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
I am trying to get Product object as shown below:
var data = await httpResponseMessage.Content.ReadAsAsync<Product>();
But As a result, I get the product object with null properties, except the ID, which is random number and which as I think is the id above the exception in the json response.
What mistake do I have?
So, I believe you are trying to parse result which is the inner object in your case.
In order to parse the whole result you have to create a type for mentioned json, which you can create using https://app.quicktype.io/#l=cs&r=json2csharp.
Classes will be as follows :
public partial class ProductResult
{
[JsonProperty("result")]
public Result Result { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("exception")]
public object Exception { get; set; }
[JsonProperty("status")]
public long Status { get; set; }
[JsonProperty("isCanceled")]
public bool IsCanceled { get; set; }
[JsonProperty("isCompleted")]
public bool IsCompleted { get; set; }
[JsonProperty("isCompletedSuccessfully")]
public bool IsCompletedSuccessfully { get; set; }
[JsonProperty("creationOptions")]
public long CreationOptions { get; set; }
[JsonProperty("asyncState")]
public object AsyncState { get; set; }
[JsonProperty("isFaulted")]
public bool IsFaulted { get; set; }
}
public partial class Result
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("productType")]
public Product ProductType { get; set; }
[JsonProperty("productTypeID")]
public long ProductTypeId { get; set; }
[JsonProperty("code")]
[JsonConverter(typeof(ParseStringConverter))]
public long Code { get; set; }
[JsonProperty("barcode")]
[JsonConverter(typeof(ParseStringConverter))]
public long Barcode { get; set; }
[JsonProperty("innerCode")]
[JsonConverter(typeof(ParseStringConverter))]
public long InnerCode { get; set; }
[JsonProperty("productUnit")]
public Product ProductUnit { get; set; }
[JsonProperty("productUnitID")]
public long ProductUnitId { get; set; }
}
public partial class Product
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("remarks")]
public object Remarks { get; set; }
[JsonProperty("products")]
public List<object> Products { get; set; }
}
now you can use ProductResult as
var data = await httpResponseMessage.Content.ReadAsAsync<ProductResult>();
Update
Another solution is, instead of creating type (class) for full JSON data you can use JObject class and using jsonpath you can select any property or object as follows :
string result = await httpResponseMessage.Content.ReadAsStringAsync();
Product product = JObject.Parse(result).SelectToken("$.result").ToObject<Product>()

How to keep the value of the source when using InjectFrom

By injecting values ​​into my domain object, I would keep the values ​​of some properties.
Example:
Domain model
public class Person
{
public string Name { get; set; }
public Guid ID { get; set; }
public DateTime CreateAt { get; set; }
public string Notes { get; set; }
public IList<string> Tags { get; set; }
}
View Model
public class PersonViewMode
{
public string Name { get; set; }
public Guid ID { get; set; }
public DateTime CreateAt { get; set; }
public string Notes { get; set; }
public IList<string> Tags { get; set; }
public PersonViewMode() { ID = Guid.NewGuid(); } //You should use this value when it is the Target
}
Sample
var p = new Person
{
ID = Guid.NewGuid() //Should be ignored!
,
Name = "Riderman"
,
CreateAt = DateTime.Now
,
Notes = "teste de nota"
,
Tags = new[] {"Tag1", "Tag2", "Tag3"}
};
var pvm = new PersonViewMode();
pvm.InjectFrom(p); //Should use the ID value generated in the class constructor PersonViewMode
if you delete the set; from from the ViewModel's ID then it won't be set;
otherwise you could save the value of ID in a separate variable and put it back after injecting,
or you can create a custom valueinjection that would ignore "ID" or would receive a list of properties to ignore as a parameter
here's the example for a custom injection that receives a list of property names to ignore:
public class MyInj : ConventionInjection
{
private readonly string[] ignores = new string[] { };
public MyInj(params string[] ignores)
{
this.ignores = ignores;
}
protected override bool Match(ConventionInfo c)
{
if (ignores.Contains(c.SourceProp.Name)) return false;
return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Type == c.TargetProp.Type;
}
}
and use it like this:
pvm.InjectFrom(new MyInj("ID"), p);
if you need to ignore more, you can do like this:
pvm.InjectFrom(new MyInj("ID","Prop2","Prop3"), p);

Resources