Mapping elements without creating duplicates - c#

I have two classes:
public class Element
{
public Item Item { get; set; }
}
public class Item
{
public Element Element { get; set; }
}
And I have DTO with same structure for this classes.
This method creates source data for mapping:
static Element[] CreateElements()
{
var element2 = new Element();
return new[]
{
new Element(new Item(element2)),
element2,
new Element()
};
}
Then I configuring mapping and map elements:
Mapper.CreateMap<Element, ElementDto>();
Mapper.CreateMap<Item, ItemDto>();
var elements = CreateElements();
var mappedElements = elements
.Select(_ => Mapper.Map(_, typeof(Element), typeof(ElementDto)))
.OfType<ElementDto>()
.ToArray();
After I check result of mapping:
foreach (var element in mappedElements)
{
Console.WriteLine(mappedElements.Any(e => e?.Item?.Element == element));
}
This code shows "False" three times. It follows that the "element2" from "CreateElements" was created two copies.
The same test for the source elements will return "False True False":
foreach (var element in elements)
{
Console.WriteLine(elements.Any(e => e?.Item?.Element == element));
}
As I need to configure the mapping so as not to duplicate elements? Is it possible?

I don't think it is AutoMapper issue.
You are creating three different Element items and map them to some kind of ElementDto. They are three different objects(both in terms of structure and reference), you cannot expect that if you map them to the same type, they will be equal.
If you consider your items:
var element2 = new Element();
return new[]
{
new Element(new Item(element2)),
element2,
new Element()
};
and compare them, you will see that none is equal. You haven't provided ElementDto class bu my guess is that you should implement IEquatable interface, what will ensure proper comparison(or overload operators).

This can be done manually. First, ignore the property Item to AutoMapper did not copy chain of elements:
Mapper.CreateMap<Item, ItemDto>()
.ForMember(_ => _.Element, _ => _.Ignore());
Secondly, copy the chain manually with a mark viewed items:
static IEnumerable<ElementDto> MapElements(Element[] elements)
{
var elementToDtoMap = new Dictionary<Element, ElementDto>();
foreach (var element in elements)
{
MapElement(element, null, elementToDtoMap);
}
return elementToDtoMap.Select(_ => _.Value);
}
static void MapElement(Element element, ItemDto parentItem, Dictionary<Element, ElementDto> elementToDtoMap)
{
ElementDto elementDto = null;
if (elementToDtoMap.TryGetValue(element, out elementDto))
return;
elementDto = Mapper.Map<ElementDto>(element);
elementToDtoMap.Add(element, elementDto);
if (parentItem != null)
{
parentItem.Element = elementDto;
}
if (element.Item != null)
{
MapElement(element.Item.Element, elementDto.Item, elementToDtoMap);
}
}

Related

How to Multisort a list of objects based on given settings?

