I am Changing the AutoMapper to Mapster and i am new to mapster.
I have two classes like below
public class PayrollLineItems
{
public int PayrollLineItemID{get;set;}
public datetime PlannedStartDate {get;set;}
public datetime PlannedEndDate {get;set;}
public datetime ActualStartDate {get;set;}
public datetime ActualEndDate {get;set;}
}
public class PayableLineItems
{
public int PayrollLineItemID {get;set;}
public TimePeriod PlannedTimePeriod {get;set;}
public TimePeriod ActualTimePeriod {get;set;}
}
public class TimePeriod
{
private DateTime _Start;
private DateTime _End;
private TimeSpan _ActualDuration;
public TimePeriod(DateTime start, DateTime end)
{
this._Start = start;
this._End = end;
this._ActualDuration = (this._End-this._Start);
}
}
Below is the AutoMapper code to map the PlannedTimePeriod property of
PayrollLineItems and PayableLineItems.
CreateMap<PayrollLineitems, PayableLineItems>()
.ForMember(x => x.PlannedTimePeriod,
option => option.MapFrom<TimePeriod>(
(src, dest) => new TimePeriod(src.PlannedStartTime.HasValue ? src.PlannedStartTime.Value : DateTime.MinValue,
src.PlannedEndTime.HasValue ? src.PlannedEndTime.Value : DateTime.MinValue))
)
I am using the below code in Mapster to map the PlannedTimePeriod property
config
.NewConfig<PayrollLineitems, PayableLineItems>()
.Map(x => x.PlannedTimePeriod, dest => new TimePeriod(dest.PlannedStartTime.HasValue ? dest.PlannedStartTime.Value : DateTime.MinValue,
dest.PlannedEndTime.HasValue ? dest.PlannedEndTime.Value : DateTime.MinValue))
I am getting the "No default constructor for type 'TimePeriod', please use 'ConstructUsing' or 'MapWith'" error with MapSter.
Can anyone please help me with the above issue.
Thanks in advance for helping.
Related
I'm trying to map the following object:
public class DatabaseInfo
{
public virtual DateTime CurrentDateTime { get; set; }
}
with the following map:
public class DatabaseInfoMap : ClassMapping<DatabaseInfo>
{
public DatabaseInfoMap()
{
Property(x => x.CurrentDateTime, map => { map.Formula("(SELECT CURRENT UTC TIMESTAMP AS CurrentDateTime)"); });
}
}
I am getting a validation error, as the Map does not contain a 'Table' definition. Is it not possible to map obejcts without having to make a Table or View?
I'm not sure if this is the right approach for me to get the database time via NHibernate. Any help is appreciated.
Since there is no table mapping as an entity makes no sense (no identity). I suggest:
public class DatabaseInfo
{
public static DatabaseInfo Get(ISession session)
{
return session.CreateSQLQuery("SELECT CURRENT UTC TIMESTAMP AS CurrentDateTime").SetResultTransformer(Transformers.AliasToBean<DatabaseInfo>()).UniqueResult<DatabaseInfo>();
}
public DateTime CurrentDateTime { get; protected set; }
}
Can't you just use:
public class DatabaseInfo
{
public DateTime CurrentDateTime { get; set; } = DateTime.UtcNow;
}
When NH hydrates your object, or a graph containing this object, this property will be set with that default value.
Is there a way in C# to add a class constraint that simply makes sure that the type is either DateTime or Nullable<DateTime>? Or makes sure that they have an == operator that is compatible with DateTime?
Below is a simplified use case where I have a method which I try to re-use between two entitites. In reality it is reused between four different entitites. The all share a "similiar" interface but some have a DateTime? and others have DateTime for their Created-property.
I really just need to make sure that they can compare with a specific DateTime which is fetched by another method GetDateForLastMonth().
Limitations for this is that I cannot change the properties for the entities. Although, I can add new interfaces for them. I cannot change the returned type from GetDateForLastMonth.
public class Context : DbContext
{
public virtual DbSet<OrderEntity> Orders { get; set; }
public virtual DbSet<InvoiceEntity> Invoices { get; set; }
public IEnumerable<T> GetItemsForSomePeriod<T, TDateType>(DbSet<T> dbSet)
where T : class, IHaveACreationDate<TDateType>
where TDateType : DateTime // Does not compile because "Only class or interface could be specified as constraint"
{
DateTime someDate = GetSomeDate();
DateTime someOtherDate = GetSomeOtherDate();
DateTime someThirdDate = GetAThirdDate();
return dbSet
.Where(x => (x.Created == lastMonth || x.Created == someOtherDate || x.Create == someThirdDate)) // Cannot compare because Created and someDate isn't same Type
.ToArray();
}
}
public interface IHaveACreationDate<T>
{
T Created { get;}
}
public class OrderEntity: IHaveACreationDate<DateTime?>
{
public DateTime? Created { get; set; }
}
public class InvoiceEntity : IHaveACreationDate<DateTime>
{
public DateTime Created { get; set; }
}
I have two classes, a ViewModel and a Dto, that are basically identical except that the Dto has a field 'readonly long? Phone;' while the ViewModel has the a property 'string Phone { get; set; }'.
The only way I've found to get AutoMapper to work is by changing the ViewModel Property to a backing property:
public long? Phone { get; set; }
public string PhoneNumberString
{
get
{
var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
return srv.GetFormattedPhoneNumber(Phone);
}
set
{
var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
Phone = srv.GetLongPhoneNumber(value);
}
}
And then in AutoMapper, have a gigantic line to call the constructor:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<MyViewModel, MyDto>()
.ConstructUsing(src => new MyDto(
src.Phone
/* ...Some ~30 other parameters here... */))
.ReverseMap();
});
...There must be a better way to do this? I've tried these:
.ForSourceMember(x => x.PhoneNumberString, opt => opt.DoNotValidate())
and
.ForMember(x => x.PhoneNumberString, opt => opt.Ignore())
and
.ForMember(viewModel => viewModel.Phone, options => options.MapFrom<PhoneNumberResolver>());//PhoneNumberResolver implements IValueResolver<ProspectMaintenanceViewModel, ProspectMaintenanceDto, long?>
Which all give 'Core.DTO.MyDto needs to have a constructor with 0 args or only optional args.' when trying to map, and:
.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => 5))
Which gives 'System.ArgumentException: 'Expression must be writeable' when trying to configure AutoMapper.
Is there some way I can make AutoMapper understand that it can entirely ignore PhoneNumberString (or, even better, some way by which I can make it map long? to string so I don't need the backing property) without having to use the dto's constructor?
Is there any special reason that requires your DTO to not have a default constructor?
I have all my fields as readonly so that I can include a constructor that modifies (e.g. 'Description = description?.Trim();') and validates (e.g. 'if (Phone.HasValue && Phone.ToString().Length != 10) throw ...') the parameters. This way I can ensure that the Dto, being a value object, is always in a valid state.
1) Mapping to readonly field
So you have a Dto class:
public class Dto
{
public readonly long? PhoneNumber;
}
And then you are trying to force AutoMapper to do this:
var dto = new Dto();
dto.PhoneNumber = 123; // <== ERROR! A readonly field cannot be assigned to.
AutoMapper cannot write to readonly fields or properties. In matter of fact you neither. Either turn your field into a property with protected or private setter:
public class Dto
{
public long? PhoneNumber { get; private set; }
}
or make it a regular field by removing the readonly keyword:
public class Dto
{
public long? PhoneNumber;
}
2) Custom value resolving
ASP.NET MVC
Use a ValueResolver:
public class StringPhoneNumberResolver : IValueResolver<Dto, ViewModel, string>
{
private readonly IPhoneNumberService _phoneNumberService;
public StringPhoneNumberResolver()
{
_phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
}
public string Resolve(Dto source, ViewModel destination, string destMember, ResolutionContext context)
{
return _phoneNumberService.GetFormattedPhoneNumber(source.PhoneNumber);
}
}
You should know that generally it is an anti-pattern to have service injection in a DTO or IValueResolver. AutoMapper should be dumb and all kind of injections and so on should be handled elsewhere. That being said, here is the AutoMapper configuration:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Dto, ViewModel>()
.ForMember(viewModel => viewModel.PhoneNumber, options =>
options.MapFrom<StringPhoneNumberResolver>());
});
If you want to reverse the process of long ==> string to string ==> long simply add another value resolver:
public class LongPhoneNumberResolver : IValueResolver<ViewModel, Dto, long?>
{
private readonly IPhoneNumberService _phoneNumberService;
public LongPhoneNumberResolver()
{
_phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
}
public long? Resolve(ViewModel source, Dto destination, long? destMember, ResolutionContext context)
{
return _phoneNumberService.GetLongPhoneNumber(source.PhoneNumber);
}
}
.NET Core
If you would operate in .NET Core environment, which fully supports IServiceCollection integration, you should add this AutoMapper configuration:
serviceCollection.AddAutoMapper(config =>
{
config.CreateMap<Dto, ViewModel>()
.ForMember(viewModel => viewModel.PhoneNumber, options =>
options.MapFrom<StringPhoneNumberResolver>());
}, typeof(Startup));
and then have IPhoneNumberServce automagically injected into value resolver:
public StringPhoneNumberResolver(IPhoneNumberService phoneNumberService)
{
_phoneNumberService = phoneNumberService;
}
For dependency injection I used automapper.extensions.microsoft.dependencyinjection package.
Well, I found the problem. It has absolutely nothing to do with what I thought it did. Mapping map long? to string works out of the box.
The problem I had was with an entirely different property.
I had the following structure:
public class MyDto
{
public readonly AddressDto BillingAddress;
public readonly AddressDto ShippingAddress;
public readonly long? Phone;
...
}
public class AddressDto
{
public readonly string Country;
public readonly string SubnationalEntity;
...
}
public class MyViewModel
{
public string BillingAddressCountry { get; set; }
public string BillingAddressSubnationalEntity { get; set; }
public string ShippingAddressCountry { get; set; }
public string ShippingAddressSubnationalEntity { get; set; }
public string Phone { get; set; }
...
}
It worked once I changed it to the following:
public class MyDto
{
public readonly AddressDto BillingAddress;
public readonly AddressDto ShippingAddress;
public readonly long? Phone;
...
}
public class AddressDto
{
public readonly string Country;
public readonly string SubnationalEntity;
...
}
public class MyViewModel
{
public string AddressViewModel BillingAddress { get; set; }
public string AddressViewModel ShippingAddress { get; set; }
public string Phone { get; set; }
...
}
public class AddressViewModel
{
public string Country { get; set; }
public string SubnationalEntity { get; set; }
...
}
Suppose I have these classes:
private class Model
{
public DateTime FirstDate { get; set; }
public DateTime SecondDate { get; set; }
public DateTime FinalDate { get; set; }
public string Name { get; set; }
}
private class Mommasan : Bandana
{
public DateTime RestDate { get; set; }
public DateTime TurboDate { get; set; }
public string Name { get; set; }
}
All dates need to be filtered up to 3 days. So how can I create a generic filtering method for the DateTime properties? Using IQueryable? I was hoping it would end up something like this:
(Obviously will not compile, but I'm guessing the idea)
void DoSpecificFilter<T>(ref IQueryable<T> query, DateTimeProperty property)
{
DateTime today = today;
query = query.Where(a => property <= today && today <= property.AddDays(3);
}
So if I need to filter Models who are on their final date, something like:
DoSpecificFilter<Model>(ref alreadyFilteredQuery, a => a.FinalDate);
and Mommasans who are on rest:
DoSpecificFilter<Mommasan>(ref alreadyFilteredQuery, a => a.RestDate);
Is this kind of thing possible at all or am I having the entirely wrong idea? Thanks!
You could work with DateTime-Selectors. For multiple DateTime fields you would have to chain them together. It would probably be easier to write an extention method for this:
public static class ExtentionMethods
{
public static IQueryable<T> DoSpecificFilter<T>(
this IQueryable<T> query,
Expression<Func<T, DateTime>> dateSelector,
DateTime filterValue,
bool blnTopLimit)
{
return query.Where(a => (blnTopLimit && dateSelector.Compile()(a) < filterValue)
|| (!blnTopLimit && dateSelector.Compile()(a) > filterValue));
}
}
Then you could use it like this:
var query = queryableCollection
.DoSpecificFilter((a) => a.RestDate, DateTime.Today, false)
.DoSpecificFilter((a) => a.TurboDate, DateTime.Today, true);
Why not write an extension method on DateTime, and not look at the class at all? Something like ...
public static class Extensions
{
public static IQueryable<T> DoSpecificFilter<T>(this DateTime value, IQueryable<T> query)
{
DateTime today = DateTime.Now;
IQueryable<T> ret = query.Where(a => value <= today && today <= value.AddDays(3));
return ret;
}
}
Is it possible to use .CheckProperty when using PersistenceSpecification on a readonly property?
For example, a class with Used as a read only property, with SetAsUsed as a method to set the property:
public class MyClass
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
public virtual DateTime? Used { get; private set; }
public virtual void SetAsUsed()
{
Used = DateTime.Now;
}
}
I'd like to do a persistence spec, something like:
new PersistenceSpecification<ForgotPasswordRequest>(Session)
.CheckProperty(x => x.Code, "a#b.com")
.CheckProperty(x => x.Used, //??
.VerifyTheMappings();
But not sure how I can invoke the SetAsUsed method from here?
Off the top of my head:
new PersistenceSpecification<ForgotPasswordRequest>(Session)
.CheckProperty(x => x.Code, "a#b.com")
.CheckProperty(x => x.Used, DateTime.Now, (entity, DateTime?) => entity.SetAsUsed())
.VerifyTheMappings();