Open ravenyue opened 2 years ago
We're experiencing the same issue on our end. I've followed the execution and figured out why this is happening.
Essentially, why we're experiencing this is because the destination type fails this check on ClassMapper.CanMap
protected override bool CanMap(PreCompileArgument arg)
{
return arg.ExplicitMapping || arg.DestinationType.IsPoco();
}
Since we don't have an ExplicitMapping defined for this convertion and IsPoco here is returning false here since none of the field satisfy this condition:
return type.GetFieldsAndProperties().Any(it => (it.SetterModifier & (AccessModifier.Public | AccessModifier.NonPublic)) != 0);
Then it falls back to using PrimitiveAdapter which will eventually fails.
The workaround here is to define an explicit mapping, see here for more details: https://github.com/MapsterMapper/Mapster/wiki/Config-validation-&-compilation#explicit-mapping
We're experiencing the same issue on our end. I've followed the execution and figured out why this is happening.
Essentially, why we're experiencing this is because the destination type fails this check on ClassMapper.CanMap
protected override bool CanMap(PreCompileArgument arg) { return arg.ExplicitMapping || arg.DestinationType.IsPoco(); }
Since we don't have an ExplicitMapping defined for this convertion and IsPoco here is returning false here since none of the field satisfy this condition:
return type.GetFieldsAndProperties().Any(it => (it.SetterModifier & (AccessModifier.Public | AccessModifier.NonPublic)) != 0);
Then it falls back to using PrimitiveAdapter which will eventually fails.
The workaround here is to define an explicit mapping, see here for more details: https://github.com/MapsterMapper/Mapster/wiki/Config-validation-&-compilation#explicit-mapping
Thanks for this, I will give it a try and report my results. My model is below:
public class BookDomain : Entity<Guid>
{
private string _title;
private string _subTitle;
private DateTime _publishedDate;
// Constructor
public BookDomain() { }
public string Title => this._title;
public string SubTitle => this._subTitle;
public DateTime PublishedDate => this._publishedDate;
}
public abstract partial class Entity<TKey> : IGeneratesDomainEvents, IEntity<TKey>, IEntity
where TKey : IComparable<TKey>, IEquatable<TKey>
{
private readonly ConcurrentQueue<IDomainEvent> _domainEvents = new();
private DateTime _createdAt;
private DateTime _deactivatedAt;
private DateTime _deletedAt;
private DateTime _modifiedAt;
public abstract object[] GetKeys();
public TKey? Id { get; }
public bool IsActive { get; }
public bool IsDeactivated { get; }
public bool IsSoftDeleted { get; }
public bool IsDeactivatedAndDeleted { get; }
public bool HasPrimaryKey => this.IsTransient.IsFalse();
public abstract bool IsTransient { get; }
public DateTime? SoftDeletedAtUtc =>
this._deletedAt.Equals(default) ? default : this._deletedAt;
public DateTime CreatedAtUtc => this._createdAt.Equals(default) ? default : this._createdAt;
public DateTime? DeactivatedAtUtc =>
this._deactivatedAt.Equals(default) ? default : this._deactivatedAt;
public DateTime ModifiedAtUtc => this._modifiedAt.Equals(default) ? default : this._modifiedAt;
public TKey CreatedByUserId { get; }
public TKey? DeletedByUserId { get; }
public TKey? DeactivatedByUserId { get; }
public TKey? ModifiedByUserId { get; }
public void MarkCreate(TKey createdByUserId, DateTime? utcDateTime = null) { }
public void MarkDeactivate(TKey deactivatedByUserId, DateTime? utcDateTime = null) { }
public void MarkSoftDelete(TKey deletedByUserId, DateTime? utcDateTime = null) { }
public void MarkModified(TKey modifiedByUserId, DateTime? utcDateTime = null) { }
public void ClearDomainEvents()
{
this._domainEvents.Clear();
}
public int GetRaisedDomainEventCount()
{
return this._domainEvents.Count;
}
public IEnumerable<IDomainEvent> GetAllDomainEvents()
{
return this._domainEvents;
}
public async IAsyncEnumerable<IDomainEvent> GetAllDomainEventsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
await foreach (
IDomainEvent domainEvent in this._domainEvents
.ToAsyncEnumerable()
.WithCancellation(cancellationToken)
)
{
yield return domainEvent;
}
}
protected internal virtual void RaiseDomainEvent(IDomainEvent domainEvent)
{
this._domainEvents.Enqueue(domainEvent);
}
}
My mapping config:
public static void RegisterMappers(IServiceCollection services)
{
services.AddSingleton(GetConfiguredMappingConfig());
services.AddScoped<IMapper, ServiceMapper>();
}
private static TypeAdapterConfig GetConfiguredMappingConfig()
{
TypeAdapterConfig config = new();
config.NewConfig<Book, BookDomain>()
// My expectation was that invoking this method below would
// result in .IsPoco() == true, however this is not the case
.EnableNonPublicMembers(true);
return config;
}
@samuelcadieux - do you find it strange that .EnableNonPublicMembers(true);
is not working?
The documentation is explicit that it is for Non-Public Members
The definition of Members from Microsoft can be found here
Screenshot:
Update ^^
I changed the class to use only private properties with getter and setter's and the issue persisted.
The workaround here is to define an explicit mapping, see here for more details: https://github.com/MapsterMapper/Mapster/wiki/Config-validation-&-compilation#explicit-mapping
I changed my code back to the original (private fields) and had success with this:
// Success
TypeAdapterConfig<Book, BookDomain>.NewConfig()
.Map("_id", "Id");
Obviously I have to map it for all of the values.
// Success
TypeAdapterConfig<Book, BookDomain>.NewConfig()
.EnableNonPublicMembers(true);
Mapping a class with only readonly properties throws an exception
Throw System.InvalidOperationException: Cannot convert immutable type, please consider using 'MapWith' method to create mapping
Works fine when containing non-read-only properties
Is this a feature or a bug, am I using it in the wrong way?