The goal : you have a list of objects, you want the end user to be able to choose some objects properties to define a sort and choose sort direction for each property.
Let's take this Node object as an example:
public class Node
{
public int CustomProperty { get; set; }
public string CustomStringProperty { get; set; }
public string id;
public string parentId;
public string Speed;
public int WheelNumber;
public int SeatNumber;
public List<Node> Child { get; set; }
public Node()
{
Child = new List<Node>();
}
}
Let's say you have a list of Node you want to sort based on criterias such as speed, wheelnumber or seat number.
You will use whatever .OrderBy, .ThenByDescending and so on based on goal but the problem is it's hard coded so it's the developer who is currently managing code; the end user won't dive into code, even less each time he needs to change the criterias collection for sorting and define direction for each criteria (object property).
You could use some code helping the user to define preferences, something like this :
public static List<Node> Sort(List<Node> nodes, int level, string[] sortingPreferences)
{
// Recursively sort nodes children
foreach (var node in nodes)
node.Child = Sort(node.Child, level + 1, sortingPreferences);
// Now sort the nodes based on preference
if (nodes.Count < 2) return nodes;
if (level < sortingPreferences.Length)
{
switch (sortingPreferences[level])
{
case "SPEED": return nodes.OrderBy(node => node.Speed).ToList();
case "WHEEL_NUMBER": return nodes.OrderBy(node => node.WheelNumber).ToList();
case "SEAT_NUMBER": return nodes.OrderBy(node => node.SeatNumber).ToList();
case "SPEED - WHEEL_NUMBER": return nodes.OrderBy(node => node.Speed).ThenBy(node => node.WheelNumber).ToList();
case "SPEED - WHEEL_NUMBER - SEAT_NUMBER": return nodes.OrderBy(node => node.Speed).ThenBy(node => node.WheelNumber).ThenByDescending(node => node.SeatNumber).ToList();
// And so on...
// And so on...
}
}
// Unchanged (or nodes.OrderBy(some default order)
return nodes;
}
This code example is just here to illustrate the idiot approach in there, ie you won't cover all criterias combinations to define your multisort to apply on a list of objects and then extend this combination process to cover also directions for each of the criterias used.
The question is : How to define some sort settings/preferences in which criterias (speed, wheel number, seat number...) would be applied, along with their related sort direction, on a list of object to multisort it ?
I mean, for example, preferences could be given this way :
new List<string>[]
{
new List<string>{ "SPEED", "ASCENDING" },
new List<string>{ "WHEEL_NUMBER", "DESCENDING" },
new List<string>{ "SEAT_NUMBER", "DESCENDING" },
},
Here you have 3 criterias but the end user, through a gui, could add some more and choose direction for each of them.
Then, how to take these preferences into account to apply them on the list of objects?
Create a dictionary of delegates to supply your query with OrderBy clauses. Each delegate must accept an object and return the value to sort on.
var map = new Dictionary<string,Func<Node,IComparable>>
{
{ "PropertyA", node => node.PropertyA },
{ "PropertyB", node => node.PropertyB }
};
Then put your sorting keys into an array as well:
var sortBy = new string[] { "PropertyA", "PropertyB" };
Once you have that in order, you can sort with a simple loop by looking up the delegate for each key. You have to reverse the order of keys, since the each key processed will take precedence over the previous keys.
foreach (var sortKey in sortBy.Reverse())
{
list = list.OrderBy( map[sortKey] );
}
Here's how it looks in context with test data:
var map = new Dictionary<string,Func<Node,IComparable>>
{
{ "PropertyA", node => node.PropertyA },
{ "PropertyB", node => node.PropertyB }
};
IEnumerable<Node> list = new Node[]
{
new Node { PropertyA = 1, PropertyB = "Z" },
new Node { PropertyA = 2, PropertyB = "A" },
new Node { PropertyA = 2, PropertyB = "B" }
};
var sortBy = new string[] { "PropertyA", "PropertyB" };
foreach (var sortKey in sortBy.Reverse())
{
list = list.OrderBy( map[sortKey] );
}
foreach (var node in list)
{
Console.WriteLine("{0} {1}", node.PropertyA, node.PropertyB);
}
Output:
1 Z
2 A
2 B
Working example on DotNetFiddle
Wrap your OrderBy calls into ICommands (command pattern). First you build a commands queue where each command returns a collection, then you loop over the commands queue. If your input changes the commandslist has to be rebuilt.
You can use a fifo queue to store the commands and when an command should be removed you just dequeue until you find the command to be removed while keeping all dequeued commands to then enqueue them minus the removed command back to the queue.
Edit: you can use c# Func for the commands and a queue of keyvaluepair sorting enum and Func.
Func<List<T>,List<T>> for example and Queue<KeyValuePair<SortEnum,Func<List<T>,List<T>>>

Foreach group items from a list of objects

