I want the null value during mapping DTO to DBO model to be ignored. This is the code:
DTO / DBO models have both property named items:
public virtual ICollection<price_list_item> items { get; set; }
DBO constructor:
public price_list()
{
this.items = new List<price_list_item>();
}
DTO constructor has no propert initialization
public price_list()
{
}
AutoMapper Profile:
this.CreateMap<DTO.price_list, DBO.price_list>()
.ForMember(m => m.id, src => src.Ignore())
.ForMember(m => m.currency_id, src => src.MapFrom(f => f.currency))
.ForMember(dest => dest.items, opt => opt.Condition(src => (src.items != null)))
API Controller:
[HttpPut]
[Route("{id:long}")]
public async Task<DTO.price_list> UpdateOneAsync(long id, [FromBody]DTO.price_list payload)
{
if (payload == null)
{
throw new ArgumentNullException("payload");
}
Console.WriteLine(payload.items == null);
var _entity = await this.IDataRepository.price_lists
.Where(w => w.id == id)
.Include(i => i.items)
.FirstOrDefaultAsync();
if (_entity == null)
{
NotFound();
return null;
}
Console.WriteLine(_entity.items.Count);
// map fields to existing model
this.IMapper.Map<DTO.price_list, DBO.price_list>(payload, _entity);
Console.WriteLine(_entity.items.Count);
When I send to API a JSON without any sign of 'items' property, Console output is:
True
1200 // price list in dbo has 1200 items
0 // here I need to have still 1200 items
What am I doing wrong? Why the condition is not respected and items property is not 'skiped' ?
Thanks
Lucian thanks, PreCondition solved the problem. This is working code:
this.CreateMap<DTO.price_list, DBO.price_list>()
.ForMember(m => m.id, src => src.Ignore())
.ForMember(m => m.currency_id, src => src.MapFrom(f => f.currency))
.ForMember(dest => dest.items, opt => opt.PreCondition(src => (src.items != null)))
Related
At the moment, in my controller's service method GetSubAccounts(accountId), I have this:
Account account = await context.Accounts.SingleOrDefaultAsync(x => x.Id == accountId);
IQueryable<Account> subAccounts = context.Accounts.Include(x => x.AccountCodes).AsNoTracking();
return await mapper.ProjectTo<SubAccountViewModel>(subAccounts, null, x => x.SubAccounts)
.Where(x => x.PersonId == account.PersonId && x.AccountId != null).ToListAsync();
My SubAccountViewModel is as follows: (note that it has a collection of itself)
public class SubAccountViewModel : Base.AccountViewModel
{
public virtual ICollection<AccountCodeViewModel> AccountCodes { get; set; }
public virtual ICollection<SubAccountViewModel> SubAccounts { get; set; }
}
My mapping profile is:
internal class SubAccountMappingProfile : Profile
{
public SubAccountMappingProfile()
{
CreateMap<Account, SubAccountViewModel>()
.ForMember(x => x.AccountCodes, options => options.ExplicitExpansion())
.ForMember(x => x.SubAccounts, options => options.ExplicitExpansion())
.ReverseMap();
}
}
And this is the JSON I'm getting as a result:
[
{
"id":"c236718f-9d91-4eea-91ee-66760a716343",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":null,
"username":"test same person",
"email":"testsameperson#gmail.com",
"birthDate":"2021-01-02",
"subaccounts":null
}
]
The problem:
I'm getting a top-level array of subaccounts for the accountId parameter I pass to the method. Fine. (There's just one, but nevermind that.)
What I do want is the main account at top-level, with the array of subaccounts as part of it.
I.e.
{
"id":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"username":"test person",
"email":"testperson#gmail.com",
"birthDate":"2021-01-01",
"subaccounts":[
{
"id":"c236718f-9d91-4eea-91ee-66760a716343",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"username":"test same person",
"email":"testsameperson#gmail.com",
"birthDate":"2021-01-02",
"subaccounts":null
}
]
}
How do I do it?
The problem was one of logic.
To start with, my service method (and my API controller) was returning Task<IEnumerable<SubAccountViewModel>>, when it should return Task<SubAccountViewModel>.
Then my solution was:
Account account = await context.Accounts.SingleOrDefaultAsync(x => x.Id == accountId);
IQueryable<Account> accounts = context.Accounts.AsNoTracking();
SubAccountViewModel subAccountViewModel = await mapper.ProjectTo<SubAccountViewModel>(accounts, null, x => x.AccountCodes)
.SingleOrDefaultAsync(x => x.Id == accountId);
subAccountViewModel.SubAccounts = await mapper.ProjectTo<SubAccountViewModel>(accounts, null, x => x.AccountCodes, x => x.SubAccounts)
.Where(x => x.PersonId == account.PersonId && x.AccountId != null).ToListAsync();
return subAccountViewModel;
This returns the resultset I wanted.
AssertConfigurationIsValid Passes, and the object being tried is fully populated, but I get the error on the first Map request called.
I'm trying to map
Survey ToLoad = Mapper.Map<Survey>(U);
I'm initializing automapper with the code below.
//Lots of other Maps
Mapper.Initialize(cfg => cfg.CreateMap<User, SMUser>()
.ForMember(t => t.AccountType, s => s.MapFrom(so => so.AccountType != null ? so.AccountType : String.Empty))
.ForMember(t => t.Username, s => s.MapFrom(so => so.Username != null ? so.Username : String.Empty)));
Mapper.Initialize(cfg => cfg.CreateMap<SurveyMonkey.Containers.Survey, Survey>().ForMember(t => t.AnalyzeUrl, s => s.MapFrom(so => so.AnalyzeUrl != null ? so.AnalyzeUrl : String.Empty))
.ForMember(t => t.Category, s => s.MapFrom(so => so.Category != null ? so.Category : String.Empty))
.ForMember(t => t.CollectUrl, s => s.MapFrom(so => so.CollectUrl != null ? so.CollectUrl : String.Empty))
.ForMember(t => t.EditUrl, s => s.MapFrom(so => so.EditUrl != null ? so.EditUrl : String.Empty))
.ForMember(t => t.Language, s => s.MapFrom(so => so.Language != null ? so.Language : String.Empty))
.ForMember(t => t.Preview, s => s.MapFrom(so => so.Preview != null ? so.Preview : String.Empty))
.ForMember(t => t.SummaryUrl, s => s.MapFrom(so => so.SummaryUrl != null ? so.SummaryUrl : String.Empty))
.ForMember(t => t.Title, s => s.MapFrom(so => so.Title != null ? so.Title : String.Empty))
//Some more members
);
//LISTS
Mapper.Initialize(cfg => cfg.CreateMap<List<SurveyMonkey.Containers.Collector>, List<Collector>>());
//Lots of other List Maps
I'm using the latest Stable version from Nuget (5.2.0).
Only call Mapper.Initialize once with the whole configuration, or you will overwrite it.
You can wrap the configuration in a class that inherits AutoMapper.Profile:
using AutoMapper;
public class MyAutoMapperProfile : Profile {
protected override void Configure() {
CreateMap<User, SMUser>();
CreateMap<SurveyMonkey.Containers.Survey, Survey>();
CreateMap<List<SurveyMonkey.Containers.Collector>, List<Collector>>();
}
}
Then initialize the Mapper using this Profile:
Mapper.Initialize(cfg => {
cfg.AddProfile<MyAutoMapperProfile>();
cfg.AddProfile<OtherAutoMapperProfile>();
});
AutoMapper Configuration
I got same error and in my startup class in ConfigureServices methode I use
services.AddAutoMapper(typeof(startup)); => startup class.
Because of that my automapper profile class (the class that inherit from Automapper.Propfle, in this case
public class MyAutoMapperProfile : Profile) not getting read.
To fix this I have replace startup class with the MyAutoMapperProfile like below
services.AddAutoMapper(typeof(MyAutoMapperProfile));.
you can debug and check whether your automapper mapping class getting hit or not.
In my data model, I have a base class Case for which there can be a few different types: an Investigation, DisciplinaryAction, etc. These all inherit from Case. This is how I've modeled it in Entity Framework.
public abstract class Case : BaseEntity
{
public int CaseId { get; set; }
public int PlanId { get; set; }
public int CaseTypeId { get; set; }
public CaseType CaseType { get; set; }
}
public class Investigation : Case
{
}
For the inherited classes, CaseId is the primary key and foreign key. I have a single DbSet for Cases. When I need to explicitly access one of the inherited types, I use context.Cases.OfType<Investigation>(). All this is working fine.
In my CaseRepository, I have a method to get a case by ID:
public Case GetById(int id)
{
var oversightCase = context.Cases
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.FirstOrDefault(f => f.CaseId == id);
}
My question is that, for one of the inherited types, I want to include another navigation property. Is there a way to access inherited types generically and include properties that not all inherited types have in a manner that's less disgusting than this:
public Case GetById(int id)
{
var oversightCase = context.Cases
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.Include(p => p.LinkedObjects)
.FirstOrDefault(f => f.CaseId == id);
if (oversightCase != null && oversightCase is Investigation)
{
oversightCase = context.Cases.OfType<Investigation>()
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.Include(p => p.LinkedObjects)
.Include(p => p.Investigatee)
.FirstOrDefault(f => f.CaseId == id);
}
return oversightCase;
}
Yes, you will need to do two requests when the case id is related to Investigation. Doing it like you write it, is not the correct way.
Because if the id is related to an Investigation you are doing two big requests to your database with each one doing the same first four join queries.
The second request (in your if statement block) is just an extension of your first request because they share this code:
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.Include(p => p.LinkedObjects)
To avoid performance issue in the future:
First create a generic method like the code below where T must be a subclass of Case:
public IQueryable<T> GetCaseCommonData<T>()
where T : Case
{
return context.Cases.OfType<T>
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.Include(p => p.LinkedObjects);
}
Second, use the generic method like this:
public Case GetById(int id)
{
Case oversightCase;
if (context.Cases.OfType<Investigation>().Any(f => f.CaseId == id))
{
oversightCase = GetCaseCommonData<Investigation>()
.Include(p => p.Investigatee)
.SingleOrDefault(f => f.CaseId == id);
}
else
{
oversightCase = GetCaseCommonData<Case>()
.SingleOrDefault(f => f.CaseId == id);
}
return oversightCase;
}
With my solution, the include like the ocde below are executed once:
.Include(p => p.Owner)
.Include(p => p.CreatedBy)
.Include(p => p.ModifiedBy)
.Include(p => p.LinkedObjects);
By refactoring your code like I do, the query to check if a case id is an Investigation type is more efficient than the code you write for the same purpose:
context.Cases.OfType<Investigation>().Any(f => f.CaseId == id)
I'm testing a PUT with two string:
company.CurrencyCode = request.CurrencyCode ?? company.CurrencyCode;
company.CountryIso2 = request.Country ?? company.CountryIso2;
and I tried with a rule like:
public UpdateCompanyValidator()
{
RuleSet(ApplyTo.Put, () =>
{
RuleFor(r => r.CountryIso2)
.Length(2)
.When(x => !x.Equals(null));
RuleFor(r => r.CurrencyCode)
.Length(3)
.When(x => !x.Equals(null));
});
}
as I don't mind to get a null on those properties, but I would like to test the Length when the property is not a null.
What is the best way to apply rules when a property is nullable and we just want to test if it's not null?
One of the ways would be:
public class ModelValidation : AbstractValidator<Model>
{
public ModelValidation()
{
RuleFor(x => x.Country).Must(x => x == null || x.Length >= 2);
}
}
I prefer the following syntax:
When(m => m.CountryIso2 != null,
() => {
RuleFor(m => m.CountryIso2)
.Length(2);
);
Best syntax for me:
RuleFor(t => t.DocumentName)
.NotEmpty()
.WithMessage("message")
.DependentRules(() =>
{
RuleFor(d2 => d2.DocumentName).MaximumLength(200)
.WithMessage(string.Format(stringLocalizer[""message""], 200));
});
I have relations one to many in DB ( i used entity interface to generate associations between 2 obj)
Im getting error:
Bound columns require a field or property access expression
My code:
in view:
Html.Telerik()
.Grid<Y.Orders.Models.Orders>("orders")
.Name("Orders")
.DataKeys(dataKeys => dataKeys.Add(o => o.Id))
//.Pageable(paging => paging.PageSize(20).Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric))
.Columns(columns =>
{
columns.Command(commands =>
{
commands.Custom("comments").ButtonType(GridButtonType.Text).Text("Szczegóły").Action("Index", "Komentarze");
});
columns.Bound(o => o.Tytul).Title("Title");
columns.Bound(o => o.Deliveries.Sum(m=>m.deliveryTime)).Title("Time of order");
})
.Filterable()
.Sortable(sort =>
{
sort.SortMode(GridSortMode.MultipleColumn); sort.OrderBy(ord =>
{
ord.Add(o => o.Time).Descending();
});
})
.Groupable(grouping => grouping.Groups(groups =>
{
groups.Add(c => c.Users.Firms.Name);
}))
.Render();
in entity model:
public ObjectSet<Deliveries> Deliveries
{
get
{
if ((_ Deliveries
== null))
{
_Deliveries = base.CreateObjectSet<Deliveries>("Deliveries");
}
return _Deliveries;
}
}
private ObjectSet<Deliveries> _Deliveries;
In deliveries i havnt any null.
Where is problem ?
columns.Bound accept only primitive as int or string.
You can't aggregate or transform object as your example :
o.Deliveries.Sum(m=>m.deliveryTime)
You do create in Deliveries object a parameter as this :
public int SumDeliveries
{
get { return this.Sum(m=>m.deliveryTime); }
}
and so you can bind this on grid.