Automapper help mapping a complex c# objects - c#

I am trying to map from Vehicle object to the Motor object using Automapper
public class Range<T>
{
public T Min { get; set; }
public T Max { get; set; }
}
public Enum SpeedType
{
[Display(Name = "-")] Unknown = 0,
[Display(Name = "M")] Manual= 1,
[Display(Name = "A")] Automatic= 2
}
public class Vehicle
{
public Range<string> Speed { get; set; }
}
public class Motor
{
public Range<SpeedType?> Speed { get; set; }
}
I have tried using the MapFrom (reading the documentation) without any success. Can someone point me in the right direction. I am not even sure if this can be even mapped using Automapper. I have used automapper in the past for simple mappings.

This works for me:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<string, SpeedType?>().ConvertUsing(speed =>
{
switch (speed)
{
case "M": return SpeedType.Manual;
case "A": return SpeedType.Automatic;
default: return SpeedType.Unknown;
}
});
cfg.CreateMap<Range<string>, Range<SpeedType?>>();
cfg.CreateMap<Vehicle, Motor>();
});
var vehicle = new Vehicle
{
Speed = new Range<string>
{
Min = "M",
Max = "A"
}
};
var motor = Mapper.Map<Vehicle, Motor>(vehicle);

I came up with the following mapping to solve my problem. I wrote a GetEnum custom method
CreateMap<Vehicle,Motor>()
.ForMember(g => g.Speed, opt => opt.MapFrom(u=> new Range<SpeedType?>
{
Min = EnumHelper.GetEnum<SpeedType?>(u.Speed.Min),
Max = EnumHelper.GetEnum<SpeedType?>(u.Speed.Max),
}))

Related

ML.NET AUC is not defined when there is no positive class in the data Arg_ParamName_Name

I want to use Random Forest method in my project.
I have this error trying to use EvaluateNonCalibrated:
System.ArgumentOutOfRangeException: „AUC is not defined when there is
no positive class in the data Arg_ParamName_Name”
var testSetTransform = _trainedModel.Transform(_dataSplit.TestSet);
return MlContext.BinaryClassification.EvaluateNonCalibrated(testSetTransform);
Here you have models:
public class MLCategoryPrediciton
{
public bool PredictedLabel { get; set; }
}
public class MLFinancialChange
{
[LoadColumn(1)]
public bool Label { get; set; }
[LoadColumn(2)]
public float Value { get; set; }
[LoadColumn(3)]
public float CategoryId { get; set; }
}
And way i preparing data:
public async Task FitAsync()
{
var list = await fcRepository.FindAllAsync();
var output = new List<MLFinancialChange>();
foreach(var item in list)
{
var x = new MLFinancialChange
{
Value = item.Value,
CategoryId = item.Category.Id,
};
output.Add(x);
}
IDataView data = MlContext.Data.LoadFromEnumerable<MLFinancialChange>(output);
DataOperationsCatalog.TrainTestData dataSplit = MlContext.Data.TrainTestSplit(data);
_dataSplit = dataSplit;
var dpp = BuildDataProcessingPipeline();
var tp = dpp.Append(_model);
_trainedModel = tp.Fit(_dataSplit.TrainSet);
}
private EstimatorChain<NormalizingTransformer> BuildDataProcessingPipeline()
{
var dataProcessPipeline = MlContext.Transforms.Concatenate("Features",
nameof(MLFinancialChange.Value),
nameof(MLFinancialChange.CategoryId)
)
.Append(MlContext.Transforms.NormalizeMinMax("Features", "Features"))
.AppendCacheCheckpoint(MlContext);
return dataProcessPipeline;
}
Thanks for help
I just want that to work and i tried to find solution in the internet. Unfortunatelly i spent a lot of time trying fix it.
I've had this problem before, and I think it was because the model wasn't predicting any positive cases. You can try inspecting the predictions and just make sure it's not predicting everything as negative.

Automapper map child object based on parent value