I need to group a big list of elements according to a certain atribute.
Is it possible in C# to do a foreach with a 'where' clause in a list of objects or is there a better way?
For example, I have 5000 records and 3 groups that separate them.
Foreach list.item where item.group = group1{
do action one for every record from group1
}
and so on...
ps.: I already have the records at this point of code so I don't think Linq would help.
You can separate a larger list into smaller ones, based on a property, by using ToLookup. The ToLookup method will produce a dictionary of lists, where the key is the property value that you are separating them by and the list contains all of the elements that match.
For example, if your objects have a CategoryID you can separate them into a dictionary of lists like this:
var smallLists = bigList.ToLookup( item => item.CategoryID, item => item );
You can then iterate them like this:
foreach (var bucket in smallLists)
{
Console.WriteLine("Bucket:");
foreach (var item in bucket)
{
Console.WriteLine("Item {0} with category {1}", item.Name, item.CategoryID);
}
}
See a working example on DotNetFiddle.
I think that you want to do is to group items of list by a Group and then create another list with each group and his items.
If that is the case, you can do something like this:
var grouped = items/*.Where(c => c.group == //desired group if want's to filter//)*/
.GroupBy(c => c.group);
var results = grouped.Select(c => new {
Group = c.Key.group,
Items = c.Select(c => new { c.PropertyOfItem1, c.PropertyOfItem2, // etc // })
});
This basic template should do what you need. You can also use a dictionary to map the groups to.
using System.Linq;
class Program
{
class Item
{
public int Key { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var actions = new Dictionary<int, Action<Item>> {
{ 1, Action1 },
{ 2, Action2 },
{ 3, Action3 }
};
var items = new List<Item>();
foreach (var group in items.GroupBy(x => x.Key))
{
var action = actions[group.Key];
foreach (var item in group)
{
action(item);
}
}
}
static void Action1(Item item)
{
}
static void Action2(Item item)
{
}
static void Action3(Item item)
{
}
}

Using same method on different classes

I'm created a method that separate the data for an SQLite database into 3 categories:
Modified (variables in list_1 that are not equals to the list_2 ones)
Created (variables in list_1 that are not found in list_2)
Deleted (list_2 variables that are not existing anymore in list_1)
sidenote: list_2 is a backup of list_1 before any modification
The problem with this code is that I can use it only on one class. If I want a second class, then I have to write down the same code again with minor changes. I have now 3 classes, but in the future, I probably want more. It'll be pretty time consuming if I try to write down over and over with every single class, so I posted this question for any suggestion. Also because I didn't find any articles where it uses lambda expressions.
public class Stats
{
public int id { get; set; }
public string name { get; set; }
}
public class FactStats : Stats
{
public string tag { get; set; }
public float balance { get; set; }
public FactStats ShallowCopy()
{
return (FactStats)this.MemberwiseClone();
}
}
List<FactStats> Factions = new List<FactStats>();
List<FactStats> SavedFactions = new List<FactStats>();
void SavetoDatabase()
{
//1. Separate Data
List<FactStats> F_JoinedList = new List<FactStats>();
List<int> F_Modify = new List<int>();
List<int> F_Create = new List<int>();
List<int> F_Delete = new List<int>();
//Modified Objects
F_JoinedList = Factions.Where(n => SavedFactions.Any(o => o.id == n.id)).ToList();
foreach (FactStats f in F_JoinedList)
{
FactStats fs = SavedFactions.Single(x => x.id == f.id);
if (!f.CompareEquals(fs))
F_Modify.Add(f.id);
}
//Created Objects
foreach (FactStats f in Factions)
{
bool vane = Convert.ToBoolean(SavedFactions.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Create.Add(f.id);
}
//Deleted Objects
foreach (FactStats f in SavedFactions)
{
bool vane = Convert.ToBoolean(Factions.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Delete.Add(f.id);
}
...
}
I've tried to do it with reflection, not much success. Probably because of my lack of experience.
CompareEquals extensive method (at the Modified Objects) is a third party code that compare two objects of the same class using reflection.
The best way to use one method on different classes is to use Generic method. Since classes are different they should conform to the common interface, for example IUniqueIdentifiable should have "id" property.
You need to create method:
void Save<T>(List<T> saved, List<T> modified) where T: IUniqueIdentifiable
{
List<T> F_JoinedList = new List<T>();
List<int> F_Modify = new List<int>();
List<int> F_Create = new List<int>();
List<int> F_Delete = new List<int>();
//Modified Objects
F_JoinedList = modified.Where(n => saved.Any(o => o.id == n.id)).ToList();
foreach (T f in F_JoinedList)
{
T fs = saved.Single(x => x.id == f.id);
if (!f.CompareEquals(fs))
F_Modify.Add(f.id);
}
//Created Objects
foreach (T f in modified)
{
bool vane = Convert.ToBoolean(saved.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Create.Add(f.id);
}
//Deleted Objects
foreach (T f in saved)
{
bool vane = Convert.ToBoolean(modified.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Delete.Add(f.id);
}
...
}
public interface IUniqueIdentifiable
{
id {get;}
}
There are tons of articles how to create Generic method, you can find one sample below
http://www.informit.com/articles/article.aspx?p=605369&seqNum=4

Mapping child classes with parent injected in the constructor using AutoMapper

I have a following class structure:
class SrcChild
{
public bool SomeProperty { get; set; }
}
class SrcParent
{
public IEnumerable<SrcChild> Children { get; set; }
}
so the SrcParent has a collection of SrcChild objects.
Now I want to map an instance of SrcParent to DstParent. Here are the destination classes:
class DstChild
{
public bool SomeProperty { get; set; }
public DstChild(DstParent parent)
{
if (parent == null)
throw new ArgumentNullException();
}
}
class DstParent
{
public IEnumerable<DstChild> Children { get; set; }
}
The DstParent has a collection of DstChild objects, that use constructor injection to keep a reference to their parent.
Using AutoMapper, I tried the following:
class Program
{
static void Main(string[] args)
{
/* mapping configuration */
Mapper.CreateMap<SrcChild, DstChild>()
.ConstructUsing(
resolutionContext => new DstChild((DstParent)resolutionContext.Parent.DestinationValue));
Mapper.CreateMap<SrcParent, DstParent>();
/* source parent object with two children */
var srcParent = new SrcParent
{
Children = new[] { new SrcChild(), new SrcChild() }
};
/* throws an exception */
var dstParent = Mapper.Map<DstParent>(srcParent);
Console.ReadKey();
}
}
The main part here is the AutoMapper configuration where I am trying to extract reference to the produced DstParent from mapping context. This doesn't work (the (DstParent)resolutionContext.Parent.DestinationValue is null), but maybe I'm completely missing a point here?
Another idea I had was to use a function to create the child values, something like this:
class Program
{
/* Should produce value for DstParent.Children */
private static IEnumerable<DstChild> MakeChildren(SrcParent src /*, DstParent dstParent */)
{
var result = new List<DstChild>();
// result.Add(new DstChild(dstParent));
return result;
}
static void Main(string[] args)
{
/* mapping configuration */
Mapper.CreateMap<SrcChild, DstChild>();
Mapper.CreateMap<SrcParent, DstParent>()
.ForMember(dst => dst.Children,
opt => opt.MapFrom(src => MakeChildren(src /*, How to obtain a reference to the destination here? */)));
/* source parent object with two children */
var srcParent = new SrcParent
{
Children = new[] { new SrcChild(), new SrcChild() }
};
var dstParent = Mapper.Map<DstParent>(srcParent);
Console.ReadKey();
}
}
but I don't know how (if even possible at all) to get reference to the DstParent object produced by the Mapper.
Does anyone have an idea how to do this or should I rather think of dropping this design altogether and get rid of the parent reference? Thanks in advance.
Ok, a solution that I found is not pretty, but it works:
class Program
{
static IEnumerable<DstChild> MakeChildren(IEnumerable<SrcChild> srcChildren, DstParent dstParent)
{
var dstChildren = new List<DstChild>();
foreach (SrcChild child in srcChildren)
{
var dstChild = new DstChild(dstParent);
Mapper.Map(child, dstChild);
dstChildren.Add(dstChild);
}
return dstChildren;
}
static void Main(string[] args)
{
Mapper.CreateMap<SrcChild, DstChild>();
Mapper.CreateMap<SrcParent, DstParent>()
/* Ignore Children property when creating DstParent*/
.ForMember(dst => dst.Children, opt => opt.Ignore())
/* After mapping is complete, populate the Children property */
.AfterMap((srcParent, dstParent) =>
{
dstParent.Children = MakeChildren(srcParent.Children, dstParent);
});
var source = new SrcParent
{
Children = new[]
{
new SrcChild() {SomeProperty = true},
new SrcChild() {SomeProperty = false}
}
};
var destination = Mapper.Map<DstParent>(source);
Console.ReadKey();
}
}
The destination has children initialized, with SomeProperty properly assigned by AutoMapper. Please let me know if you find a better looking solution.

How to modify some elements of a list using linq in C#?

I have this class:
public class Document
{
public string ID { get; set; }
public string Type { get; set; }
public bool Checked {get;set; }
}
I create a set of 10 elements using Enumerable.Repeat static method:
var list = Enumerable.Repeat<Document>(
new Document
{
ID="1",
Type ="someType"
Checked = true
}, 10).ToList<Document>();
These creates 10 Documents all with the same properties. I need that some of them, for instance, the first 5 elements of the list list have the Checked property to false.
How can I achieve it, using as possible linq?
Note that your original sample has a bug because it's creating a 10 element List<Document> that only has 1 actual Document object. Here is a better way of doing it
Enumerable
.Range(1, 10)
.Select(i =>
new Document() {
ID = "1",
Type = "someType",
Checked = i <= 5
})
.ToList();
EDIT
Changed the code to be simpler. My original response was to editing an already existing list for which the following can be done
list.Take(5).ForEach(x => { x.Checked = false });
Note that you may have to define a simple ForEach method for this operation. If you don't have one defined here is an example
static class Extensions {
internal static void ForEach<T>(this IEnumerable<T> e, Action<T> action) {
foreach (var item in e) {
action(item);
}
}
}
Alternate idea to accomplish what you're asking for (also populates your ID column with something other than "1"):
var list = Enumerable.Range(1, 10)
.Select(i => new Document
{
ID = i.ToString(),
Type = "someType",
Checked = (i > 5)
}).ToList();

Categories

Resources