Re-using mappings in automapper - automapper

My existing solution has a lot of repetition like so:
config.CreateMap<IPublishedContent, ContactListingPage>()
.ForMember(n => n.Title, map => map.ResolveUsing(AppearanceManager.GetTitle));
config.CreateMap<IPublishedContent, NewsListingPage>()
.ForMember(n => n.Title, map => map.ResolveUsing(AppearanceManager.GetTitle));
Is there a way to remove this repetition?
I tried: using an interface:
config.CreateMap<IPublishedContent, IHaveTitle>()
.ForMember(n => n.Title, map => map.ResolveUsing(AppearanceManager.GetTitle));
But the actual map operations need to be to the concrete types, so this mapping is not used.
Note: I'm hoping to mix and match the mappings, so all pages have titles, and some pages have promotions, etc...
I tried:
Creating a custom type for title with implicit conversions to string, I think this 'would' work except for the fact that I actually need the IPublishedContent object as the input into the GetTitle method rather than the sub property PublishedProperty which it would be mapped from.

AutoMapper doesn't try to reuse mappings by design - mostly because I would confuse myself in these cases. You opt in to shared mappings:
Mapper.Initialize(cfg => {
cfg.CreateMap<IPublishedContent, IHaveTitle>()
.ForMember(n => n.Title, map => map.ResolveUsing(AppearanceManager.GetTitle));
cfg.CreateMap<IPublishedContent, ContactListingPage>()
.IncludeBase<IPublishedContent, IHaveTitle>();
cfg.CreateMap<IPublishedContent, NewsListingPage>()
.IncludeBase<IPublishedContent, IHaveTitle>();
});
I'm usually pretty careful about these because I don't necessarily want to introduce coupling on the ViewModel side. I've already removed the duplication through a shared resolver.

Related

Use JOOQ Multiset with custom RecordMapper - How to create Field<List<String>>?

