AutoMapper 5.1 - Map Model and extended Model - c#

I'm new with AutoMapper and i'm using 5.1.1 version.
I tried to map a list of model with a list of extended model. I get empty object in return.
My source model
public class spt_detail
{
public string Name { get; set; }
public string Age { get; set; }
}
My destination model
public class spt_detail_extended : spt_detail
{
public string Mm1 { get; set; }
public string Mm2 { get; set; }
}
AutoMapper code
spt_detail details = db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
List<spt_creance_detail_enrichi> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_enrichi>>(details);
Issue : details contains 8 rows but cenr 0.
Someone can helps me ?

Mapping for spt_detail to spt_detail_extended.
You should create map rules only for models, like that:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<spt_detail, spt_detail_extended>();
});
And after that, you hould use the construction:
List<spt_detail_extended> extendeds = Mapper.Map<List<spt_detail_extended>>(details);
If you want to map other models, just add or edit your configuration.

Don't map the list, instead map like so:
Mapper.Initialize(n => n.CreateMap<spt_detail, spt_detail_extended>());
You call to do the map would stay the same:
List<spt_detail_extended> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_extended>>(details);

You have missed two steps in here.
The first one is that you need to initialize a list of your business object instead of initializing only a single one.
You retrieve a list from your database (you use .Tolist())
here is a sample on how i initialized an object:
List <SptDetail> details = new List <SptDetail> {
new SptDetail {
Age = "10",
Name = "Marion"
},
new SptDetail {
Age = "11",
Name = "Elisabeth"
}
};
The second misstep is that you were mapping lits, i suggest work with your single business class objects instead like following:
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
.ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
And the key in the hole story is to use .ForMember to specify wich member goes where because the properties does not have same name.
here is a code sample that runs like a charm with:
internal class Program
{
public static List<SptDetailExtended> InitializeExtendedObjects()
{
var details = new List<SptDetail>
{
new SptDetail
{
Age = "10",
Name = "Marion"
},
new SptDetail
{
Age = "11",
Name = "Elisabeth"
}
};
//this is wrong db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
/*you need to use ForMember*/ .ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
//instead of this Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
//change your mapping like following too
var cenr = Mapper.Map<List<SptDetailExtended>>(details);
return cenr;
}
private static void Main(string[] args)
{
var result = InitializeExtendedObjects();
foreach (var sptDetailExtended in result)
{
Console.WriteLine(sptDetailExtended.ExProp1);
Console.WriteLine(sptDetailExtended.ExProp2);
}
Console.ReadLine();
}
}
Hope this helps!

Related

How to map a class A that inherits from base class using AutoMapper in c#

I have a class GetData
public class GetData
{
public FooClass PrevData {get;set;}
public FooClass NewData {get;set;}
}
public class FooClass: Class B{}
public class B
{
public string varC;
public string varD;
public string varE;
}
I need to use automapper to map class GetData's PrevDataA's fields to Class C's properties. I will then use class C to store data in database.
public class C
{
public Data data {get;set;}
public string var_e;
}
public class Data
{
public string var_c;
public string var_d;
}
I have tried these two :
CreateMap<GetData,C>()
.ConvertUsing(src => src.PrevData.Select (x => new C
{
var_e = x.varE;
varData= new Data()
{
var_c = varC;
var_d = varD;
}}));
}
Here I get an error on 'select' that testClass does not contain definition for 'Select'.
Also, I have tried using below:
.ForMember(dest => dest.Data.var_c, src => src.MapFrom(src.PrevData.VarC))
This is also giving an error saying :
Expression "dest => dest.Data.var_C" must resolve top level member and not any child's properties. You can Forpath, a custom resolver on the childtype or the AfterMap option instead.
Can anyone help in using ForPath or this.
You can specify the mapping for the properties/fields C.data and C.var_e manually with the ForMember() configuration of CreateMap(). The configuration can look like this:
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<GetData, C>()
.ForMember(dest => dest.var_e, i => i.MapFrom(src => src.PrevData.varE))
.ForMember(dest => dest.data, i => i.MapFrom(src => new Data
{
var_c = src.PrevData.varC,
var_d = src.PrevData.varD
}));
});
This will say that for the target C.var_e field, use the value of src.PrevData.varE. And for the C.data field, create a new Data instance with the filled var_c and var_d fields with values from src.PrevData.varC and src.PrevData.varD.
See the following example:
GetData getData = new GetData {
NewData = new FooClass {
varC = "newData_C",
varD = "newData_D",
varE = "newData_E"
},
PrevData = new FooClass {
varC = "prevData_C",
varD = "prevData_D",
varE = "prevData_E"
}
};
C target = mapper.Map<C>(getData);
Console.WriteLine(target.var_e);
if (target.data != null)
{
Console.WriteLine(target.data.var_c);
Console.WriteLine(target.data.var_d);
}
else
{
Console.WriteLine("target.data is NULL");
}
This will generate the following output:
prevData_E
prevData_C
prevData_D