If the header object has a prop set to 1 then it should map field type1 in the child to type in the destination. Otherwise it should use type2.
Bonus points if I can use IValueResolver to use type1 or type1extended if extended is filled.
Here is my minimum viable product/demo
using AutoMapper;
using AutoMapper.Configuration.Conventions;
using System;
using System.Collections.Generic;
namespace ConsoleAppAutoMapper
{
class Program
{
static void Main(string[] args)
{
var source = new SourceParent() {
Header = new SourceHeader() { Currency = 30, FileName = "testfile.txt", Type = 1 },
Rows = new List<SourceRow>() {
new SourceRow() { ID = 1, Amount1 = 100, Amount2 = 200 },
new SourceRow() { ID = 2, Amount1 = 101, Amount2 = 201 },
new SourceRow() { ID = 3, Amount1 = 102, Amount2 = 202 }
} };
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceParent, DestinationParent>();
cfg.CreateMap<SourceRow, DestinationRow>()
.ForMember(x => x.Type, opt => opt.MapFrom(p => p.Type1));
});
var mapper = config.CreateMapper();
var dest = mapper.Map<DestinationParent>(source);
Console.WriteLine(dest.Rows[0].Type == 100); // should be true if SourceHeader.Type = 1 and should be 200 (SourceRow.Type2) if SourceHeader.Type = 2
Console.ReadKey();
}
}
// source
public class SourceParent
{
public SourceHeader Header { get; set; }
public List<SourceRow> Rows { get; set; }
}
public class SourceHeader
{
public string FileName { get; set; }
public int Type { get; set; }
}
public class SourceRow
{
public int ID { get; set; }
public int Amount1 { get; set; }
public int Amount2 { get; set; }
}
//destination
public class DestinationParent
{
public DestinationHeader Header { get; set; }
public List<DestinationRow> Rows { get; set; }
}
public class DestinationHeader
{
public string FileName { get; set; }
}
public class DestinationRow
{
public int ID { get; set; }
public int Type { get; set; }
public int Amount{ get; set; } // if type=1 then source is amount1 otherwise amount2
}
}
edit
I tried to solve it by having an Aftermap on the sourceparent mapping which took the value from the header and put it in a prop from the destinationrow (it is the Type value) and wanted another aftermap on the row to see if I needed prop A or B (type1 or type2) but that aftermap still does not know (it's null) what type it is because it happens before the aftermap of the parent it seems.
public class MapRowType : IMappingAction<SourceParent, DestinationParent>
{
public void Process(SourceParentsource, DestinationParent destination)
{
foreach (var row in destination.Rows)
{
row.Type = source.Header.Type; // so now I have type in the row, but still do not know if I should use Amount1 or Amount2
}
}
}
you can use the resolution context. Declare the mapping:
cfg.CreateMap<SourceRow, DestinationRow>()
.ForMember(x => x.Type,
opt => opt.ResolveUsing((src, dest1, destMember, resContext) => resContext.Items["Type"] as int? == 1? src.Type2: src.Type1));
After pass the value:
var dest = mapper.Map<DestinationParent>(source, opts=> { opts.Items["Type"] = source.Header.Type;});

How can I use LINQ to retrieve entries that contain more than one property of a list within a list?

I have a List of IJapaneseDictionaryEntry objects which are described below. Inside this are IKanji objects that contain Priorites objects.
I have a rather difficult thing I would like to do and would appreciate any advice / suggestions. What I would like to do is to retrieve entries that have an entry that have Priority of "Frequency1" have Priority of "Frequency2" or Priority of "Frequency3" from the list entries that I created.
public interface IJapaneseDictionaryEntry
{
int Sequence { get; }
IEnumerable<IKanji> Kanjis { get; }
IEnumerable<IReading> Readings { get; }
IEnumerable<ISense> Senses { get; }
}
Where each object contains a list of IKanji objects
public interface IKanji
{
string Text { get; }
IEnumerable<KanjiInformation> Informations { get; }
IEnumerable<Priority> Priorities { get; }
}
Here's the list:
List<IJapaneseDictionaryEntry> entries = dictionary.GetEntries().ToList();
Here's a view that I think might help explain the contents:
I hope the information here is enough as it seems difficult to explain what I need to retrieve.
var result = entries.Where(e => e.Kanjis.Any(k => k.Priorities.Contains(Priority.Frequency1) ||
k.Priorities.Contains(Priority.Frequency2) ||
k.Priorities.Contains(Priority.Frequency3)
)).ToList();
Considering your 2 questions, I would have made something like this:
[Flags]
public enum Priority
{
Frequency1 = 1,
Frequency2 = 2,
Frequency3 = 4,
Frequency4 = 8
}
public interface IKanji
{
string Text { get; }
IEnumerable<KanjiInformation> Informations { get; }
Priority Priorities { get; }
}
In above consider each Priority as a bit in an int, you can add priority by using bitwise or (|) :
Priorities = Priority.Frequency1 | Priority.Frequency2 // means have both priorities
To check if it has specific priority use bitwise and (&):
if((Priorities & Priority.Frequency1) == Priority.Frequency1
{
// it contains Priority.Frequency1
}
Then the answer you were looking for will be like:
Priority p = Priority.Frequency1 | Priority.Frequency2 | Priority.Frequency3
var result = entries.Where(e => e.Kanjis.Any(k => k.Priorities & p == p)))
.ToList();
This could be one solution:
var filteredEntries = entries.Where( // Only entries
e => e.Kanjis.Any( // which have one or more kanjis with..
a => a.Priorities.Any( // which have one or more priorities
p => p.Value == "Frequency1" // which have a value of "Frequency1"
)));
I changed your interfaces to classes to make it run with some example-data:
public class IJapaneseDictionaryEntry
{
public int Sequence { get; set; }
public IEnumerable<IKanji> Kanjis { get; set; }
}
public class IKanji
{
public string Text { get; set; }
public IEnumerable<Priority> Priorities { get; set; }
}
public class Priority
{
public string Value { get; set; }
}
public static void Main(string[] args)
{
// Initialize 3 objects. One has Priority we're searching
List<IJapaneseDictionaryEntry> entries = new List<IJapaneseDictionaryEntry>()
{
new IJapaneseDictionaryEntry(){ Sequence = 1, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { Value = "Frequency1" } } } } },
new IJapaneseDictionaryEntry(){ Sequence = 2, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { Value = "Frequency2" } } } } },
new IJapaneseDictionaryEntry(){ Sequence = 3, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { } } } } },
};
// Here's the magic:
var filteredEntries = entries.Where( // Only entries
e => e.Kanjis.Any( // which have one or more kanjis with..
a => a.Priorities.Any( // which have one or more priorities
p => p.Value == "Frequency1" // which have a value of "Frequency1"
)));
// Let's check the output
foreach (var e in filteredEntries)
{
Console.WriteLine(e.Sequence);
}
}

