How can I create a function using LINQ to get the specified node?
I'm using this code from the another web:
How can I model this class in a database?
I have in each objective record a field named "Rank". It tells me what position is. For instance:
Objective "Geometry": Rank1
|_Objective "Squares": Rank1
|_Objective "Circles": Rank2
|_Objective "Triangle": Rank3
|_Objective "Types"开发者_运维问答: Rank1
Objective "Algebra": Rank2
Objective "Trigonometry": Rank3
That rank tells me the order of the nodes. But I want to get all the rank:
Objective "Geometry": Rank1
|_Objective "Squares": Rank1 -> 1.1
|_Objective "Circles": Rank2
|_Objective "Triangle": Rank3
|_Objective "Types": Rank1 -> 1.3.1
Objective "Algebra": Rank2
Objective "Trigonometry": Rank3 -> 3
I'm using LINQ to SQL.
<TreeView Name="treeView1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type data:Objective}" ItemsSource="{Binding Path=Objectives}" >
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I need a linq function where I can get a specified node. I mean, a function which gets the node through the level (1.2), (1.3.1)
If exists, return the node, if not null.
UPDATE 1:
This is not really a function, but I realized it's better to create a getNode function.
private void AddButton_Click(object sender, RoutedEventArgs e)
{
NorthwindDataContext cd = new NorthwindDataContext();
int[] levels = LevelTextBox.Text.ToIntArray('.');
string newGroupName = NameTextBox.Text;
Objective currentObjective = null;
int? identity = null;
for (int i = 0; i < levels.Length - 1; i++ )
{
int currentRank = levels[i];
if (identity == null)
{
currentObjective = (from p in cd.Objective
where p.Level == currentRank && p.Parent_ObjectiveID == null
select p).SingleOrDefault();
}
else
{
currentObjective = (from p in cd.Objective
where p.Level == currentRank && p.Parent_ObjectiveID == identity
select p).SingleOrDefault();
}
if (currentObjective == null)
{
MessageBox.Show("Levels don't exist");
return;
}
else
{
identity = currentObjective.ObjectiveID;
}
}
if (currentObjective != null)
{
if (levels.Last() == currentObjective.Level)
{
MessageBox.Show("Level already exists");
return;
}
}
else
{
var aux = (from p in cd.Objective
where p.Parent_ObjectiveID == null && p.Level == levels.Last()
select p).SingleOrDefault();
if (aux != null)
{
MessageBox.Show("Level already exists");
return;
}
}
var newObjective = new Objective();
newObjective.Name = NameTextBox.Text;
newObjective.Level = levels.Last();
newObjective.Parent_ObjectiveID = currentObjective == null ? null : (int?)currentObjective.ObjectiveID ;
cd.Objective.InsertOnSubmit(newObjective);
cd.SubmitChanges();
}
UPDATE 2:
public Objective GetNode(params int[] indexes)
{
return GetNode(null, 0, indexes);
}
public Objective GetNode(int? parentid, int level, params int[] indexes)
{
NorthwindDataContext cd = new NorthwindDataContext();
Objective item = null;
if (indexes.Length == 0)
return null;
if (parentid == null)
{
item = (from p in cd.Objective
where p.Level == indexes[level] && p.Parent_ObjectiveID == null
select p).SingleOrDefault();
}
else
{
item = (from p in cd.Objective
where p.Level == indexes[level] && p.Parent_ObjectiveID == parentid
select p).SingleOrDefault();
}
if (item == null)
return null;
if (++level < indexes.Length)
item = GetNode(item.ObjectiveID, level, indexes);
return item;
}
Edit:
You're probably best to pass in an instance of the NorthwindDataContext versus creating a new one with each pass.
You could do this by creating a method as below, which has been refactored so that it doesn't need to be recursive which should help a little in the readability department.
public Objective GetNode(IEnumerable<Objective> collection, params int[] indices)
{
Objective current = null;
for (int t = 0; t < indices.Length; t++)
{
Objective item = collection.SingleOrDefault(x => x.Parent == current && x.Rank == indices[t] - 1);
if (item == null)
return null;
}
return current;
}
To be called like: GetNode(cd.Objective, LevelTextBox.Text.ToIntArray());
Original: You could use something like this, it's just a simple Extension method:
public static TreeViewItem Get(this TreeView tree, params int[] indexes)
{
if (tree == null)
return null;
if (indexes == null || indexes.Length == 0)
return null;
TreeViewItem i = tree.Items[indexes[0] - 1] as TreeViewItem;
for (int index = 1; index < indexes.Length; index++)
{
i = i.Items.Count >= indexes[index] - 1 ? i.Items[indexes[index] - 1] as TreeViewItem : null;
if (i == null)
return null;
}
return i;
}
And would be used by treeView1.Get(1,3,1);
or in the case of your edit, treeView1.Get(LevelTextBox.Text.Split('.').Select(x => int.Parse(x)).ToArray());
however, this has zero error handling for invalid input.
If you can't be certain that all the items will be TreeViewItem objects, you can replace the tree.Items[...]
with tree.ItemContainerGenerator.ContainerFromIndex(...)
(and same with i.Items
These changes will require the TreeView to have been rendered fully, however.
Here's a LINQ way of doing this.
I've assumed a definition of Objective
like so:
public class Objective
{
public int ObjectiveId { get; set; }
public int? Parent_ObjectiveId { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
I've then created a support class called LevelObjective
to capture the level (ie "1.3.1") like so:
public class LevelObjective
{
public Objective Objective { get; set; }
public string Level { get; set; }
}
And I've begun with a collection of objectives defined so:
var objectives = new []
{
new Objective { ObjectiveId = 1, Parent_ObjectiveId = null, Name = "Geometry", Rank = 1, },
new Objective { ObjectiveId = 2, Parent_ObjectiveId = 1, Name = "Squares", Rank = 1, },
new Objective { ObjectiveId = 3, Parent_ObjectiveId = 1, Name = "Circles", Rank = 2, },
new Objective { ObjectiveId = 4, Parent_ObjectiveId = 1, Name = "Triangle", Rank = 3, },
new Objective { ObjectiveId = 5, Parent_ObjectiveId = 4, Name = "Types", Rank = 1, },
new Objective { ObjectiveId = 6, Parent_ObjectiveId = null, Name = "Algebra", Rank = 2, },
new Objective { ObjectiveId = 7, Parent_ObjectiveId = null, Name = "Trigonometry", Rank = 3, },
};
Next I created a look up to get the children from any id.
var lookup = objectives.ToLookup(x => x.Parent_ObjectiveId);
I used this look up to create a set of the top-level objectives:
var roots = lookup[null]
.Select(o => new LevelObjective()
{
Objective = o,
Level = o.Rank.ToString(),
});
I then defined a function that flattens a hierarchy:
Func<
IEnumerable<LevelObjective>,
Func<LevelObjective, IEnumerable<LevelObjective>>,
IEnumerable<LevelObjective>> flatten = null;
flatten = (rs, f) =>
rs.Concat(
from r in rs
from c in flatten(f(r), f)
select c);
I already had one of these defined as an extension method that used generics, but I just refactored into a lambda expression that used LevelObjective
.
I now defined the Func<LevelObjective, IEnumerable<LevelObjective>>
required to get the children of any LevelObjective
.
Func<LevelObjective, IEnumerable<LevelObjective>> getChildren = lo =>
from o in lookup[lo.Objective.ObjectiveId]
select new LevelObjective()
{
Objective = o,
Level = String.Format("{0}.{1}", lo.Level, o.Rank),
};
I could then create a complete list of LevelObjective
objects based on the original set of Objective
objects.
var levelObjectives = flatten(roots, getChildren);
Finally I can turn this into a map from level to objectives.
var map = levelObjectives.ToLookup(x => x.Level, x => x.Objective);
Now, to find any objective I just have to call this code:
var objective = map["1.3.1"].FirstOrDefault();
So now I have a function that will return zero or more objectives for any supplied level key. The nice thing is that this will perform only one query on the database and calls to the map
function are returned in o(1) time.
Does this work for you?
精彩评论