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();
}
}
Related
EF Core modifies objects it tracks by setting keys and maintaining navigation properties.
As an example of why this may be a problem, let's say you start a task which will add an entity to a DbContext. If you then immediately enumerate some navigation properties of that same entity without waiting for the task to finish, you can get an InvalidOperationException. When the entity got tracked in the other thread, it might have picked up some other data from the context and changed the collection.
I'd like to avoid those issues by cloning the entities going into and out of EF Core. But I also don't wish to write a ton of unmaintainable and error-prone code for cloning the entities by hand.
Here's how far I got:
public static TEntity CloneEntity<TEntity>(this DbContext context, TEntity entity)
where TEntity : class
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (entity == null)
throw new ArgumentNullException(nameof(entity));
// A map for keeping track of already-cloned objects for circular references.
var map = new Dictionary<object, object>(ReferenceEqualityComparer.Instance);
return (TEntity)Recurse(context.Entry(entity));
object Recurse(EntityEntry entry)
{
if (map.TryGetValue(entry.Entity, out var clone))
return clone;
clone = entry.CurrentValues.ToObject();
map.Add(entry.Entity, clone);
// TODO: Recursively clone and set all the navigation properties.
return clone;
}
}
I can probably figure out how to solve the TODO-bit by reflection, but EF Core should have done all of that already and it should already have compiled methods for efficiently setting navigation properties. Is there a way to use those, similar to entry.CurrentValues.ToObject()?
As far as you are able to deliver solution by your own(I guess) I just put in here something I spent time on. It is a working draft. It may or may not satisfy your needs and may help others to find at least something as a solution.
I like expresions, so it was done using them, but still you can go purely with reflection.
class Program
{
static void Main(string[] args)
{
#region Create data
using (var context = new ConsoleDbContext())
{
context.Add(new Person()
{
Id = 1,
Name = "Relatively Random",
Animals = new List<Animal>
{
new Cat {Id= 1, Name = "Relatively" },
new Dog {Id = 2, Name = "Random" }
},
Address = new Address
{
Street = "Sesame street",
City = "London",
Country = "United Kingdom"
}
}).State = EntityState.Added;
context.Add(new Person()
{
Id = 2,
Name = "Random Relatively",
Animals = new List<Animal>
{
new Cat {Id= 3, Name = "Relatively" },
new Dog {Id = 4, Name = "Random" }
}
});
#endregion
#region Save data
context.SaveChanges();
#endregion
// Create empty clone map
IDictionary<EntityEntry, object> cloneMap = new Dictionary<EntityEntry, object>(context.ChangeTracker.Entries().Count());
Func<Dog> getDogFunc = () => context.Set<Dog>().Last();
// Get entry we want to clone
var dog = context.Entry(getDogFunc());
// Clone entry
Dog dogClone = CloneEntity<Dog>(dog, cloneMap);
// Change name of tracked entity
dog.Entity.Name = "New name";
// Compare against entity entry entity value and entity itself(which is non sense as those are same reference, but just to be sure it works
var result = (dogClone.Name == dog.Entity.Name) || (dogClone.Name == getDogFunc().Name);
}
}
public static TEntity CloneEntity<TEntity>(EntityEntry entityEntry, IDictionary<EntityEntry, object> cloneMap)
where TEntity : class
{
if (entityEntry == null)
{
throw new ArgumentNullException(nameof(entityEntry));
}
if (cloneMap is null)
{
throw new ArgumentNullException(nameof(cloneMap));
}
//Try get existing clone
if (cloneMap.TryGetValue(entityEntry, out var clone))
{
return (TEntity)clone;
}
var cloneMapConstant = Expression.Constant(cloneMap);
var entityEntryType = typeof(EntityEntry);
var bindings = new List<MemberBinding>(typeof(TEntity).GetProperties().Length);
var entryParameter = Expression.Parameter(entityEntryType, "entry");
// Create property binding expressions e.g. Name = entry.Entity.Name
bindings.AddRange(CreatePropertyBinding<TEntity>(ref entityEntry, ref entryParameter));
// Create reference binding expression e.g. Owner = new Owner() { ..initialization... }
bindings.AddRange(CreateReferenceBinding(entityEntry, cloneMapConstant));
// Get existing clone or create new one based on binding expressions created earlier
var result = GetOrCreateNewEntity<TEntity>(ref entityEntry, ref entryParameter, ref bindings);
cloneMap.Add(entityEntry, result);
return result;
}
private static TEntity GetOrCreateNewEntity<TEntity>(ref EntityEntry entityEntry, ref ParameterExpression entryParameter, ref List<MemberBinding> bindings)
where TEntity : class
{
// new TEntity()
var newEntity = Expression.New(typeof(TEntity));
// new TEntity() { ...initialization... }
var initilizer = Expression.MemberInit(newEntity, bindings);
// entry => new TEntity() { ...initialization... }
var lambda = Expression.Lambda(initilizer, entryParameter);
// Compile expression to function
var function = lambda.Compile();
// Invoke function and cast result
var result = (TEntity)function.DynamicInvoke(entityEntry);
// Return result
return result;
}
private static IEnumerable<MemberBinding> CreateCollectionBinding(EntityEntry entityEntry, ConstantExpression cloneMapConstant)
{
// Get all collection properties
var collections = entityEntry
.Collections;
//.Where(n => n.IsLoaded);
var cloneEntityMethod = typeof(Program).GetMethod(nameof(CloneEntity));
foreach (var collection in collections)
{
// ICollection<SomeType>
var clrType = collection.Metadata.ClrType;
var elementType = clrType.GenericTypeArguments[0];
var hashSetType = typeof(HashSet<>).MakeGenericType(elementType);
// new HashSet<SomeType>()
var hashSet = Expression.New(hashSetType);
var converted = Expression.TypeAs(hashSet, clrType);
var result = Expression.Bind(collection.Metadata.PropertyInfo, converted);
yield return result;
}
}
private static IEnumerable<EntityEntry> GetCollectionItemEntries(DbContext context, CollectionEntry collection)
{
foreach (var item in collection.CurrentValue)
{
yield return context.Entry(item);
}
}
private static IEnumerable<MemberBinding> CreateReferenceBinding(EntityEntry entityEntry, ConstantExpression cloneMapConstant)
{
var references = entityEntry
.References
.Where(n => n.IsLoaded);
var result = new List<MemberBinding>(references.Count());
var cloneEntityMethod = typeof(Program).GetMethod(nameof(CloneEntity));
foreach (var reference in references)
{
var referenceEntityEntry = Expression.Constant(reference.EntityEntry.Context.Entry(reference.TargetEntry.Entity));
var genericCloneEntityMethod = cloneEntityMethod.MakeGenericMethod(reference.Metadata.ClrType);
var callCloneEntityMethod = Expression.Call(null, genericCloneEntityMethod, new Expression[] { referenceEntityEntry, cloneMapConstant });
yield return Expression.Bind(reference.Metadata.PropertyInfo, callCloneEntityMethod);
}
}
private static IEnumerable<MemberAssignment> CreatePropertyBinding<TEntity>(ref EntityEntry entityEntry, ref ParameterExpression parameter)
{
var entityEntryType = parameter.Type.GetProperty(nameof(EntityEntry.Entity), typeof(object));
var entityProperty = Expression.MakeMemberAccess(parameter, entityEntryType);
var convertedEntity = Expression.Convert(entityProperty, typeof(TEntity)); ;
var result = entityEntry
.Properties
.Where(p => p.Metadata.IsShadowProperty() == false)
.Select(p => Expression.Bind(p.Metadata.PropertyInfo, Expression.MakeMemberAccess(convertedEntity, p.Metadata.PropertyInfo)));
return result;
}
}
public class ConsoleDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("inmemory");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Owned<Address>();
modelBuilder.Entity<Person>(person =>
{
person.HasKey(e => e.Id);
person.Property(e => e.Name);
person.OwnsOne(e => e.Address);
});
modelBuilder.Entity<Animal>(animal =>
{
animal.HasKey(e => e.Id);
animal.HasDiscriminator();
animal.Property(e => e.Name);
animal.HasOne(e => e.Owner)
.WithMany(e => e.Animals);
});
modelBuilder.Entity<Dog>(dog =>
{
dog.HasBaseType<Animal>();
});
modelBuilder.Entity<Cat>(cat =>
{
cat.HasBaseType<Animal>();
});
}
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public abstract class Animal : Entity<int>
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public class Cat : Animal
{
}
public class Dog : Animal
{
}
public abstract class Entity<TKey>
{
public TKey Id { get; set; }
}
public class Person : Entity<int>
{
public Person()
{
//Animals = new HashSet<Animal>();
}
public string Name { get; set; }
public Address Address { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
}
I am trying to create an json data similar to this https://cdn.jsdelivr.net/gh/highcharts/highcharts#v7.0.0/samples/data/world-mortality.json
The current output I am getting is like this. The model name is being displayed and child does not seems to work. is t possible to remove the model field name on the serialize?
[{Name:"ABC",
Firstchild:[{Name:"AIR IMPORT",
Secondchild:[{NAME:"SCMBOA00052997",VALUE:69.7500},
{NAME:"SH123",VALUE:-100.0000},
{NAME:"SH456",VALUE:50.0000},
{NAME:"SH789",VALUE:150.0000}]
}]
}{Name:"DEF",
Firstchild:[{Name:"AIR IMPORT",
Secondchild:[{NAME:"SCMBOA00052997",VALUE:69.7500},
{NAME:"SH111",VALUE:-10.0000},
{NAME:"SH222",VALUE:80.0000},
{NAME:"SH333",VALUE:160.0000}]
}]
}]
What I need is like this
{
"ABC": {
"AIR IMPORT":{
"SH123": -100.0000,
"SH456": 50.0000,
"SH789": 150.0000
}
},
"DEF": {
"AIR IMPORT":{
"SH111": -10.0000,
"SH222": 80.0000,
"SH333": 160.0000
}
}
}
MODEL
public class ParentTreemap
{
public string Name { get; set; }
public List<FirstChildTreemap> Firstchild { get; set; }
}
public class FirstChildTreemap
{
public string Name { get; set; }
public List<SecondChildTreemap> Secondchild { get; set; }
}
public class SecondChildTreemap
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public decimal Value { get; set; }
}
Controller
var parent = treemaplist.Where(s => s.Parent == 0);
List<ParentTreemap> plist = new List<ParentTreemap>();
foreach (var item in parent)
{
var firstchild = treemaplist.Where(s => s.Parent == item.Id && s.Value==0);
List<FirstChildTreemap> flist = new List<FirstChildTreemap>();
foreach (var fitem in firstchild)
{
var secondchild = treemaplist.Where(s => s.Parent == fitem.Id && s.Value!=0);
List<SecondChildTreemap> slist = new List<SecondChildTreemap>();
foreach (var sitem in secondchild)
{
SecondChildTreemap model = new SecondChildTreemap();
model.Name = sitem.Name;
model.Value = sitem.Value;
slist.Add(model);
}
FirstChildTreemap child = new FirstChildTreemap();
child.Name = fitem.Name;
child.Secondchild = slist;
flist.Add(child);
}
ParentTreemap pmodel = new ParentTreemap();
pmodel.Name = item.Name;
pmodel.Firstchild = flist;
plist.Add(pmodel);
}
var stringWriter = new StringWriter();
var serializer = new JsonSerializer();
using (var writer = new JsonTextWriter(stringWriter))
{
writer.QuoteName = false;
serializer.Serialize(writer, plist);
}
ViewData["result"] = stringWriter;
I have an array of strings separated by "!". I am trying to break that string up and create a tree hierarchy recursively in my custom class called PivotGroup. For example, what I am aiming at is to break up string array
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"}
Into the PivotGroup class such as PivotGroup contains ChildGroups[] that embed the array strings.
So for example:
PivotGroup pgGroup = new PivotGroup();
pgGroup.ChildGroups[0] = PivotGroup[]; // Key:Book Level 3 Value: "AAA"
Now within Book Level 3 ChildGroups I need to set Book Level 4 which value is "EEE" and within the ChildGroups of "EEE" I would need to create another childGroup array which size in the case would be 3 called Book Level 5 and set another PivotGroup for each of following 15712, 15722, 13891
Here is my PivotGroup Class and embedded class Objects:
public class PivotGroup
{
public PivotGroup() { }
public PivotGroup(PivotGroupKey groupKey, PivotRow data, PivotGroup[] childGroups, bool leaf, int groupLevel)
{
GroupKey = groupKey;
Data = data;
ChildGroups = childGroups;
Leaf = leaf;
GroupLevel = groupLevel;
}
public PivotGroupKey GroupKey { get; private set; }
public PivotRow Data { get; private set; }
public PivotGroup[] ChildGroups { get; set; }
public bool Leaf { get; private set; }
public int GroupLevel { get; private set; }
public override string ToString()
{
return GroupKey + ", GroupLevel: " + GroupLevel + ", Children: " +
ChildGroups.Length + (Leaf ? " (Leaf)" : "");
}
}
public class PivotGroupKey
{
public PivotGroupKey()
{
}
public PivotGroupKey(string keyGroup, string keyValue)
{
if(keyGroup != null)
KeyGroup = string.Intern(keyGroup);
if (keyValue != null)
KeyValue = string.Intern(keyValue);
}
public string KeyGroup { get; private set; }
public string KeyValue { get; private set; }
public override string ToString()
{
return KeyGroup + ": " + KeyValue;
}
}
public class PivotRow
{
public PivotRow()
{
}
public PivotRow(string key, params object[] data) : this(key, true, data) { }
public PivotRow(string key, bool entitled, params object[] data)
{
Data = data;
Key = null;
Entitled = entitled;
}
public object[] Data { get; private set; }
public bool Entitled { get; private set; }
public string Key { get { return null; } set { } }
}
Main program I tried:
public class BookLevels
{
public string Root { get; set; }
public string BookLevel2 { get; set; }
public string BookLevel3 { get; set; }
public string BookLevel4 { get; set; }
public string BookLevel5 { get; set; }
}
class Program
{
static void BuildTree(string[] paths)
{
var BookPaths = paths.Select(x => x.Split('!'))
.Select(x => new BookLevels
{
Root = x[0],
BookLevel2 = x[1],
BookLevel3 = x[2],
BookLevel4 = x[3],
BookLevel5 = x[4]
}).GroupBy(z => new { z.BookLevel3, z.BookLevel4 }).ToArray();
var BookLevel3Cnt = BookPaths.Select(q => q.Key.BookLevel3).Count();
PivotGroup root = new PivotGroup(
new PivotGroupKey("Total", ""),
new PivotRow(null, new string[8]),
new PivotGroup[BookLevel3Cnt], false, 0);
foreach (var booklevel3 in BookPaths)
{
AddChildren(root, booklevel3);
}
}
private static void AddChildren(PivotGroup root, IGrouping<object, BookLevels> booklevel, int index = 0)
{
root.ChildGroups[index] = new PivotGroup(
new PivotGroupKey("Book Level " + (index + 3).ToString(), booklevel.Key.ToString()),
new PivotRow(null, new string[8]),
AddChildren(root, booklevel[index], index + 1), false, 0);
}
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891",
"ROOT!ZZZ!AAA!DDD!15712",
"ROOT!ZZZ!AAA!DDD!15722",
"ROOT!ZZZ!AAA!DDD!13891",
"ROOT!ZZZ!BBB!DDD!15812",
"ROOT!ZZZ!BBB!DDD!15822",
"ROOT!ZZZ!BBB!DDD!13891",
};
BuildTree(paths);
Console.WriteLine();
Console.ReadLine();
}
I think my issue might be the way I am creating the Linq statement that breaks up the string, since I'm not sure how to progress thru it recursively.
I'm not sure what goes into which property. Also, for sake of simplicity and to be able to concentrate on the recursive algorithm, I redefine the group class like this (it does not mean that you have to change your class, instead, adapt my algorithm):
public class PivotGroup
{
public string Key { get; set; }
public List<PivotGroup> ChildGroups { get; } = new List<PivotGroup>();
public override string ToString() => Key; // Makes debugging easier.
}
The idea is that the values of the path go into the key. I made ChildGroups a list to be able to add children successively. My BuildTree returns the root
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
AddChildren(root, path.Split('!').Skip(1).ToList());
}
return root;
}
The recursive part goes into AddChildren. I convert the path into a List<string> to be able to remove the added part. AddChildren assumes that the first item in path is the first child to be added.
static void AddChildren(PivotGroup group, List<string> path)
{
string key = path[0];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
if (path.Count > 1) {
path.RemoveAt(0); // Remove the added child key and add the rest recursively.
AddChildren(child, path);
}
}
We add children by walking down the tree and adding new children if necessary.
This prints the tree recursively:
private static void PrintTree(PivotGroup group, int level)
{
Console.WriteLine(new String(' ', 2 * level) + group.Key);
foreach (PivotGroup child in group.ChildGroups) {
PrintTree(child, level + 1);
}
}
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
...
};
PivotGroup root = BuildTree(paths);
PrintTree(root, 0);
Console.ReadKey();
We could also use a loop instead of doing a recursion, since we add one branch at a time:
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
PivotGroup group = root;
string[] pathElements = path.Split('!');
for (int i = 1; i < pathElements.Length; i++) { // Element [0] is ROOT, we skip it.
string key = pathElements[i];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
group = child;
}
}
return root;
}
List<T>.FindIndex is inefficient for large lists. If you have large data sets and the order does not matter, switch to Dictionary<string, PivotGroup>. If you need the data to be sorted, use SortedDictionary<string, PivotGroup>.
Here is some simple recursive code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"};
List<List<string>> inputData = paths.Select(x => x.Split(new char[] {'!'}).ToList()).ToList();
Node root = new Node();
Node.ParseTree(root, inputData);
}
}
public class Node
{
public string name { get; set; }
public List<Node> children { get; set; }
public static void ParseTree(Node parent, List<List<string>> inputData)
{
parent.name = inputData.First().FirstOrDefault();
var groups = inputData.Select(x => x.Skip(1)).GroupBy(x => x.Take(1).FirstOrDefault());
foreach (var group in groups)
{
if (group.Key != null)
{
if (parent.children == null) parent.children = new List<Node>();
Node newNode = new Node();
parent.children.Add(newNode);
ParseTree(newNode, group.Select(x => x.Select(y => y).ToList()).ToList());
}
}
}
}
}
How it would be possible to get a parent when tree structure is like this:
public class TreeModel
{
public int ID { get; set; }
public List<TreeModel> Children { get; set; }
}
Let's say we can't add a parent element item to this class (public TreeModel Parent { get; set; }).
Edit
How to get element m22 (ID=22) parent m2 (ID=2) from the m1? I thought we could iterate through m1 and somehow return parent when condition is right.
var m1 = new TreeModel() { ID = 1 };
var m2 = new TreeModel() { ID = 2 };
var m21 = new TreeModel() { ID = 21 };
var m22 = new TreeModel() { ID = 22 };
var m3 = new TreeModel() { ID = 3 };
m1.Children.Add(m2);
m2.Children.Add(m21);
m2.Children.Add(m22);
m1.Children.Add(m3);
var parent = m1.GetParent(p => p.ID == 22); //<-- How?
public IEnumerable<TreeModel> GetAllDescendants(IEnumerable<TreeModel> rootNodes)
{
var descendants = rootNodes.SelectMany(_ => GetAllDescendants(_.Children));
return rootNodes.Concat(descendants);
}
public static TreeModel GetParent(this TreeModel rootNode, Func<TreeModel, bool> childSelector)
{
var allNodes = GetAllDescendants(new [] { rootNode });
var parentsOfSelectedChildren = allNodes.Where(node => node.Children.Any(childSelector));
return parentsOfSelectedChildren.Single();
}
m1.GetParent(_ => _.ID == 22);
Obtain a flat list of all nodes
Search this list for the node whose direct children contains m22
Use this code pattern. It simplifies the code because you don't have to explicitly add nodes to the children and each node knows who its parent is and who its children are. Also it is all type safe.
class Program
{
static void Main(string[] args)
{
var m1=new TreeModel() { ID=1 };
var m2=new TreeModel(m1) { ID=2 };
var m21=new TreeModel(m2) { ID=21 };
var m22=new TreeModel(m2) { ID=22};
var m3=new TreeModel(m1) { ID=3 };
var item=m1.RecursiveFind((p) => p.ID==22);
var parent=item.Parent;
// parent.ID == 2
var root=item.Root;
// root.ID == 1;
}
}
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
When you derive from Tree<T>, you create custom tree structures that you design what the node class is (TreeModel here) and how to handle parents, children and siblings if needed.
What about:
public class SaneTreeModel: TreeModel
{
public SaneTreeModel Parent { get; set; }
}
}
I would approach this by first finding the first element that satisfies the condition (ID == 22 in your example) and then finding the parent of this element. Not the best solution but maybe you will need them separately for something else.
public TreeModel GetParent(Func<TreeModel, bool> function)
{
return GetParent(Where(function));
}
private TreeModel GetParent(TreeModel treeModel)
{
if (Children == null) return null;
if (Children.Contains(treeModel)) return this;
foreach (TreeModel child in Children)
{
TreeModel result = child.GetParent(treeModel);
if (result != null)
return result;
}
return null;
}
private TreeModel Where(Func<TreeModel, bool> function)
{
if (Children == null) return null;
foreach (TreeModel child in Children)
{
if (function(child))
return child;
TreeModel result = child.Where(function);
if (result != null)
return result;
}
return null;
}
If you put this code block in your TreeModel class, the example you provided will return m2
Absolutely not, with a child node like that you can't get its parent. Simply because there isn't any reference to it.
To get the parent of the node, you have to either add the parent field or save the reference in somewhere else (by a variable or something).
EDIT
#Zulis If you search from the root node, you can definitely find the node you want. But as I said, with just the child node you can't do that.
But I think you should avoid searching because that would be slow
I'd like to write a generic extension method to link two classes/objects
Classes:
public class Model1
{
public Guid Model2ID { get; set; }
public Model2 Model2 { get; set; }
// .. other insignificant properties to this question
}
public class Model2
{
public Guid ID { get; set; }
}
I'm trying to write a method that looks something like:
public static class ObjectExtensions
{
public static Void LinkTo<T,U>(
this T m1,
IEnumerable<U> m2,
m1p // expression to select what property on m1 is populated
Action<bool,T,IEnumerable<U>> expression)
{
// not sure about this part at all
m1p = u2.FirstOrDefault(expression);
}
}
Usage:
var listOfModel2 = //....
Model1.LinkTo(listOfModel2, m => m.Model2, (m1,m2) m1.Model2Id == m2.ID);
As we had discussed in Chat, I'd recommend a light-weight version of EF Context (with reflection). This is completely custom and dynamic, you simply need to use KeyAttribute and ForeignKeyAttribute on your models and add the models to your this custom Context. I only wired up the Add, as the rest you should be able to figure out on your own.
Classes:
public class Staff
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Contact")]
public Guid ContactId { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Dog")]
public Guid DogId { get; set; }
public Dog Dog { get; set; }
}
public class Dog
{
[Key]
public Guid Id { get; set; }
}
Context:
public class Context
{
//Add as many of these as you want. Don't forget to make public properties for them!
private ObservableCollection<Staff> _staffs = new ObservableCollection<Staff>();
private ObservableCollection<Contact> _contacts = new ObservableCollection<Contact>();
private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>();
private List<IForeignKeyRelation> _relations = new List<IForeignKeyRelation>();
public Context()
{
var observables = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.ToList();
foreach(var observable in observables)
{
var notifyCollection = observable.GetValue(this) as INotifyCollectionChanged;
if (notifyCollection != null)
{
notifyCollection.CollectionChanged += CollectionChanged;
Type principalType = observable.FieldType.GetGenericArguments()[0];
var relations = principalType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(p => p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute != null)
.Select(p => new { PrincipalForeignKeyInfo = p, ForeignKey = p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute })
.Where(p => principalType.GetProperty(p.ForeignKey.Name) != null)
.Select(p => {
var principalForeignKeyInfo = p.PrincipalForeignKeyInfo;
var principalRelationInfo = principalType.GetProperty(p.ForeignKey.Name);
var dependantType = principalRelationInfo.PropertyType;
var dependantKeyProperties = dependantType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(dp => dp.GetCustomAttribute(typeof(KeyAttribute)) as KeyAttribute != null)
.ToList();
var dependantKeyInfo = dependantKeyProperties.FirstOrDefault();
var isValid = (dependantKeyInfo != null)
// Don't allow INT to GUID comparisons
// Keys need to be of same type;
&& (principalForeignKeyInfo.PropertyType == dependantKeyInfo.PropertyType);
return new {
IsValid = isValid,
PrincipalRelationInfo = principalRelationInfo,
DependantType = dependantType,
PrincipalCollection = observable.GetValue(this),
PrincipalForeignKeyInfo = principalForeignKeyInfo,
DependantKeyInfo = dependantKeyInfo
};
})
.Where(r => r.IsValid)
.Select(r =>
{
var relationType = typeof(ForeignKeyRelation<,>).MakeGenericType(principalType, r.DependantType);
var relation = Activator.CreateInstance(relationType) as IForeignKeyRelation;
relation.GetType().GetProperty("PrincipalCollection").SetValue(relation, observable.GetValue(this));
relation.DependantKeyInfo = r.DependantKeyInfo;
relation.PrincipalForeignKeyInfo = r.PrincipalForeignKeyInfo;
relation.PrincipalRelationInfo = r.PrincipalRelationInfo;
return relation;
})
.ToList();
_relations.AddRange(relations);
}
}
}
// Makes storing Generic types easy when the
// Generic type doesn't exist;
private interface IForeignKeyRelation
{
PropertyInfo PrincipalForeignKeyInfo { get; set; }
PropertyInfo PrincipalRelationInfo { get; set; }
PropertyInfo DependantKeyInfo { get; set; }
void Add<T>(T value);
}
// Class to hold reflected values
// Reflection
private class ForeignKeyRelation<P,D> : IForeignKeyRelation
{
// Staff.ContactId
public PropertyInfo PrincipalForeignKeyInfo { get; set; }
public Collection<P> PrincipalCollection { get; set; }
// Staff.Contact
public PropertyInfo PrincipalRelationInfo { get; set; }
// Contact.Id
public PropertyInfo DependantKeyInfo { get; set; }
public void Add<T>(T value)
{
if (value.GetType() == typeof(D))
{
var dependantKey = DependantKeyInfo.GetValue(value);
var principals = PrincipalCollection.Where(p => this.PrincipalForeignKeyInfo.GetValue(p).Equals(dependantKey))
.ToList();
foreach(var principal in principals)
{
PrincipalRelationInfo.SetValue(principal, value);
}
}
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach(var relation in this._relations)
{
foreach(var item in args.NewItems)
{
relation.Add(item);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
break;
case NotifyCollectionChangedAction.Replace:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new NotImplementedException(args.Action.ToString());
}
}
public IList<Staff> Staffs
{
get
{
return _staffs;
}
}
public IList<Contact> Contacts
{
get
{
return _contacts;
}
}
public IList<Dog> Dogs
{
get
{
return _dogs;
}
}
}
Simple example program:
public static void Main()
{
var context = new Context();
var staff = new Staff() { Id = Guid.NewGuid() };
var contact = new Contact();
contact.Id = Guid.NewGuid();
contact.Name = "Hello DotFiddle!";
staff.ContactId = contact.Id;
context.Staffs.Add(staff);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
context.Contacts.Add(contact);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
Console.WriteLine("Staff.Contact.Name: "+ staff.Contact.Name);
}
result:
staff contact is null: True
staff contact is null: False
Staff.Contact.Name: Hello DotFiddle!
This Entire Example on DotNetFiddle.net
I don't know exactly what you're going for, but one of these accomplishes it: The bottom two are 'iffy' as you can see by all the if statements. Simply put there's no way for the compiler to be able to be sure that they'll work, since you can easily pass a bad propertyExpression.
class Program
{
static void Main(string[] args)
{
var guids = Enumerable.Range(0, 10).Select(i => Guid.NewGuid()).ToList();
var m2s = guids.Select(g => new Model2 { ID = g }).ToList();
var model1 = new Model1 { Model2ID = m2s[4].ID };
model1.LinkTo(m2s, (m1, m2) => m1.Model2 = m2, (m1, m2) => m2.ID == m1.Model2ID);
var model1a = new Model1 { Model2ID = m2s[4].ID };
model1a.LinkTo(m2s, m1 => m1.Model2, m1 => m1.Model2ID, m2 => m2.ID);
var model1b = new Model1 { Model2ID = m2s[4].ID };
model1b.LinkTo(m2s, m1 => m1.Model2, (m1, m2) => m1.Model2ID == m2.ID);
}
}
public static class ObjectExtensions
{
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Action<T, U> action, Func<T, U, bool> filter)
{
if (m2s.Any(m2 => filter(m1, m2)))
{
var x = m2s.First(m2 => filter(m1, m2));
action(m1, x);
}
}
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, U, bool> filter)
{
var results = m2s.Where(m2 => filter(m1, m2));
if (!results.Any())
return;
var x = results.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
public static void LinkTo<T, U, Key>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, Key> tKey, Func<U, Key> uKey)
{
var results = Enumerable.Repeat(m1, 1)
.Join(m2s, tKey, uKey, (t, u) => u);
if(!results.Any())
return;
var x = results
.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
}
You should read about extension methods. They are called "from" object.
For example you can write generic extension method as below
class Program
{
static void Main(string[] args)
{
var listOfModel2 = new Model1();
//You can call it from "object"
listOfModel2.MyExtensionMethod();
//Or directly as it is declared
ObjectExtensions.MyExtensionMethod(listOfModel2);
}
}
public static class ObjectExtensions
{
public static void MyExtensionMethod<T>(this T t)
{
//Do somthing
}
}