I am having an odd error when using Fluent NHibernate. I have an entity object called Module that is my "aggregate root" if you will. I created and tested my mappings for the Module class and the various related classes and tried creating a Module with all of its fields filled out and saving it. This worked correctly.
My problem is when I try to retrieve the objects from the database. The Module object comes back just fine, however any of the collections it contains are all empty even though I see the objects in the database tables!! I could really use a hand on this one, I hope somone can help!
edit: added the code for BuildDeriveModule below.
Here is my test:
var dei1 = BuildDeriveModule();
var sessionSource = Injector.Resolve<ISessionSource>();
using (var session = sessionSource.CreateSession())
{
using (var transaction = session.BeginTransaction())
{
session.Save(dei1);
transaction.Commit();
}
}
Module item = null;
using (var session = sessionSource.CreateSession())
{
item = session
.CreateCriteria(typeof(Module))
.Add(Restrictions.Eq("ModuleId", dei1.ModuleId))
.UniqueResult<Module>();
Assert.Equal<Module>(dei1, item);
Assert.Equal<int>(4, item.Variables.Count);
}
My Module class looks like this: (Note: Entity comes from FluentNHibernate.Data.Entity)
public class Module : Entity, IEntity
{
public Module()
{
Variables = new HashedSet<ModuleVariable>();
}
public virtual string ModuleId
{
get;
set;
}
public virtual ISet<ModuleVariable> Variables
{
get;
set;
}
public virtual Variable StratumVariable
{
get;
set;
}
public virtual Stratum DefaultStratum
{
get;
set;
}
public virtual ISet<Stratum> OptionalStrata
{
get;
set;
}
public virtual DateTime LastUpdated
{
get;
set;
}
#region Override Methods
public override string ToString()
{
return this.ModuleId;
}
public override bool Equals(object obj)
{
if (obj == null) return false;
var other = obj as Module;
if (other == null) return false;
if (other.ModuleId != this.ModuleId) return false;
return true;
}
public override int GetHashCode()
{
return ModuleId.GetHashCode();
}
#endregion
}
And my Mapping looks like this:
public class ModuleMap : ClassMap<Module>
{
public ModuleMap()
{
Id(x => x.Id);
Version(x => x.LastUpdated);
Map(x => x.ModuleId)
.Unique()
.Not.Nullable();
HasMany<ModuleVariable>(x => x.Variables)
.Inverse()
.Cascade.SaveUpdate();
References<Variable>(x => x.StratumVariable)
.Not.Nullable()
.Cascade.SaveUpdate();
References<Stratum>(x => x.DefaultStratum)
.Not.Nullable()
.Cascade.SaveUpdate();
HasMany<Stratum>(x => x.OptionalStrata)
.Inverse()
.Cascade.SaveUpdate();
}
}
BuildDeriveModule's code:
private Module BuildDeriveModule()
{
var pp01 = new Relation
{
RelationId = "PP01"
};
var hh01 = new Relation
{
RelationId = "HH01"
};
var stdei1 = new Variable
{
VariableId = "STDEI1",
Relation = pp01
};
var frameId = new Variable
{
VariableId = "FRAME_ID",
Relation = pp01
};
var persno = new Variable
{
VariableId = "PERSNO",
Relation = pp01
};
var hp = new Driver
{
Name = "HP",
Phase = 1
};
hp.KeyVariables.Add(frameId);
hp.KeyVariables.Add(persno);
var r2p1 = new Variable
{
VariableId = "R2P1",
Relation = pp01
};
var age = new Variable
{
VariableId = "AGE",
Relation = pp01
};
var marst = new Variable
{
VariableId = "MARST",
Relation = pp01
};
var doctp = new Variable
{
VariableId = "DOCTP",
Relation = hh01
};
pp01.AddVariable(stdei1);
pp01.AddVariable(r2p1);
pp01.AddVariable(age);
pp01.AddVariable(marst);
hh01.AddVariable(doctp);
var defaultStratum = new Stratum
{
Number = -1,
Driver = hp
};
var dei1 = new Module
{
ModuleId = "DEI1",
StratumVariable = stdei1,
DefaultStratum = defaultStratum
};
var mv_r2p1 = new ModuleVariable
{
ModuleId = dei1,
VariableId = "R2P1",
UploadId = r2p1,
DownloadId = r2p1,
HasSubunits = true
};
var mv_age = new ModuleVariable
{
ModuleId = dei1,
VariableId = "AGE",
UploadId = age,
DownloadId = age,
HasSubunits = true
};
var mv_marst = new ModuleVariable
{
ModuleId = dei1,
VariableId = "MARST",
UploadId = marst,
DownloadId = marst,
HasSubunits = true
};
var mv_doctp = new ModuleVariable
{
ModuleId = dei1,
VariableId = "DOCTP",
UploadId = doctp,
DownloadId = doctp,
HasSubunits = false
};
dei1.AddVariable(mv_r2p1);
dei1.AddVariable(mv_age);
dei1.AddVariable(mv_marst);
dei1.AddVariable(mv_doctp);
return dei1;
}
OK, I think I got it to work. I removed the .Inverse() from thins line
HasMany<ModuleVariable>(x => x.Variables)
.Inverse()
.Cascade.SaveUpdate();
in ModuleMap and the unit test worked. I'm not quite sure why though, so if anyone can point out that out it would be appreciated.
Related
I have the following two tables:
- ProductElements (which contains FK of Product and Element)
- ElementProcesses (which contains FK of Element and Process)
The relationship is many to many: so 1 product is related with multiple elements and by the other hand 1 element has multiple processes.
The data I want to get is like the following table:
https://i.imgur.com/0NWu8Oi.jpg
I have tried the following code, which by now only displays me the first relationship.
var result = from item1 in db.ProductElements.GetAll(includeProperties: "Element,Product")
join item2 in db.ElementProcesses.GetAll(includeProperties: "Element,Process") on item1.ElementId equals item2.ElementId
group new { item2.Element } by new { item1.Product } into g
select new
{
Product = g.Key.Product.Name,
Elements = g.Select(t=>t.Element).Distinct(),
};
For relationships you describe, you may have simple classes:
class Products
{
public string ProdName { get; set; }
}
class Elements
{
public string ElemName { get; set; }
}
class Processes
{
public string ProcName { get; set; }
}
class ProductElements
{
public Products Product { get; set; }
public Elements Element { get; set; }
}
class ElementProcesses
{
public Elements Element { get; set; }
public Processes Process { get; set; }
}
var p01 = new Products() { ProdName = "P01" };
var p02 = new Products() { ProdName = "P02" };
var e01 = new Elements() { ElemName = "E01" };
var e05 = new Elements() { ElemName = "E05" };
var e06 = new Elements() { ElemName = "E06" };
var e08 = new Elements() { ElemName = "E08" };
var pc01 = new Processes() { ProcName = "PC01" };
var pc02 = new Processes() { ProcName = "PC02" };
var pc07 = new Processes() { ProcName = "PC07" };
var pc08 = new Processes() { ProcName = "PC08" };
var pc09 = new Processes() { ProcName = "PC09" };
var productElements01 = new List<ProductElements>() {
new ProductElements(){ Product = p01, Element = e01},
new ProductElements(){ Product = p01, Element = e06},
new ProductElements(){ Product = p02, Element = e05},
new ProductElements(){ Product = p02, Element = e08}};
var elemProc01 = new List<ElementProcesses>() {
new ElementProcesses(){Element = e01, Process = pc01},
{new ElementProcesses(){Element = e01, Process = pc02}},
{new ElementProcesses(){Element = e06, Process = pc08}},
{new ElementProcesses(){Element = e05, Process = pc07}},
{new ElementProcesses(){Element = e08, Process = pc09}}};
then your query may look like this:
var query = (from ps in productElements01
from ep in elemProc01
where ps.Element == ep.Element
select new {ps.Product, ps.Element, ep.Process});
I'm trying to agregate a list of multiple propertys with Linq.
My Second field is a List of Strings + an other List of strings inside.
Here's a sample of my code :
using System;
using System.Collections.Generic;
using System.Linq;
public class RefValueData
{
public int ReferenceId { get; set; }
public int SiteId { get; set; }
public string SiteName { get; set; }
public string Code { get; set; }
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
}
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
}
public class Program
{
public static void Main()
{
var values = new List<RefValueData>
{
new RefValueData(){
ReferenceId = 4,
Code = "Code",
SiteId = 2,
SiteName = "Paris",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 5,
Code = "Code",
SiteId = 4,
SiteName = "Lyon",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 6,
Code = "Code",
SiteId = 3,
SiteName = "Paris",
UnitPoints = 52,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "B",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Salut" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Ciao" },
}
}
}
}
};
var values2 = values
.Distinct()
.GroupBy(x => new
{
x.UnitPoints,
x.Texts
})
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y=>y.SiteName)
})
.ToList();
Console.WriteLine(values2.Count);
}
}
I want to have only two lines in my values2 list, but everytime it returns me the whole list.
When I only group by Unit Point, it's work great !
I tried to group the first two lines of my list with some custom Linq query but it doesn't work at all...
Any help / advice is much appreciated :) !
EDIT :
I also tried with an override of the Equals methods like this, but I can't make it work :
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationValue;
if (other == null)
{
return false;
}
return Culture == other.Culture && TranslationText == other.TranslationText;
}
public override int GetHashCode()
{
var hashCode = -2095322044;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TranslationText);
return hashCode;
}
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationData;
if (other == null)
{
return false;
}
return Text == other.Text && Translations.SequenceEqual(other.Translations);
}
public override int GetHashCode()
{
var hashCode = -1551681861;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
hashCode = hashCode * -1521134295 + EqualityComparer<List<TranslationValue>>.Default.GetHashCode(Translations);
return hashCode;
}
}
EDIT2 : Here's my 'real' code :
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = // Type: List<TranslationData> , but it will not use the TranslationDataList class that normally work thanks to your help
value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
}).ToList()
}
Julien.
Here's one solution. It works for the sample code that you wrote. But it needs a little work to be robust:
// and also change the declarations in the main method to: new TranslationDataList
public class TranslationDataList : List<TranslationData>
{
public override int GetHashCode()
{
int hash = 13;
// string.GetHashCode() is not reliable. This should be an algorithm that returns the same value for two different lists that contain the same data
foreach (var data in this)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var other = obj as TranslationDataList;
if (other == null) return false;
if (other.Count != Count) return false;
// write the equality logic here. I don't know if it's ok!
for (int i = 0; i < other.Count; i++)
if (other[i].Text != this[i].Text)
return false;
return true;
}
}
First of all you should add a constructor to the TranslationDataList:
public class TranslationDataList : List<TranslationData>
{
public TranslationDataList(IEnumerable<TranslationData> translationData)
: base(translationData)
{ }
// other members ...
}
Now you can use the TranslationDataList in your query:
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = new TranslationDataList( value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
})); // don't ToList() here anymore
}
And here is another solution:
The GroupBy method takes an IEqualityComparer that can do the responsibility of comparing items for the grouping. But the problem is you used an anonymous type for the key in your grouping "GroupBy(x=>new{x.UnitPoints, x.Texts})". First we have to create a class to play the key role:
public class Key
{
public Key(decimal unitPoints, List<TranslationData> texts)
{
UnitPoints = unitPoints;
Texts = texts;
}
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
then we can implement the comparer:
public class Comparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.UnitPoints != y.UnitPoints) return false;
if (!ListsAreEqual(x.Texts, y.Texts)) return false;
return true;
}
private bool ListsAreEqual(List<TranslationData> x, List<TranslationData> y)
{
if (x.Count != y.Count) return false;
for (int i = 0; i < x.Count; i++)
if (x[i].Text != y[i].Text)
return false;
return true;
}
public int GetHashCode(Key key)
{
int hash = 13;
hash = (hash * 7) + key.UnitPoints.GetHashCode();
foreach (var data in key.Texts)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
}
and finally this is what your query will look like:
var values2 = values
.Distinct()
.GroupBy(x => new Key(x.UnitPoints, x.Texts), new Comparer())
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y => y.SiteName)
}).ToList();
I think the first solution (creating the customized list class) is better, because you can also refactor your code and extract some logic to it.
I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }
I am a beginner learning linq. How to query a list object using linq
var dbUserSettings = new List<UserSetting> {
new UserSetting {
UserId = "abcxyz#123",
Applications = new List<Application> {
new Application {
ApplicationID = "application123",
ApplicationName = "ProtocolArchiving",
Settings = new List<Setting> {
new Setting {
SettingId = "setting123",
SettingKey = "RefreshInterval",
SettingValue = "20",
IsActive = "true",
UpdatedOn = "2017-06-22",
SettingLabel = "PageRefresh" } } } } },
new UserSetting {
UserId = "abcxyz#345",
Applications = new List<Application> {
new Application {
ApplicationID = "application345",
ApplicationName = "ProtocolArchiving",
Settings = new List<Setting> {
new Setting {
SettingId = "setting456",
SettingKey = "UploadSetting",
SettingValue = "20",
IsActive = "true",
UpdatedOn = "2017-06-22",
SettingLabel = "Upload" } } } } },
new UserSetting {
UserId = "abcxyz#567",
Applications = new List<Application> {
new Application {
ApplicationID = "application678",
ApplicationName = "ProtocolArchiving",
Settings = new List<Setting> {
new Setting {
SettingId = "setting789",
SettingKey = "DownloadSetting",
SettingValue = "20",
IsActive = "true",
UpdatedOn = "2017-06-22",
SettingLabel = "Download" } } } } }
};
var response = dbUserSettings.Where(e => e.UserId == userID)
.Select(dbsetting => new UserSettingViewModel
{
SettingKey = dbsetting.Applications.Single<Setting>(s=> s == )
})
.ToArray();
I am querying for settingkey which matches with my userID.
Edit:
Missed couple of things to mention. I have tried few things here
SettingKey = dbsetting.Applications.FirstOrDefault().Select(sk => sk.Settings.FirstOrDefault()?.SettingKey);
The error is as below
My application class looks like this.
public class Application
{
public string ApplicationID { get; set; }
public string ApplicationName { get; set; }
public List<Setting> Settings { get; set; }
}
As you´re just interested in the one and only Application for every UserSetting I suppose you need this:
var response = dbUserSettings.Where(e => e.UserId == userID)
.Select(dbsetting => new UserSettingViewModel
{
SettingKey = dbsetting.Applications.FirstOrDefault()?.Settings.FirstOrDefault()?.SettingKey
};
This will just return the very first (and probably only) Setting within the very first (and propbably also only) Application within every UserSetting. If any of the lists in between in empty (either Applications or Settings) the SettingKey will be null.
I have the domain classes separated from the ones I use in the views, so when retrieving data I have to map the domain classes to the view ones.
Until now this have been straight forward, but now I have a case in which I need to map parent-child classes from the domain to parent-child classes on the view.
Using foreach structures works fine, but I have quite a few linq methods that do the mapping between domain and view classes, that need to be refactored to accomodated to the new requirements and would be faster if I knew how to do it with linq. Thanks in advance.
As an example of what I'm trying to accomplish see code below:
In the repository I have the classes:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
};
public class ChildA : Parent
{
public string ChildPropertyA { get; set; }
};
public class ChildB : Parent
{
public string ChildPropertyB { get; set; }
};
Then in the UI I have the classes:
public class ParentVM
{
public int ParentIdVM { get; set; }
public string ParentNameVM { get; set; }
};
public class ChildAVM : ParentVM
{
public string ChildPropertyAVM { get; set; }
};
public class ChildBVM : ParentVM
{
public string ChildPropertyBVM { get; set; }
};
Now I will have a service class in which the methods will look like the one below:
public GetParentVMs()
{
var parents = initializeRepositoryClass();
var parentsVM = MapRepositoryToViewClasses(parents);
ShowResult(parentsVM);
}
Where:
public List<Parent> initializeRepositoryClass()
{
var parents = new List<Parent>(){
new ChildA(){ParentId=1, ParentName="Parent 1", ChildPropertyA="A"},
new Parent(){ParentId=2, ParentName="Parent 2"},
new ChildB(){ParentId=3, ParentName="Parent 3", ChildPropertyB="B"},
};
return parents;
}
private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
{
var parentsVM = new List<ParentVM>();
foreach (var item in parents)
{
if (item is ChildA)
{
var itemVM = item as ChildA;
parentsVM.Add(
new ChildAVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName, ChildPropertyAVM = itemVM.ChildPropertyA }
);
}
else if (item is ChildB)
{
var itemVM = item as ChildB;
parentsVM.Add(
new ChildBVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName, ChildPropertyBVM = itemVM.ChildPropertyB }
);
}
else
{
var itemVM = item as Parent;
parentsVM.Add(
new ParentVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName }
);
}
}
return parentsVM;
}
private void ShowResult(List<ParentVM> parentsVM)
{
foreach (var item in parentsVM)
{
if (item is ChildAVM)
{
var ca = (ChildAVM)item;
Console.WriteLine("Child A " + ca.ChildPropertyAVM);
}
else if (item is ChildBVM)
{
var cb = (ChildBVM)item;
Console.WriteLine("Child B " + cb.ChildPropertyBVM);
}
else
{
Console.WriteLine("Parent ");
}
}
}
The code above will work, but I like to change the method MapRepositoryToViewClasses to another that uses linq, and looks a like the one below:
private List<ParentVM> MapRepositoryToViewClassesLinq(List<Parent> parents)
{
var parentsVM =
from p in parents
case
p is ChildA then select new ChildAVM() {ChildPropertyAVM = p.ChildPropertyA, ...};
else
p is ChildB then select new ChildBVM() {ChildPropertyBVM = p.ChildPropertyB, ...};
else
select new ParentVM() {ParentIdVM = p.ParentId};
return parentsVM.ToList();
}
Any ideas? Thanks.
You need some changes in your code to make it better
1) You have to introduce a factory to create VM's instances.
class VMFactory
{
public ParentVM Create(Parent obj)
{
var childA = obj as ChildA;
if (childA != null)
{
return new ChildAVM() { ParentIdVM = childA.ParentId, ParentNameVM = childA.ParentName, ChildPropertyAVM = childA .ChildPropertyA };
}
var childB = obj as ChildB;
if(childB != null)
{
return new ChildBVM() { ParentIdVM = childB.ParentId, ParentNameVM = childB.ParentName, ChildPropertyBVM = childB.ChildPropertyB };
}
return new ParentVM() { ParentIdVM = obj.ParentId, ParentNameVM = obj.ParentName };
}
}
2) Now you can simplify your code at MapRepositoryToViewClasses method
private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
{
// Factory instance can be provided by the outer scope
var factory = new VMFactory();
var parentsVM = new List<ParentVM>();
foreach (var item in parents)
{
parentsVM.Add(factory.Create(item));
}
return parentsVM;
}
3) Final step, let's use Linq to map
private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
{
// Factory instance can be provided by the outer scope
var factory = new VMFactory();
return parents.Select(factory.Create).ToList();
}
It's done
Yet another attempt to solve it
1) Create the extensions to solve common tasks.
static class Ext
{
public static ParentVM Map<TIn>(this TIn obj, Func<TIn, ParentVM> func)
where TIn : Parent
{
var source = obj as TIn;
return source != null
? func(obj)
: null;
}
}
2) Use the extension method to get VMs
private List<ParentVM> MapRepositoryToViewClassesLinq(List<Parent> parents)
{
var tmp = from p in parents
select
p.Map<ChildA>(c => new ChildAVM() { ParentIdVM = c.ParentId, ParentNameVM = c.ParentName, ChildPropertyAVM = c.ChildPropertyA }) ??
p.Map<ChildB>(c => new ChildBVM() { ParentIdVM = c.ParentId, ParentNameVM = c.ParentName, ChildPropertyBVM = c.ChildPropertyB }) ??
new ParentVM() { ParentIdVM = obj.ParentId, ParentNameVM = obj.ParentName };
return tmp.ToList();
}
If you setup mapping extensions for each type, then the mapping process becomes trivial.
SQL Fiddle: https://dotnetfiddle.net/a4eQ6S
How to map (GetParentsVM())
var parents = initializeRepositoryClass();
var parentsVM = parents.Map();
Mapping Extensions
public static class ParentMappings
{
public static ChildAVM Map(this ChildA model)
{
return new ChildAVM()
{
ParentIdVM = model.ParentId,
ParentNameVM = model.ParentName,
ChildPropertyAVM = model.ChildPropertyA,
};
}
public static ChildBVM Map(this ChildB model)
{
return new ChildBVM()
{
ParentIdVM = model.ParentId,
ParentNameVM = model.ParentName,
ChildPropertyBVM = model.ChildPropertyB,
};
}
public static ParentVM Map(this Parent model)
{
if (model is ChildA)
return ((ChildA)model).Map();
else if (model is ChildB)
return ((ChildB)model).Map();
else
return new ParentVM()
{
ParentIdVM = model.ParentId,
ParentNameVM = model.ParentName,
};
}
public static List<ParentVM> Map(this List<Parent> parents)
{
return parents.Select(p => p.Map()).ToList();
}
}