How to make Filter Aggregations using NEST?

I have a requirement for filter aggregations using NEST. But since I don't know much about this, I have made the below:
class Program
{
static void Main(string[] args)
{
ISearchResponse<TestReportModel> searchResponse =
ConnectionToES.EsClient()
.Search<TestReportModel>
(s => s
.Index("feedbackdata")
.From(0)
.Size(50000)
.Query(q =>q.MatchAll())
);
var testRecords = searchResponse.Documents.ToList<TestReportModel>();
result = ComputeTrailGap(testRecords);
}
private static List<TestModel> ComputeTrailGap(List<TestReportModel> testRecords)
{
var objTestModel = new List<TestModel>();
var ptpDispositionCodes = new string[] { "PTP" };
var bptpDispositionCodes = new string[] { "BPTP","SBPTP" };
int gapResult = testRecords.Where(w => w.trailstatus == "Gap").Count();
var ptpResult = testRecords.Where(w => ptpDispositionCodes.Contains(w.lastdispositioncode)).ToList().Count();
var bptpResult = testRecords.Where(w => bptpDispositionCodes.Contains(w.lastdispositioncode)).ToList().Count();
objTestModel.Add(new TestModel { TrailStatus = "Gap", NoOfAccounts = gapResult });
objTestModel.Add(new TestModel { TrailStatus = "PTP", NoOfAccounts = ptpResult });
objTestModel.Add(new TestModel { TrailStatus = "BPTP", NoOfAccounts = bptpResult });
return objTestModel;
}
}
DTO
public class TestReportModel
{
public string trailstatus { get; set; }
public string lastdispositioncode { get; set; }
}
public class TestOutputAPIModel
{
public List<TestModel> TestModelDetail { get; set; }
}
public class TestModel
{
public string TrailStatus { get; set; }
public int NoOfAccounts { get; set; }
}
This program works but as can be figure out that we are only accessing the Elastic Search via NEST and the rest of the Aggregations /Filter are done using Lambda.
I would like to perform the entire operation (Aggregations /Filter etc) using NEST framework and put it in the TestModel using filter aggregation.
How can I construct the DSL query inside NEST?
Update
I have been able to make the below so far but the count is zero. What is wrong in my query construction?
var ptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "PTP" },
};
var bptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "BPTP" },
};
ISearchResponse<TestReportModel> searchResponse =
ConnectionToES.EsClient()
.Search<TestReportModel>
(s => s
.Index("feedbackdata")
.From(0)
.Size(50000)
.Query(q =>q.MatchAll())
.Aggregations(fa => fa
.Filter("ptp_aggs", f => f.Filter(fd => ptpDispositionCodes))
.Filter("bptp_aggs", f => f.Filter(fd => bptpDispositionCodes))
)
);
Result
I see that you are trying to perform a search on the type of TestReportModel. The overall structure of your approach seems good enough. However, there is a trouble with the queries that are being attached to your filter containers.
Your TestReportModel contains two properties trialStatus and lastdispositioncode. You are setting the Field property as description inside your terms query. This is the reason that you are seeing the counts as zero. The model on which you are performing the search on (in turn the index that you are performing the search on) does not have a property description and hence the difference. NEST or Elasticsearch, in this case does not throw any exception. Instead, it returns count zero. Field value should be modified to lastdispositioncode.
// Type on which the search is being performed.
// Response is of the type ISearchResponse<TestReportModel>
public class TestReportModel
{
public string trailstatus { get; set; }
public string lastdispositioncode { get; set; }
}
Modified terms queries are as follows
// Field is "lastdispositioncode" and not "description"
// You may amend the Terms field as applicable
var ptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "PTP" },
};
var bptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "BPTP", "SBPTP" },
};
Since it seems that the values to lastdispositioncode seem to take a single word value (PTP or BPTP from your examples), I believe, it is not going to matter if the field in the doc is analyzed or not. You can further obtain the counts from the ISearchResponse<T> type as shown below
var ptpDocCount = ((Nest.SingleBucketAggregate)response.Aggregations["ptp_aggs"]).DocCount;
var bptpDocCount = ((Nest.SingleBucketAggregate)response.Aggregations["bptp_aggs"]).DocCount;
Edit: Adding an approach for keyword search
QueryContainer qc1 = new QueryContainerDescriptor<TestReportModel>()
.Bool(b => b.Must(m => m.Terms(t => t.Field(f => f.lastdispositioncode.Suffix("keyword"))
.Terms(new string[]{"ptp"}))));
QueryContainer qc2 = new QueryContainerDescriptor<TestReportModel>()
.Bool(b => b.Must(m => m.Terms(t => t.Field(f => f.lastdispositioncode.Suffix("keyword"))
.Terms(new string[]{"bptp", "sbptp"}))));
Now these query containers can be hooked to your aggregation as shown below
.Aggregations(aggs => aggs
.Filter("f1", f => f.Filter(f => qc1))
.Filter("f2", f => f.Filter(f => qc2)))
The queries for aggregations that are generated by the NEST client in this case look like below
"f1": {
"filter": {
"bool": {
"must": [
{
"terms": {
"lastdispositioncode.keyword": [
"bptp"
]
}
}
]
}
}
}
Also, coming back to the case of search being case-insensitive, Elasticsearch deals with search in a case-insensitive fashion. However, it varies depending on analyzed vs non-analyzed fields. Analyzed fields are tokenized and text fields are by default tokenized. We use the suffix extension method of NEST on analyzed fields ideally and to get an exact match on the analyzed field. More about them here