Changing a property of an element in a list doesn't change it

I'm totally confused about a value not being stored. There's an instance of a class that has a member defined like this.
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public bool Taken { get; set; }
public int Id { get; set; }
}
I try to probe that class for IDs to accepted elements and it doesn't work because the updates I'm making don't seem to get through. So I've created the following, extremely simple probe.
List<int> before = myHolder.Where(e => e.Taken).Select(f => f.Id).ToList();
myHolder.First(p => p.Id == 7).Taken = false;
List<int> before = myHolder.Where(e => e.Taken).Select(f => f.Id).ToList();
To my great surprise, the number of before and after stays the same! I've verified that for all the IDs and I've made sure that e.g. 7 is true from the start. I even tried initiating it with false and then setting it to true. There's no other logic going on, as far I can see. I know for sure that it's me doing something wrong but I'm not sure what it is. And it's kind of hard to search for it because this weird behavior is very generic.
It's not like we create a copy of myHolder and put the updated value in it. And if it's so, how can I obtain and write to the real thing?
I'm hoping that someone sees something obvious. Or at least points me in a good direction to search more.
Did you mean something like this:
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public int Id { get; set; }
public bool Taken { get; set; }
public string Name { get; set; }
}
In this code update works as expected
var myHolder = new MyHolder {
Parts = new List<MyPart> {
new MyPart { Id = 7, Taken = true, Name = "Test" },
new MyPart { Id = 8, Taken = false, Name = "Test 1" }
}
};
var before = myHolder.Parts.Where(e => e.Taken).Select(f => f.Id).ToList();
Console.WriteLine(before.Count());
myHolder.Parts.First(p => p.Id == 7).Taken = false;
var after = myHolder.Parts.Where(e => e.Taken).Select(f => f.Id).ToList();
Console.WriteLine(after.Count());
See working fiddle
This worked for me -
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public int Id { get; set; }
public bool Taken { get; set; }
public string Name { get; set; }
}
var holder = new MyHolder() { Parts = new List<MyPart>() { new MyPart() { Id = 7, Name = "R", Taken = true }, new MyPart() { Id = 8, Name = "S", Taken = true }, new MyPart() { Id = 9, Name = "T", Taken = true } } };
List<int> before = holder.Parts.Where(m => m.Taken).Select(f => f.Id).ToList();
holder.Parts.First(p => p.Id == 7).Taken = false;
List<int> after = holder.Parts.Where(m => m.Taken).Select(f => f.Id).ToList();