Suppose I have two tables USER_GROUP and USER_GROUP_DATASOURCE. I have a classic relation where one userGroup can have multiple dataSources and one DataSource simply is a String.
Due to some reasons, I have a custom RecordMapper creating a Java UserGroup POJO. (Mainly compatibility with the other code in the codebase, always being explicit on whats happening). This mapper sometimes creates simply POJOs containing data only from the USER_GROUP table, sometimes also the left joined dataSources.
Currently, I am trying to write the Multiset query along with the custom record mapper. My query thus far looks like this:
List<UserGroup> = ctx
.select(
asterisk(),
multiset(select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(new UserGroupMapper()))
Now my question is: How to create the UserGroupMapper? I am stuck right here:
public class UserGroupMapper implements RecordMapper<Record, UserGroup> {
#Override
public UserGroup map(Record rec) {
UserGroup grp = new UserGroup(rec.getValue(USER_GROUP.ID),
rec.getValue(USER_GROUP.NAME),
rec.getValue(USER_GROUP.DESCRIPTION)
javaParseTags(USER_GROUP.TAGS)
);
// Convention: if we have an additional field "datasources", we assume it to be a list of dataSources to be filled in
if (rec.indexOf("datasources") >= 0) {
// How to make `rec.getValue` return my List<String>????
List<String> dataSources = ?????
grp.dataSources.addAll(dataSources);
}
}
My guess is to have something like List<String> dataSources = rec.getValue(..) where I pass in a Field<List<String>> but I have no clue how I could create such Field<List<String>> with something like DSL.field().
How to get a type safe reference to your field from your RecordMapper
There are mostly two ways to do this:
Keep a reference to your multiset() field definition somewhere, and reuse that. Keep in mind that every jOOQ query is a dynamic SQL query, so you can use this feature of jOOQ to assign arbitrary query fragments to local variables (or return them from methods), in order to improve code reuse
You can just raw type cast the value, and not care about type safety. It's always an option, evne if not the cleanest one.
How to improve your query
Unless you're re-using that RecordMapper several times for different types of queries, why not do use Java's type inference instead? The main reason why you're not getting type information in your output is because of your asterisk() usage. But what if you did this instead:
List<UserGroup> = ctx
.select(
USER_GROUP, // Instead of asterisk()
multiset(
select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(r -> {
UserGroupRecord ug = r.value1();
List<String> list = r.value2(); // Type information available now
// ...
})
There are other ways than the above, which is using jOOQ 3.17+'s support for Table as SelectField. E.g. in jOOQ 3.16+, you can use row(USER_GROUP.fields()).
The important part is that you avoid the asterisk() expression, which removes type safety. You could even convert the USER_GROUP to your UserGroup type using USER_GROUP.convertFrom(r -> ...) when you project it:
List<UserGroup> = ctx
.select(
USER_GROUP.convertFrom(r -> ...),
// ...

AutoMapper member-level transform not applied to destination value

Quick question about member-level value transformers. I have a string property mapped to a decimal property. I need to scale and round the destination value after it is mapped. I thought a value transformer would be the appropriate way to do this.
CreateMap<Source, Target>()
.ForMember(dest => dest.Rate, opts =>
{
opts.MapFrom(src => src.Rate);
opts.AddTransform(x => Math.Round(x * 100M, 2));
});
The value transformer never seems to be applied to the destination value (see fiddle, below for example)
I could also do
.ForMember(dest => dest.Rate, opts => opts.MapFrom(src => Math.Round(decimal.Parse(src.Rate) * 100, 2)));
but I'd prefer the member transform approach if possible as it uses AutoMapper's built-in string to decimal mapping convention not to mention it's a bit easier to read.
Am I misusing this feature? Any help or advice will be greatly appreciated!
Full example:
https://dotnetfiddle.net/PxoDGY
Thanks for taking a look.

How to access the data stored in this object?

I'm using BioPerl module to obtain a string from a set of parameters. I followed the HOWTO:Beginners page. The module apparently returns a hash object. How do I get the actual string out of the hash object?
use Bio::DB::GenBank;
use Data::Dumper;
my $gb = Bio::DB::GenBank->new(-format => 'Fasta',
-seq_start => 1,
-seq_stop => 251,
-strand => 1
-complexity => 1);
my $seq = $gb->get_Seq_by_acc('NG_016346');
my $sequence_string = lc($seq->seq());
my $seq_obj = Bio::Seq->new(-seq => $sequence_string,
-alphabet => 'dna' );
my $prot_obj = $seq_obj->translate;
print Dumper($prot_obj);
The data dumper prints the following:
$VAR1 = bless( {
'primary_seq' => bless( {
'length' => 83,
'_root_verbose' => 0,
'_nowarnonempty' => undef,
'seq' => 'RLCVKEGPWPAVEGTWSWG*HRPGSRACPRWGAPNSVQATSYTPSPTHAPFSVSPIPIC*MSLLEASCWPGSREDGARMSAGM',
'alphabet' => 'protein'
}, 'Bio::PrimarySeq' ),
'_root_verbose' => 0
}, 'Bio::Seq' );
How do I obtain 'seq' that is stored in $prot_obj?
I tried
print $prot_obj{'primary_seq'}{'seq'};
but it doesn't print anything. Data dumper printed the word bless. Maybe seq is a field of an object oriented variable.
The correct format for accessing object properties uses ->:
print $prot_obj->{'primary_seq'}->{'seq'};
I'm going to dispute the other answer, and say - the correct way to access object properties is not to do so, and use a method instead.
The reason for doing this is the whole point of OO. Which is to encapsulate chunks of your program, such that multiple developers can work with it concurrently, and the code scales because you can find where things are going wrong more easily.
This only works if you used published methods - the specified way of driving the object - because then you don't have to know what's going on behind the scenes. It also means the implementor is free to change what is going on - maybe simply validating, but maybe overloading or having different responses depending on another property within the object.
All this is subverted by direct access to object properties.
You shouldn't do it, even if perl will "let" you. Let's face it, perl will let you do many bad things.
Bio::PrimarySeq has a method call of seq. to retrieve the seq() attribute. Bio::Seq has an accessor for the primary sequence:
So:
$prot_obj -> seq();
I think would probably do it. (Although, the doc isn't exactly easy reading).
There is an accepted answer but I would also advise against poking around in the intervals of objects like that with the only exception being to see what kind of object is returned (or just use ref). Here is how I would approach the problem:
use 5.010;
use strict;
use warnings;
use Bio::DB::GenBank;
use Bio::Seq;
my $gb = Bio::DB::GenBank->new(
-format => 'Fasta',
-seq_start => 1,
-seq_stop => 251,
-strand => 1,
-complexity => 1
);
my $seq = $gb->get_Seq_by_acc('NG_016346');
my $seq_obj = Bio::Seq->new(
-id => $seq->id,
-seq => $seq->seq,
-alphabet => 'dna'
);
say join "\n", ">".$seq_obj->id, $seq_obj->translate->seq;
Running this gives you the translated FASTA record:
>gi|283837914:1-251
RLCVKEGPWPAVEGTWSWG*HRPGSRACPRWGAPNSVQATSYTPSPTHAPFSVSPIPIC*MSLLEASCWPGSREDGARMSAGM
The real benefit of using BioPerl is in combining the different classes together to solve problems with minimal (but also readable and reusable) code. There was also a minor typo in your code that would have been caught with strict and warnings pragmas enabled (that is my best advice).

How to remove default mapping of index without deleting its data of elasticsearch

I have some documents in Couch-base in which some field values are having space in it ("Ex. "New York").
I am not able to search it as exact match using term query.
I found that for searching the field values which are have space require some mapping like - index : not_analysed
and I have done the same
But when I am doing XDCR from couch base to elastic search .Couch base itself creates default mapping.Because of which I am not getting proper result.
So, is there any way by which we can delete default mapping without deleting its data?
Please refer the below code.
I have created and mapped index with dynamic template and then did the XDCR.
Now all string fields will be not_analysed.
IIndicesOperationResponse result = null;
if (!objElasticClient.IndexExists(elastic_indexname).Exists)
{
result = objElasticClient.CreateIndex(elastic_indexname, c => c.AddMapping<dynamic>(m => m.Type("_default_").DynamicTemplates(t => t
.Add(f => f.Name("string_fields").Match("*").MatchMappingType("string").Mapping(ma => ma
.String(s => s.Index(FieldIndexOption.NotAnalyzed)))))));
}
Technically you can change the mappings (check out the mappings API) but in practice I've found it more or less impossible because of conflicts with the existing mappings.
So:
1) Create mappings before indexing any data
2) Apply some of the ideas from this blog post: https://www.elastic.co/blog/changing-mapping-with-zero-downtime
I have got the solution
Here I have created and mapped index with dynamic template and then did the XDCR. Now all string fields will be not_analysed.It worked for me.
IIndicesOperationResponse result = null;
if (!objElasticClient.IndexExists(elastic_indexname).Exists)
{
result = objElasticClient.CreateIndex(elastic_indexname, c => c.AddMapping<dynamic>(m => m.Type("_default_").DynamicTemplates(t => t
.Add(f => f.Name("string_fields").Match("*").MatchMappingType("string").Mapping(ma => ma
.String(s => s.Index(FieldIndexOption.NotAnalyzed)))))));
}
Thanks
Mukesh Raghuwanshi

Can't listen to ItemChanged, ItemsAdded or ItemsRemoved on ReactiveList<T>

I have a ReactiveObject with a Property "A" of type "ReactiveList".
In my ViewModel I'd like to sum a property of "T" of every item in my list.
I got it working, while "A" not changes its value.
But all gets "out of sync", when I assign a new value to "A" (e.g. this.A = new ReactiveList();).
Does anyone have an idea how to solve this? I hope I explained my problem clear enough.
Instead of listening to A directly, listen to this:
this.WhenAnyObservable(x => x.A.ItemsChanged).Subscribe(...);
Whenever A changes, you'll resubscribe to A. Now, knowing when to reset to do a Sum, that's a bit more tricky. Here's the lazy yet more fool-proof way to do this:
Observable.Merge(
this.WhenAny(x => x.A, _ => Unit.Default),
this.WhenAnyObservable(x => x.Changed).Select(_ => Unit.Default))
.Select(_ => this.A.Sum(x => x.SomePropOnTheItem))
.DistinctUntilChanged()
.Subscribe(x => Console.WriteLine("Latest sum is {0}", x);
1 - I'd avoid assigning new Collections continuously as part of your logic, and rather use Clear(). Which should have the same impact in terms of GC.
2- If absolutely necessary, utilize a SerialDisposable and subsribe on the main object A
this.ObservableForProperty(p=> p.A).Subscribe(newAvalue=>{
someSerialDisposable.Disposable=
newAvalue.Changed.Subscribe(_=>this.Result=this.A.Sum(x=>x.T))
});
This would dispose previous deep subscriptions on A instances.

Resources