Trying to deep clone the row values with in the entity except Id

I have project entity structure like this as below with project name and project number and along with some list objects as well as properties.
public class DesignProject
{
[Key, GraphQLNonNullType]
public string ProjectNumber { get; set; }
public string Name { get; set; }
[Column(TypeName = "jsonb")]
public ProjectSectionStatus SectionStatuses { get; set; } = new ProjectSectionStatus();
[ForeignKey("AshraeClimateZone"), GraphQLIgnore]
public Guid? AshraeClimateZoneId { get; set; }
public virtual AshraeClimateZone AshraeClimateZone { get; set; }
[Column(TypeName = "jsonb")]
public List<ProjectObject<classA>> classAList{ get; set; } = new List<ProjectObject<classA>>();
[Column(TypeName = "jsonb")]
public List<ProjectObject<classB>> classBList{ get; set; } = new List<ProjectObject<classB>>();
......
......
......
Some more json columns
}
and the project object class like this
public class ProjectObject<T>
{
public Guid? Id { get; set; }
public T OriginalObject { get; set; }
public T ModifiedObject { get; set; }
[GraphQLIgnore, JsonIgnore]
public T TargetObject
{
get
{
return ModifiedObject ?? OriginalObject;
}
}
}
and ClassA entity structure like as below
public class ClassA
{
public string Edition { get; set; }
public string City { get; set; }
}
and i have some similar children entities (ClassA) same like as above, I want to copy the contents and statuses from one project entity to other project entity.
I have project entity with ProjectNumber 1212 and have another project having ProjectNumber like 23323 so i would like copy entire project contents from 1212 to 23323. So is there any way we can achieve this with C# and i am using .Net Core with Entity framework core.
Here the source design project that i am going to copy have same structure with destination design project and i am fine with overriding the destination project values and i don't want to update the project number here.
Could any one please let me know the how can i achieve this copying? Thanks in advance!!
Please let me know if i need to add any details for this question
Update : Deep copy related code
public InsertResponse<string> CopyBookmarkproject(string SourceProjectNumber, string destinationProjectNumber)
{
var sourceDesignProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();
CopyProject(sourceDesignProject, destinationProject);
// Need to update the Db context at here after deep copy
}
private void CopyProject(DesignProject sourceDesignProject, DesignProject destinationProject)
{
destinationProject.classAList= sourceDesignProject.classAList; // Not sure whether this copy will works
destinationProject.AshraeClimateZone = sourceDesignProject.AshraeClimateZone; // not sure whether this copy will works also
}
Updated solution 2:
var sourceDesignProject = this._dbContext.DesignProjects.AsNoTracking()
.Where(a => a.ProjectNumber == sourceProjectNumber)
.Include(a => a.PrimaryBuildingType)
.Include(a => a.AshraeClimateZone).SingleOrDefault();
var targetDesignProject = this._dbContext.DesignProjects.Where(a => a.ProjectNumber == targetProjectNumber).SingleOrDefault();
sourceDesignProject.ProjectNumber = targetDesignProject.ProjectNumber;
sourceDesignProject.SectionStatuses.AirSystemsSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.CodesAndGuidelinesSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.ExecutiveSummarySectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.ExhaustEquipmentSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.InfiltrationSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
this._dbContext.Entry(sourceDesignProject).State = EntityState.Modified; // getting the below error at this line
this._dbContext.SaveChanges();
ok = true;
getting an error like as below
The instance of entity type 'DesignProject' cannot be tracked because another instance with the same key value for {'ProjectNumber'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
There's a simple and short solution which uses .AsNoTracking() for source entry, assigning destination's ProjectNumber to the source and then changing the EntityState as Modified for it. It will copy all the properties to the destination entry :
var sourceDesignProject = this._dbContext.DesignProject.AsNoTracking().Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();
sourceDesignProject.ProjectNumber = destinationProject.ProjectNumber;
this._dbContext.Entry(sourceDesignProject).State = System.Data.Entity.EntityState.Modified;
this._dbContext.SaveChanges();
Make sure your relational properties are loaded before.
You can make use of Reflection. As you have both, source and destination objects and are of the same type. You can just traverse through source object and fetch the properties and replace the values.
Something like :
if (inputObjectA != null && inputObjectB != null)
{
//create variables to store object values
object value1, value2;
PropertyInfo[] properties = inputObjectA.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
//get all public properties of the object using reflection
foreach (PropertyInfo propertyInfo in properties)
{
//get the property values of both the objects
value1 = propertyInfo.GetValue(inputObjectA, null);
value2 = propertyInfo.GetValue(inputObjectB, null);
}
Ignore the properties that are not needed to be copied. The method will have a signature like below :
CopyObjects(object inputObjectA, object inputObjectB, string[] ignorePropertiesList)
Two choices bring to mind for deep copy of objects:
1- Using BinaryFormatter (Needs Serializable attribute on your class, See original answer):
public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
2- Using JsonSerializers like NewtoneSoft:
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Update
Here's the code you can use with NewtonSoft Json library:
private void CopyProject(DesignProject sourceDesignProject, ref DesignProject destinationProject)
{
if (Object.ReferenceEquals(sourceDesignProject, null))
{
destinationProject = null;
return;
}
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
var destProjNumber = destinationProject.ProjectNumber;
destinationProject = JsonConvert.DeserializeObject<DesignProject>
(JsonConvert.SerializeObject(sourceDesignProject), deserializeSettings);
destinationProject.ProjectNumber = destProjNumber;
}
I always prefer using AutoMapper for these type of work. It's optimized and lower risk of making mistakes. And better than serialize/deserialize method since that would cause performance issues.
In your constructor (or in startup.cs and passing via dependency injection)
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<DesignProject, DesignProject>().ForMember(x => x.Id, opt => opt.Ignore());
cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));
});
var mapper = config.CreateMapper();
In your method :
var newProject = mapper.Map(sourceProject, new DesignProject());
OR
mapper.Map(sourceProject, targetProject);
This shouldn't work. If I understand you want to copy all elements from list into new list, but to make new object. Than maybe you can use extension method to clone complete list as described here link. For updating navigation properties check this post link.