Reduce cannot contain Average() methods in grouping

Just upgraded to v2 and this no longer works; I get a similar error if I try to use Count()
public class Deck_Ratings : AbstractIndexCreationTask<DeckRating, Deck_Ratings.ReduceResult>
{
public class ReduceResult
{
public string DeckId { get; set; }
public int Rating { get; set; }
}
public Deck_Ratings()
{
Map = deckRatings => deckRatings.Select(deckRating => new
{
deckRating.DeckId,
deckRating.Rating
});
Reduce = reduceResults => reduceResults
.GroupBy(reduceResult => reduceResult.DeckId)
.Select(grouping => new
{
DeckId = grouping.Key,
Rating = grouping.Average(reduceResult => reduceResult.Rating)
});
}
}
Aggregates that can be influenced by the size of the reduce batch (such as Count and Average) are prohibited because they will yield the wrong results. You may have been able to use it under 1.0, but your averages were probably wrong unless you had so few items that they all got done in one reduce batch. To understand more about reduce batches, read Map / Reduce - A Visual Explanation
You must count items by summing a 1 for each item. You must average items by taking a sum of the values as a total, a sum of 1's as a count, and then dividing them.
public class Deck_Ratings : AbstractIndexCreationTask<DeckRating, Deck_Ratings.ReduceResult>
{
public class ReduceResult
{
public string DeckId { get; set; }
public int TotalRating { get; set; }
public int CountRating { get; set; }
public double AverageRating { get; set; }
}
public Deck_Ratings()
{
Map = deckRatings => deckRatings.Select(deckRating => new
{
deckRating.DeckId,
TotalRating = deckRating.Rating,
CountRating = 1,
AverageRating = 0
});
Reduce = reduceResults => reduceResults
.GroupBy(reduceResult => reduceResult.DeckId)
.Select(grouping => new
{
DeckId = grouping.Key,
TotalRating = grouping.Sum(reduceResult => reduceResult.TotalRating)
CountRating = grouping.Sum(reduceResult => reduceResult.CountRating)
})
.Select(x => new
{
x.DeckId,
x.TotalRating,
x.CountRating,
AverageRating = x.TotalRating / x.CountRating
});
}
}
This is issue RavenDB-783. This is expected behavior since v2.0.
Not sure what he recommends as an alternative, though.

Categories

Resources