I'm trying to map a parent/child to another parent/child where the child.id need to be read from a key/value dictionary.
I could do a foreach loop to map the children but I'm interrested to see if there is another way. The code is a simplified version of my model and the remoteDict is read async so using a IValueResolver seems unusable?
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentFrom, ParentTo>()
.ForMember(d => d.Children, o => o.MapFrom(s => s.Children));
cfg.CreateMap<(ChildFrom child, Dictionary<string, int> dict), ChildTo>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.dict.GetValueOrDefault(s.child.ExternalId)));
});
var mapper = config.CreateMapper();
Dictionary<string, int> remoteDict = new Dictionary<string, int>();
remoteDict.Add("A1", 1);
remoteDict.Add("B1", 1);
ParentFrom p = new ParentFrom() { Id = 1001 };
p.Children.Add(new ChildFrom() { ExternalId = "A1" });
p.Children.Add(new ChildFrom() { ExternalId = "B1" });
ParentTo p2 = mapper.Map<ParentTo>(p);
/*
Missing type map configuration or unsupported mapping.
Mapping types:
ChildFrom -> ChildTo
mapping.ChildFrom -> mapping.ChildTo
*/
}
}
public class ParentFrom
{
public int Id { get; set; }
public List<ChildFrom> Children { get; set; } = new List<ChildFrom>();
}
public class ChildFrom
{
public string ExternalId { get; set; }
}
public class ParentTo
{
public int Id { get; set; }
public List<ChildTo> Children { get; set; }
}
public class ChildTo
{
public int Id { get; set; }
}
UPDATE
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentFrom, ParentTo>()
.ForMember(d => d.Children, o => o.MapFrom(s => s.Children));
cfg.CreateMap<ChildFrom, ChildTo>()
.ForMember(d => d.Id, o => o.MapFrom<FromRemote>());
});
var mapper = config.CreateMapper();
Dictionary<string, int> remoteDict = new Dictionary<string, int>();
remoteDict.Add("A1", 1);
remoteDict.Add("B1", 1);
ParentFrom p = new ParentFrom() { Id = 1001 };
p.Children.Add(new ChildFrom() { ExternalId = "A1" });
p.Children.Add(new ChildFrom() { ExternalId = "B1" });
ParentTo p2 = mapper.Map<ParentTo>(p, opt => opt.Items["dict"] = remoteDict);
}
}
public class FromRemote : IValueResolver<ChildFrom, ChildTo, int>
{
public int Resolve(ChildFrom source, ChildTo destination, int destMember, ResolutionContext context)
{
var dict = context.Items["dict"] as Dictionary<string, int>;
return dict.GetValueOrDefault(source.ExternalId);
}
}
public class ParentFrom
{
public int Id { get; set; }
public List<ChildFrom> Children { get; set; } = new List<ChildFrom>();
}
public class ChildFrom
{
public string ExternalId { get; set; }
}
public class ParentTo
{
public int Id { get; set; }
public List<ChildTo> Children { get; set; }
}
public class ChildTo
{
public int Id { get; set; }
}
Related
I want my union LINQ query to be evaluated on server side with EF Core.
There're entities:
public class Entity1
{
public int Id { get; set; }
public List<StringWithStyle> Names { get; set; } = new List<StringWithStyle>();
}
public class Entity2
{
public int Id { get; set; }
public StringWithStyle Name { get; set; }
}
public class StringWithStyle
{
public string Text { get; set; }
public bool IsBold { get; set; }
public bool IsItalic { get; set; }
public bool IsUpperCase { get; set; }
}
Their properties are stored in DbContext as json string using Value conversion:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity1>()
.HasKey(e => e.Id);
modelBuilder.Entity<Entity1>()
.Property(e => e.Names)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<StringWithStyle>>(v, (JsonSerializerOptions)null)
,
new ValueComparer<List<StringWithStyle>>(
(arr1, arr2) => arr1.Count() == arr2.Count() && !arr1.Except(arr2).Any(),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())))
);
modelBuilder.Entity<Entity2>()
.HasKey(e => e.Id);
modelBuilder.Entity<Entity2>()
.Property(e => e.Name)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<StringWithStyle>(v, (JsonSerializerOptions)null)
,
new ValueComparer<StringWithStyle>(
(val1, val2) => val1.Equals(val2),
c => c.GetHashCode())
);
}
I need to show both entities in one grid. So, I use such a query:
var entities1 = from e1 in dbContext.Set<Entity1>()
select new GridModel
{
Id = e1.Id,
IsFirst = true,
Names = e1.Names,
Name = default
};
var entities2 = from e2 in dbContext.Set<Entity2>()
select new GridModel
{
Id = e2.Id,
IsFirst = false,
Name = e2.Name,
Names = default
};
var grid = entities1.Union(entities2).ToList();
And it throws an Exception:
System.InvalidOperationException : Unable to translate set operation after client projection has been applied. Consider moving the set operation before the last 'Select' call.
Is it possible to to get such a query that is evaluating on server side?
*** UPDATE ***
There's GridModel class:
public class GridModel
{
public int Id { get; set; }
public bool IsFirst { get; set; }
public List<StringWithStyle> Names { get; set; }
public StringWithStyle Name { get; set; }
}
I am having the following models
Source:
public class Opportunity
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid QuotationId { get; set; }
public int? QuotationNumber { get; set; }
public int? QuotationVersionNumber { get; set; }
}
Target:
public class OpportunityDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<QuotationDto> Quotations { get; set; }
}
public class QuotationDto
{
public Guid Id { get; set; }
public int Number { get; set; }
public int VersionNumber { get; set; }
}
The data I would fetch from my database would be flat as the Opportunity model and my api is exposing the OpportunityDto model.
so, in my auto-mapper configuration, I have the following code:
services
.AddSingleton(new MapperConfiguration(cfg =>
{
cfg.CreateMap<OpportunityDto, Opportunity>().ReverseMap();
cfg.CreateMap<QuotationDto, Quotation>().ReverseMap();
}).CreateMapper())
what I want to achive is a list of unique opportunities and each opportunity would have a nested member which would have the list of the quotations.
how can I make the automapper perform this grouping? right now the Quotations member of the opportunityDto returned from the api is always empty.
AutoMapper Configuration
You can do something like the following:
public static void InitialiseMapper()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<IEnumerable<Opportunity>, OpportunityDto>()
.ForMember(x => x.Id, x => x.MapFrom(y => y.FirstOrDefault().Id))
.ForMember(x => x.Name, x => x.MapFrom(y => y.FirstOrDefault().Name))
.ForMember(x => x.Quotations,
x => x.MapFrom(y => Mapper.Map<IEnumerable<Opportunity>, IEnumerable<QuotationDto>>(y).ToArray()))
;
cfg.CreateMap<Opportunity, QuotationDto>()
.ForMember(x => x.Id, x => x.MapFrom(y => y.QuotationId))
.ForMember(x => x.Number, x => x.MapFrom(y => y.QuotationNumber))
.ForMember(x => x.VersionNumber, x => x.MapFrom(y => y.QuotationVersionNumber))
;
});
}
Test
Which then successfully knows how to map as demonstrated by the following test:
[TestMethod]
public void TestMethod1()
{
var oppo1Guid = Guid.NewGuid();
var opportunities = new List<Opportunity>
{
new Opportunity
{
Id = oppo1Guid,
Name = "Mikeys Oppurtunity",
QuotationId = Guid.NewGuid(),
QuotationNumber = 169,
QuotationVersionNumber = 80,
},
new Opportunity
{
Id = oppo1Guid,
Name = "Mikeys Oppurtunity",
QuotationId = Guid.NewGuid(),
QuotationNumber = 170,
QuotationVersionNumber = 20,
}
};
var dtos = Mapper.Map<IEnumerable<Opportunity>, OpportunityDto>(opportunities);
var json = JsonConvert.SerializeObject(dtos, Formatting.Indented);
Console.WriteLine(json);
}
And the output is
Test Output
{
"Id": "623c17df-f748-47a2-bc7e-35eb124dbfa3",
"Name": "Mikeys Oppurtunity",
"Quotations": [
{
"Id": "ad8b31c2-6157-4b7f-a1f2-9f8cfc1474b7",
"Number": 169,
"VersionNumber": 80
},
{
"Id": "515aa560-6a5b-47da-a214-255d1815e153",
"Number": 170,
"VersionNumber": 20
}
]
}
#Afflatus I would give you the idea that you can follow. I am assuming that you are using AspNetCore based on your services variable.
You can create an extension method like this, to later make a call on your ConfigurationServices like services.RegisterMappingsWithAutomapper():
public static IServiceCollection RegisterMappingsWithAutomapper(this IServiceCollection services)
{
var mapperConfig = AutoMapperHelper.InitializeAutoMapper();
services.AddScoped<IMapper>(provider => new Mapper(mapperConfig));
return services;
}
The InitializeAutoMapper is below:
public static class AutoMapperHelper
{
public static MapperConfiguration InitializeAutoMapper()
{
//Load here all your assemblies
var allClasses = AllClasses.FromLoadedAssemblies();
MapperConfiguration config = new MapperConfiguration(cfg =>
{
if (allClasses != null)
{
//here normally I add another Profiles that I use with reflection, marking my DTOs with an interface
cfg.AddProfile(new MappingModelsAndDtos(allClasses));
cfg.AddProfile(new MyCustomProfile());
}
});
return config;
}
}
now you need to implement the Profile, in this case MyCustomProfile
public class ModelProfile : Profile
{
public ModelProfile()
{
//put your source and destination here
CreateMap<MySource, MyDestination>()
.ConvertUsing<MySourceToMyDestination<MySource, MyDestination>>();
}
}
you need to implement MySourceToMyDestination class then.
Bellow is an example of the code of how I am using it in my projects
public class ApplicationModel2ApplicationDto : ITypeConverter<ApplicationModel, ApplicationDto>
{
public ApplicationDto Convert(ApplicationModel source, ApplicationDto destination, ResolutionContext context)
{
var mapper = context.Mapper;
try
{
destination = new ApplicationDto
{
ApplicationId = source.ApplicationId,
ApplicationName = source.ApplicationName,
Documents = mapper.Map<IEnumerable<DocumentDto>>(source.Documents),
Tags = mapper.Map<IEnumerable<TagInfoDto>>(source.TagInfos)
};
}
catch
{
return null;
}
return destination;
}
}
Hope this helps
I have code that works, but I worked around a 'Join' in Linq to Entities, because I could not figure it out.
Could you please show me how to succesfully apply it to my code?
My desired result is a dictionary:
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = new Dictionary<string, SelectedCorffData>();
The above mentioned class:
public class SelectedCorffData
{
public long CorffId { get; set; }
public string ReportNumber { get; set; }
public DateTime CorffSubmittedDateTime { get; set; }
}
Please note the 'intersectResult' I am looping through is just a string collection.
Here is my code:
DateTime dateToCompare = DateTime.Now.Date;
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = new Dictionary<string, SelectedCorffData>();
foreach (var mafId in intersectResult)
{
var corffIdsPerMaf = context
.Mafs
.Where(m => m.MafId == mafId)
.Select(m => m.CorffId);
var corffIdForMaf = context
.Corffs
.Where(c => corffIdsPerMaf.Contains(c.Id))
.OrderByDescending(c => c.CorffSubmittedDateTime)
.Select(c => c.Id)
.First();
//Selected close-out forms, whose MAF's may be up for deletion, based on date.
var corffData = context
.Corffs
.Where(c => c.Id == corffIdForMaf && System.Data.Entity.DbFunctions.AddYears(c.CorffSubmittedDateTime, 1).Value > dateToCompare)
.Select(c => new SelectedCorffData () { CorffId = c.Id, ReportNumber = c.ReportNumber, CorffSubmittedDateTime = c.CorffSubmittedDateTime })
.FirstOrDefault();
if(corffData != null)
{
dataSelectedForDeletion.Add(mafId, corffData);
}
}
Please note: this is not just a simple join. If it can't be simplified, please tell me. Also please explain why.
The code below I don't think is exactly right but it is close to what you need. I simulated the database so I could get the syntax correct.
namespace System
{
namespace Data
{
namespace Entity
{
public class DbFunctions
{
public static Data AddYears(DateTime submittedTime, int i)
{
return new Data();
}
public class Data
{
public int Value { get; set; }
}
}
}
}
}
namespace ConsoleApplication23
{
class Program
{
static void Main(string[] args)
{
Context context = new Context();
int dateToCompare = DateTime.Now.Year;
var corffIdsPerMaf = context.Mafs.Select(m => new { id = m.CorffId, mafs = m}).ToList();
var corffIdForMaf = context.Corffs
.Where(c => System.Data.Entity.DbFunctions.AddYears(c.CorffSubmittedDateTime, 1).Value > dateToCompare)
.OrderByDescending(c => c.CorffSubmittedDateTime).Select(c => new { id = c.Id, corff = c}).ToList();
var intersectResult = from p in corffIdsPerMaf
join f in corffIdForMaf on p.id equals f.id
select new SelectedCorffData() { CorffId = p.id, ReportNumber = f.corff.ReportNumber, CorffSubmittedDateTime = f.corff.CorffSubmittedDateTime };
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = intersectResult.GroupBy(x => x.ReportNumber, y => y).ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
public class Context
{
public List<cMafs> Mafs { get; set;}
public List<cCorffs> Corffs { get; set;}
}
public class cMafs
{
public int CorffId { get; set; }
}
public class cCorffs
{
public DateTime CorffSubmittedDateTime { get; set; }
public int Id { get; set; }
public string ReportNumber { get; set; }
}
public class Test
{
}
public class SelectedCorffData
{
public long CorffId { get; set; }
public string ReportNumber { get; set; }
public DateTime CorffSubmittedDateTime { get; set; }
}
}
Lets say i got a something like this:
public class Subscriber{
public string Name {get; set;}
}
public class SomeData{
public string Content {get; set;}
}
public class InputData {
public Subscriber Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
}
public class QueueItem {
public IEnumerable<Subscriber> Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
}
Now lets say i get a List<InputData> full of "Subscribers" with a list of data for each subscriber.
Now i want to compare the list of data of each subscriber, and end up with a List<QueueItem>, where if 2 subscribers have the same set of data items, they would be 1 QueueItem.
Hope this makes sense
The technique is using EqualityComparer with Enumerable.SequenceEqual()
public class Subscriber
{
public string Name { get; set; }
// For compare
public override bool Equals(object obj) { return string.Equals(this.Name, ((Subscriber)obj).Name); }
public override int GetHashCode() { return this.Name.GetHashCode(); }
}
public class SomeData
{
public string Content { get; set; }
// For compare
public override bool Equals(object obj) { return string.Equals(this.Content, ((SomeData)obj).Content); }
public override int GetHashCode() { return this.Content.GetHashCode(); }
}
public class InputData
{
public Subscriber Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
// Should always initialize an empty collection
public InputData() { this.DataItems = new List<SomeData>(); }
}
public class QueueItem
{
public IEnumerable<Subscriber> Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
// Should always initialize an empty collection
public QueueItem() { this.Subscribers = new List<Subscriber>(); this.DataItems = new List<SomeData>(); }
}
public class DataItemsEqualityComparer : EqualityComparer<IEnumerable<SomeData>>
{
public override bool Equals(IEnumerable<SomeData> x, IEnumerable<SomeData> y)
{
return Enumerable.SequenceEqual(x.OrderBy(i => i.Content), y.OrderBy(i => i.Content));
}
public override int GetHashCode(IEnumerable<SomeData> obj)
{
return obj.Select(i => i.GetHashCode()).Sum().GetHashCode();
}
}
Usage
var data = new List<InputData>();
var fruits = new[] { new SomeData() { Content = "apple" }, new SomeData() { Content = "pear"} };
var colors = new[] { new SomeData() { Content = "red" }, new SomeData() { Content = "blue" }, new SomeData() { Content = "green" } };
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Alice" }, DataItems = new List<SomeData>(fruits) });
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Bob" }, DataItems = new List<SomeData>(colors) });
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Charlie" }, DataItems = new List<SomeData>(fruits) });
List<QueueItem> groupedData = data.GroupBy(
i => i.DataItems,
i => i.Subscribers,
new DataItemsEqualityComparer())
.Select(i => new QueueItem() { Subscribers = i, DataItems = i.Key }).ToList();
Result
QueueItem :
Subscribers:
- Alice
- Charlie
Data:
- apple
- pear
QueueItem :
Subscribers:
- Bob
Data:
- red
- blue
- green
var queue = Dictionary(Subscriber, List<SomeData>);
//And lets just for example add some data
var items1 = new List<SomeData>();
items1.Add(new SomeData("test"));
items1.Add(new SomeData("test2"));
var items2 = new List<SomeData>();
items2.Add(new SomeData("test"));
queue.Add(new Subscriber("Peter"), items1);
queue.Add(new Subscriber("Luke"), items1);
queue.Add(new Subscriber("Anna"), items2);
Dictionary<Subscriber, List<SomeData>> myDictionary = queue
.GroupBy(o => o.PropertyName)
.ToDictionary(g => g.Key, g => g.ToList());
I am trying to utilize CustomAttributes to specify the order of the properties of the object
public class WCG : DistrictExport
{
[DataMember(Name = "Survey", Order = 190)]
public string Survey { get; set; }
[DataMember(Name = "Western Hemisphere\nwith Europe", Order = 200)]
public string WesternHemisphereWithEurope { get; set; }
[DataMember(Name = "Eastern Hemisphere", Order = 210)]
public string EasternHemisphere { get; set; }
}
How do you specify the order of columns of a doddlereport without creating a new object?
List<object> report = GetReportResults();
report = new Report(results.ToReportSource(), Writer);
OK!,I try your problem.And I'm great worked.Try my source:
My simple Test class:
public class Test
{
[DataMember(Name = "A", Order = 96)]
public string A { get; set; }
[DataMember(Name = "B", Order = 97)]
public string B { get; set; }
[DataMember(Name = "C", Order = 98)]
public string C { get; set; }
}
And Ext:
public static class Ext
{
public static IList<KeyValuePair<string, int>> AsOrderColumns<T>(this T t)
where T : class
{
return t.GetType()
.GetProperties()
.Where(w => w.IsOrderColumn())
.Select(s => s.GetOrderColumn())
.OrderBy(o => o.Value)
.ToList();
}
private static bool IsOrderColumn(this PropertyInfo prop)
{
return prop.GetCustomAttributes(typeof(DataMemberAttribute), true)
.Any();
}
private static KeyValuePair<string, int> GetOrderColumn(this PropertyInfo prop)
{
var attr = prop.GetCustomAttributes(typeof(DataMemberAttribute), true)
.ElementAt(0) as DataMemberAttribute;
return (attr != null)
? new KeyValuePair<string, int>(attr.Name, attr.Order)
: new KeyValuePair<string, int>();
}
public static IList<object> AsOrderRow<T>(this T t)
where T : class
{
return t.GetType()
.GetProperties()
.Where(w => w.IsOrderColumn())
.OrderBy(o => o.GetOrderColumn().Value)
.Select(s => s.GetValue(t, null))
.ToList();
}
}
Console test code:
class Program
{
static void Main(string[] args)
{
var test = new Test();
var tests = new List<Test>()
{
new Test() {A = "A-1.1", B = "B-1.2", C = "C-1.3"},
new Test() {A = "A-2.1", B = "B-2.2", C = "C-2.3"},
new Test() {A = "A-3.1", B = "B-3.2", C = "C-3.3"},
new Test() {A = "A-4.1", B = "B-4.2", C = "C-4.3"}
};
Console.WriteLine(String.Join<string>("\t", test.AsOrderColumns().Select(s => String.Format("{0}({1})", s.Key, s.Value))));
foreach (var item in tests)
{
Console.WriteLine(String.Join<object>("\t", item.AsOrderRow()));
}
Console.ReadKey();
}
}