Separate class for the same purpose

I have this class
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<string> Blessings;
}
I am Getting the response of the two lists this way:
public async Task<List<BlessingDTO>> GetBlessing(string UserType)
{
string blessing = "Blessing_" + UserType;
List<BlessingDTO> results = new List<BlessingDTO>();
using (DTS_OnlineContext context = new DTS_OnlineContext())
{
var items = await context.Messages.AsNoTracking().Where(x => x.MessageContext == blessing).GroupBy(x=>x.GroupKey).Select(b=>b.OrderBy(x=>x.Sort)).ToListAsync();
if (items.Count() > 0)
{//Notes.Select(x => x.Author).Distinct();
results = items.ToList().ConvertAll(x => new BlessingDTO()
{ BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
});
}
}
return results;
}
if I am changing the class, for my porpuse to be:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings;
}
public class bless
{
public string text;
public int length;
}
how can I initialize the new class ?
Blessings = new bless
won't give the results. how can I save the data to bring them in the response
Let's focus in this part:
items
.ToList()
.ConvertAll(x =>
new BlessingDTO()
{
BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
}
);
where items is probably a List<List<Message>>, thus x being a List<Message>.
Now what is causing an error is the following: Blessings = x.ToList().Select(y => y.MessageText).ToList(). This creates a new list for the list of messages, then selects the MessageText from that list, which results in IEnumerable<string>. In the end a new list is created for these strings. This list of strings isn't assignable to List<bless>, thus will generate an error.
What you want is a result of List<bless>, so we need to convert the List<Message> list into a List<bless> somehow. We know how to do that, namely with a select: x.Select(message => new bless()).ToList(). All we have to do is fill in the properties of bless: x.Select(message => new bless { text = message.MessageText }).ToList(). The other property is up to you.
You can initialise the list like this:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings = new List<bless>();
}
Although, I would recommend these fields are changes to properties, as that is more idiomatic in C#
public class BlessingDTO
{
public List<string> BlessingCategoryName {get;set;}
public List<bless> Blessings {get;set;} = new List<bless>();
}

