I have a collection of items coming from a database which has a parentid value or null.
Here is my class design:
public class Item
{
public int id{get;set;}
public string Name{get;set;}
public int? ParentId{get;set;}
public List<Item> SubItems{get;set;}
}
I want to build a hierarchical structure of Items from the collection. Assume a collection is 100 items from which I need to construct the structure based on the ParentId mapping.
I tried this post Recursive Hierarchical Joins in C# and LINQ
but it gives me an error if ParentId is null.
Also tried Build tree type list by recursively checking parent-child relationship C# , but this solution also does not work for me.
How do I achieve this?
You could use this approach:
Get all the items from the database (without filling the SubItems).
Build a Lookup<int?,Item> of parent ids and items with that parent id.
Loop through the items and associate each item with the subitems using the lookup.
Code:
var items = // get from the database... (e.g. as a list)
var lookup = items.ToLookup(x => x.ParentId);
foreach (var item in items)
item.SubItems = lookup[item.Id].ToList();
As #EamonNerbonne commented below, you can get the root elements as well, if you need to:
var roots = lookup[null].ToList();
Using this Node class you can simply do this:
var flatListOfItems = GetItemsFromDatabase();
var rootNodes =Node<Item>.CreateTree(flatListOfItems, i => i.id, i => i.ParentId);
Your items doesn't need the subitems anymore because the Node class has a children and a descendants property. (Also ancestors, siblings, level etc.).
The CreateTree method results in 1 or more rootnodes. If you are sure that there is always 1 rootnode, you can do rootNodes.Single() to get the root.
Do you really need a setter for sub items? Also be mindful of the performance issues when you run Select* queries on SQL server.
public List<Item> SubItems{
get
{
try{
var validParents = db.items.Where(x=>x.ParentId!=null && x.ParentId.Equals(Id)); //db is your dbcontext
if(validParents !=null)
{
return validParents.ToList();
}else
{
return null;
}
catch(Exception)
{
return null;
}
}
(Note: Think of adding this to your partial entity class. Never name your entity as "Item" :).Item is a reserved word. )
Related
I have created a derived collection object to introduce some added functionality to filter the active records in the collection as shown in the below code snippet. How to achieve it as i want to just filter the same collection while keeping the original references in the filter without creating copy.
public class ExtendedTypes : List<ExtendedType>
{
public ExtendedTypes Active
{
get { return this.Where(x => x.IsActive).ToList(); } // Compile Error
}
}
Filtering an existing list
You mentioned that you wanted to just filter the existing list without keeping a copy. In this case, creating a List won't do, since creating a list from the subset will always create a new collection, not just a filter. List<T> is not a lazily-evaluated collection.
What you probably need to do is either define Active as IEnumerable<ExtendedType> and return the result of the Where directly (using LINQ's lazy implementation), or, if you're in WPF, use something like CollectionView as an additional filter on top of a collection, like this:
public ICollectionView ActiveTypes
{
get
{
if (_activeTypes == null)
{
_activeTypes = CollectionViewSource.GetDefaultView(myExtendedTypes);
_activeTypes.Filter = (type) => (type as ExtendedType).IsActive;
}
return _activeTypes;
}
}
You can now bind to ActiveTypes and get only a subset of the original list, filtered by the result of the Filter clause.
Creating a new List
However, assuming ExtendedType is a Reference type, you don't have to worry about copies of the items themselves being made by duplicating the list. If you don't mind creating a copy of the list with the same references, use my original answer:
The compiler is correct, in the sense that an ExtendedTypes is-a List<ExtendedType>, but not the other way around, and ToList() create a List<ExtendedType>.
There is, however, a simple workaround. Rather than ToList, just create a new ExtendedTypes with a constructor that initializes from a collection:
public class ExtendedTypes : List<ExtendedType>
{
public ExtendedTypes (IEnumerable<ExtendedType> items) : base(items)
{}
public ExtendedTypes Active
{
get { return new ExtendedTypes(this.Where(x => x.IsActive)); }
}
}
Here is my situation. I have 2 list of the same type. Imagine the names like these. FullList and ElementsRemoved. So in order to avoid the database roundtrip, anytime I delete an element from the fulllist I added to the list of ElementsRemoved in case of regret's user so he can revert the deletion.
I was thinking to loop inside my ElementsRemoved to insert them again into the FullList from where initially were removed.
There is any way to do this as simple with List Methods.
Something like
FullList.Insert, Add, ..... (x =>
in order to reduce line code and optimized?
Instead of deleting the item from your database consider using a flag in the table.
For example consider this entities table (written in TSQL):
CREATE TABLE Entity
(
Id INT IDENTITY PRIMARY KEY
,Name NVARCHAR(20) NOT NULL
,IsDelete BIT NOT NULL DEFAULT 0
);
This way you can set the IsDelete bit when the user deletes the entity which will prevent the data from being lost. The data can be pruned on a job in the off hours.
The would lead to only needing one list instead of keeping track of two lists.
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDelete { get; set; }
}
public static void UndoDelete(IEnumerable<Entity> fullList, int[] removedIds)
{
foreach(var entity in fullList.Where(e => removedIds.Contains(e.Id)))
{
entity.IsDelete = false;
}
}
In case you cannot modify your application.
You can simply add the entities back in.
See List(T).AddRange
var entitiesToAdd = new[] { 2, 3, 4 };
var entitiesToInsert = ElementsRemoved.Where(e => entitiesToAdd.Contains(e.Id));
FullList.AddRange(entitiesToInsert);
In your front end make a class that holds a bool and your object:
public class DelPair<T>{
public bool IsDeleted{get;set;}
public T Item{get;set;}
}
Now instead of using a list of objects use a list of DelPair<YourClass> and set IsDeleted=true when deleting.
This pattern will also allow you to track other things, such as IsModified if it comes to that.
Based on OP comment that he's using an ENTITY class and needs it to function as such:
One option is to make your DelPair class inherit ENTITY. Another may be to put implicit casting operator:
...
// not exactly sure about the signature, trial/error should do :)
public static implicit operator T(DelPair<T> pair)
{
return pair.Item;
}
Suppose you have an element having a field id which uniquely identifies it.
class Element{public int id;}
In that case you can do this
FullList.Add(ElementsRemoved.FirstOrDefault(e=>e.id==id));
In case you want to add all elements use AddRange
FullList.AddRange(ElementsRemoved);
You can use the AddRange method
FullList.AddRange(ElementsRemoved);
But consider doing this
public class YourClass
{
public string AnyValue{get;set;}
public bool IsDeleted{get;set;}
}
And you have list like this List < YourClass> FullList. Now whenever user removes any item you just set the
IsDeleted = true
of the item that is removed. This will help you in keeping just one list and adding removing from the list
I have an OrderDto class that implements the IEquatable interface. I have a list of Orders that are retrieved from the database as OrderDto objects. This list is edited by the end user... Adding new objects; Deleting existing objects; Editing existing objects. I need to determine the following:
New objects in the list;
Deleted objects in the list;
Edited objects in the list;
I have commented the following code to discuss these three goals. The first two are trivial, but the third item in my list is very vexing! I cannot seem to create it using linq. I hope that you can give me a nudge in the correct direction. Please see the following code for details.
Thank you for your help!
Mike
// Get the original list of OrderDtos before the user did anything.
var entityOrders = Mapper.Map<IList<OrderEntity>, IList<OrderDto>>(entity.Orders.ToList());
// dto.Orders is a parameter that is being used in this code. It is the list of OrderDto
// objects that the user has modified: Insert, edit, delete.
// Works properly to locate items that were deleted.
var deletedOrders = entityOrders.Except(dto.Orders);
// Works properly to locate items that were added.
var addedOrders = dto.Orders.Except(entityOrders);
// Determine the list of order objects less new orders in the list.
// The user has already deleted objects, so we don't have to worry
// about them. This list will contain only items that MAY have
// been edited.
var potentialChanges = dto.Orders.Except(addedOrders);
// ARGGG! I cannot seem to get this one to work properly! As you can see in the
// definition of the OrderDto, it implements the IEquatable interface. So, it
// should be very easy to determine which items in the dto.Orders list have been
// edited as opposed to the original version in the entityOrders list. What I
// would like to have is a list of all the OrderDto objects in the dto.Orders list
// that have been modified. Since OrderDto implements IEquatable, we should be able
// to compare OrderDto objects directly, but I have not been able get it to work.
// Please helP! :)
var changedOrders =
from dtoOrder in potentialChanges
let entityOrder = entityOrders.First(x => x.OrderId == dtoOrder.OrderId)
where entityOrder != null && !dtoOrder.Equals(entityOrder)
select dtoOrder;
Here is the definition of the OrderDto class...
public class OrderDto : IEquatable<OrderDto>
{
public long OrderId { get; set; }
public string DisplayAs { get; set; }
#region IEquatable
public override int GetHashCode()
{
// Populate with all fields so
// we can detect when an entity
// has been edited.
return
(
(int) OrderId ^
DisplayAs.GetHashCode()
);
}
public override bool Equals(object other)
{
return this.Equals(other as OrderDto);
}
public bool Equals(OrderDto other)
{
// Populate with all fields so
// we can detect when an entity
// has been edited.
return
(
other != null &&
other.OrderId == this.OrderId &&
other.DisplayAs == this.DisplayAs
);
}
#endregion
}
The easiest would probably be to contain a State property, for instance with the values New, Saved, Loaded, Modified. You may also add an IsDeleted property.
Then you implement every property set to throw a PropertyChanged event, and implement an event handler that automatically sets the State property to Modified.
Later you can easily query:
var modified = entityOrders.Where(x => x.State == EntityState.Modified);
This approach has the advantage of being very deliberate, i.e., you're being explicit and code is readable rather than trying to be hard (and writing way too much and way too complicated code).
In the same fashion you can implement a Delete() method which simply marks the entity as deleted,
and query all deleted entities as:
var deleted = entityOrders.Where(x => x.IsDeleted);
or
// ignore objects that were created, then deleted, without saving
var deleted = entityOrders.Where(x => x.IsDeleted && x.State != x.New);
and all valid data as:
var data = entityOrders.Where(x => !x.IsDeleted);
I'm trying to use a lambda expression to remove a certain object from a list, based on a value within that object. Here is my lambda:
ChartAttributes.ToList().RemoveAll(a => a.AttributeValue.Contains("PILOT"));
Here is the ChartAttributes list
IList<IChartAttribute> ChartAttributes
Here is the object ChartAttribute contained within the above list
public virtual string AttributeKey { get; set; }
public virtual string AttributeValue { get; set; }
public virtual int ChartAttributeId { get; set; }
public virtual int ChartSpecificationId { get; set; }
There is a chart attribute with its AttributeKey set to "PILOT". But this never gets removed. What am I doing wrong?
Thanks
Your code is taking an IEnumerable, copying all of its elements into a list and then removing items from that copy. The source IEnumerable is not modified.
Try this:
var list = ChartAttributes.ToList();
list.RemoveAll(a => a.AttributeValue.Contains("PILOT"));
ChartAttributes = list;
EDIT
Actually a better way, without needing to call ToList:
ChartAttributes = ChartAttributes.Where(a => !a.AttributeValue.Contains("PILOT"));
Your call to .ToList() makes a new list, and you end up removing the item from that list.
Whatever ChartAttributes is, you're not touching the contents of that.
Basically you're doing this:
var newList = ChartAttributes.ToList();
newList.RemoveAll(...);
If you were to inspect the contents of newList at this point you'd notice that your object(s) had been removed, but ChartAttributes, whatever type that is, still has those objects present.
You will have to remove the objects directly from ChartAttributes, but since you didn't say which type that is, I can't give you an example of how to do that.
If you need to remove items and save to database, you can try this sample code:
foreach (var x in db.myEntities.Where(a => a.AttributeValue.Contains("PILOT")))
db.myEntities.Remove(x);
db.SaveChanges();
It doesn't use RemoveAll, but it's another option to save the contents.
I had a similar problem and did a cast instead (as my setter for the property was internal):
((List<IChartAttribute>)ChartAttributes).RemoveAll(a => a.AttributeValue.Contains("PILOT"));
I have the following structure.
public class ToolSettings
{
public string Extension { get; set; }
public ObservableCollection<Tool> Tools { get; set; }
}
public class Tool
{
public string Name { get; set; }
public string Command { get set; }
}
// Within app code
public ObservableCollection<ToolSettings> settings { get; set; }
I'd like to grab the Tools collection from the settings collection where the Extension equals a certain string.
Below is my LINQ code, but I'm only getting one item in my collection when I know there's more. It looks like it produces a collection of a collection, which is why there's only one item.
myListBox.ItemsSource = from i in settings
where i.Extension == myExtension
select i.Tools;
EDIT:
Thanks for all the good (and quick) answers. It turns out I only need the first item, but I know that the SelectMany method will come in handy in the future. So, thanks for all the heads up. Here is the full solution I used.
myListBox.ItemsSource = (from i in settings
where i.Extension == myExtension
select i.Tools).First();
myListBox.ItemsSource = settings.Where(s => s.Extension == myExtension)
.SelectMany(s => s.Tools);
Or, if you prefer query syntax to the fluent syntax:
myListBox.ItemsSource = from s in settings
where (s.Extension == myExtension)
from t in s.Tools
select t;
That will give you an IEnumerable<ObservableCollection<Tool>>. It willprobably only have one item in it, but that item will be a ObservableCollection. If you want that collection itself, tack .First() (or .FirstOrDefault()) at the end.
If i.Extension == myExtension could find several ToolsSettings in the collection (I'm guessing not), then you will need to use .SelectMany()
Try this:
myListBox.ItemsSource = (from i in settings
where i.Extension == myExtension
from t in i.Tools
select t);
You can use .SelectMany(), but that's only really useful if you want to take multiple ToolSettings and select all of their Tools as a single collection. If Extension is unique, use .Single() to reduce the collection of a single collection to just the single collection.
The question is a little vague. You are right, you are getting a collection of a collection, in that there might only be one ToolSettings instance where Extension satisfies your criteria and because of that, since you are selecting Tools, you are getting a sequence of ObservableCollection<Tool> instances.
What it sounds like you really want is to get the sequence of all Tool instances where the condition is met. In that case, you want to use the SelectMany extension method on the Enumerable class:
myListBox.ItemsSource = settings.Where(i => i.Extension == myExtension).
SelectMany(i => i.Tools);
Or, if you prefer query syntax, you can do the following:
myListBox.ItemsSource =
from i in settings
where i.Extension == myExtension
from t in i.Tools
select t;
Which will translate to a call to SelectMany when the compiler gets done with it.