Populating SubModels using LINQ

I have a list of object which is something like this
[Code Type][condition Type][Question Type][Description]
[A1][C1][q1][dC1]
[A1][C1][q2][dC1]
[A1][C1][q3][dC1]
[B1][C2][q4][dC2]
[B1][C2][q5][dC2]
[B1][C2][q6][dC2]
[B1][C3][q7][dC3]
[B1][C3][q8][dC3]
[B1][C3][q9][dc3]
I want to map this with a class which has a subclass and it's subclass also has a subclass.
Structure is like this
public Class TypeModel
{
public string Type{get;set;}
public List<ConditionModel> Conditions {get;set;}
}
public Class ConditionModel
{
public string Type{get;set;}
public string Description {get;set;}
public List<QuestionModel> Questions {get;set;}
}
public Class QuestionModel
{
public string Type {get;set;}
}
I have written this LINQ query to populate the main Type Class so far but it is not working. I need help in creating this query.
var results = allTypes.GroupBy(type => type.CodeType)
.Select(grp => new TypeModel
{
Type = grp.Select(i => i.CodeType).First(),
Conditions = new List<ConditionModel>
{
grp.GroupBy(condition => condition.ConditionType)
.Select(conditionGrp => new ConditionModel {
Type = conditionGrp.Select(i => i.ConditionType).First(),
Description = conditionGrp.Select(i => i.Description).First(),
Questions = new List<QuestionModel>
{
conditionGrp.GroupBy(question => question.QuestionType)
.Select(questionGrp => new QuestionModel
{
Type = questionGrp.Select(i => i.QuestionType).First(),
})
}
})
}
});
What I am trying to achieve with this query? To get list of TypeModel.
If you'll notice the table first three rows will fetch me one typeModel and another 6 rows another typeModel but it will have two ConditonModels and each condition Model, 3 questionModel.
The following should work:
Group by CodeType first, then inside each group, group by ConditionType, Description and select appropriate results.
var results = allTypes.GroupBy(
type => type.CodeType, // CodeType key selector
(codeType, elements) => new TypeModel
{
Type = codeType,
Conditions = elements.GroupBy(
x => new { x.ConditionType, x.Description }, // ConditionType key selector
x => x.QuestionType, // QuestionType selector as elements of the ConditionType group
(condition, elements2) => new ConditionModel
{
Type = condition.ConditionType,
Description = condition.Description,
// Questions transformation
Questions = elements2.Select(q => new QuestionModel { Type = q }).ToList()
}).ToList()
});
In case you are confused by so much nested LINQ, there is nothing wrong in using some plain old loops in order to create your resulting data:
var results = new List<TypeModel>();
foreach (var item in allTypes.GroupBy(type => type.CodeType))
{
var conditionsList = new List<ConditionModel>();
foreach (var item2 in item.GroupBy(x => new { x.ConditionType, x.Description }))
{
conditionsList.Add(new ConditionModel
{
Type = item2.Key.ConditionType,
Description = item2.Key.Description,
Questions = item2.Select(x => new QuestionModel { Type = x.QuestionType }).ToList()
});
}
results.Add(new TypeModel
{
Type = item.Key,
Conditions = conditionsList
});
}

Categories

Resources