Skip to content

Instantly share code, notes, and snippets.

@zaus
Created October 18, 2012 15:09
Show Gist options
  • Save zaus/3912453 to your computer and use it in GitHub Desktop.
Save zaus/3912453 to your computer and use it in GitHub Desktop.
C# Linq Sublevel Filtering - Only return 1st-level with matching 2nd-level items, but resultset only contains matching 2nd-level items
public class LinqSubsetTests {
/// example class
public class Foo {
public long ID { get; set; }
public int Filterable { get; set; }
public string Name { get; set; }
public IEnumerable<Bar> Children { get; set; }
public override string ToString() {
return string.Format(@"Foo[I:{3}, F:{0}, N:{1}; C:
{2}
]"
, this.Filterable
, this.Name
, string.Join(@",
", this.Children.Select(o => o.ToString()))
, this.ID
);
}
}
/// another example class
public class Bar {
public long ID { get; set; }
public long ParentID { get; set; }
public int Filterable { get; set; }
public string Name { get; set; }
public virtual Foo Parent { get; set; }
public override string ToString() {
return string.Format("Bar[I: {2}, F:{0}, N:{1}; P:{3}]"
, this.Filterable
, this.Name
, this.ID
, this.ParentID
);
}
}
protected static Random Rand = new Random();
protected const int MAX_VALUE = 5;
/// <summary>
/// for calculating "guid"
/// </summary>
private static int guid_counter = 1;
/// <summary>
/// Randomized Foo list
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
protected static List<Foo> MakeFooList(int size) {
var results = new List<Foo>();
long id;
while (size-- > 0) {
var newitem = new Foo {
ID = guid_counter++
, Filterable = Rand.Next(1, MAX_VALUE)
, Name = string.Format("Foo{0}", size)
};
// recursive attach
newitem.Children = MakeBarList(Rand.Next(2, 5), newitem);
results.Add(newitem);
}
return results;
}
/// <summary>
/// Randomized Bar list
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
protected static List<Bar> MakeBarList(int size, Foo parent) {
var results = new List<Bar>();
while (size-- > 0) {
results.Add(new Bar {
ID = guid_counter++
, ParentID = parent.ID
, Parent = parent
, Filterable = Rand.Next(1, MAX_VALUE)
, Name = string.Format("Bar{0}", size)
});
}
return results;
}
/// <summary>
/// Actual "Tests": Filter on filterable column
/// </summary>
/// <param name="filter"></param>
public static void GetForSubset() {
int filter = Rand.Next(1, MAX_VALUE);
var foos = MakeFooList(5);
// debug output to review
foos.ForEach(o => Program.Out(o.ToString()));
Program.Out("==== Filtering on {0} ====", filter);
var filtered1 = foos.Where(o => o.Filterable == filter).ToList();
// debug output to review
Program.Out("==== Filtered on Foo ====");
filtered1.ForEach(o => Program.Out(o.ToString()));
var filtered2 = foos.Where(o => o.Children.Any(o2 => o2.Filterable == filter)).ToList();
// debug output to review
Program.Out("==== Filtered Foo on Bar ====");
filtered2.ForEach(o => Program.Out(o.ToString()));
var filtered3 = foos
/* ^ "table" of Foo */.SelectMany(o => o.Children)
/* ^ simulated table of Bar */.Where(o => o.Filterable == filter)
/* ^ only matching items */.GroupBy(o => o.Parent)
/* ^ grouped by top-level */.Select(o =>
// reconstruct top level
new Foo {
ID = o.Key.ID,
Filterable = o.Key.Filterable,
Name = o.Key.Name,
// filter sublevel
Children = o.Where(o2 => o2.Filterable == filter)
}).ToList();
// debug output to review
Program.Out("==== Filtered Foo by Bar ====");
filtered3.ForEach(o => Program.Out(o.ToString()));
}
}
============== SAMPLE RESULTS ====================
Foo[I:1, F:1, N:Foo4; C:
Bar[I: 2, F:1, N:Bar2; P:1],
Bar[I: 3, F:3, N:Bar1; P:1],
Bar[I: 4, F:4, N:Bar0; P:1]
]
Foo[I:5, F:4, N:Foo3; C:
Bar[I: 6, F:2, N:Bar3; P:5],
Bar[I: 7, F:3, N:Bar2; P:5],
Bar[I: 8, F:1, N:Bar1; P:5],
Bar[I: 9, F:3, N:Bar0; P:5]
]
Foo[I:10, F:3, N:Foo2; C:
Bar[I: 11, F:1, N:Bar1; P:10],
Bar[I: 12, F:4, N:Bar0; P:10]
]
Foo[I:13, F:2, N:Foo1; C:
Bar[I: 14, F:2, N:Bar2; P:13],
Bar[I: 15, F:2, N:Bar1; P:13],
Bar[I: 16, F:1, N:Bar0; P:13]
]
Foo[I:17, F:4, N:Foo0; C:
Bar[I: 18, F:1, N:Bar1; P:17],
Bar[I: 19, F:2, N:Bar0; P:17]
]
==== Filtering on 4 ====
==== Filtered on Foo ====
Foo[I:5, F:4, N:Foo3; C:
Bar[I: 6, F:2, N:Bar3; P:5],
Bar[I: 7, F:3, N:Bar2; P:5],
Bar[I: 8, F:1, N:Bar1; P:5],
Bar[I: 9, F:3, N:Bar0; P:5]
]
Foo[I:17, F:4, N:Foo0; C:
Bar[I: 18, F:1, N:Bar1; P:17],
Bar[I: 19, F:2, N:Bar0; P:17]
]
==== Filtered Foo on Bar ====
Foo[I:1, F:1, N:Foo4; C:
Bar[I: 2, F:1, N:Bar2; P:1],
Bar[I: 3, F:3, N:Bar1; P:1],
Bar[I: 4, F:4, N:Bar0; P:1]
]
Foo[I:10, F:3, N:Foo2; C:
Bar[I: 11, F:1, N:Bar1; P:10],
Bar[I: 12, F:4, N:Bar0; P:10]
]
==== Filtered Foo by Bar ====
Foo[I:1, F:1, N:Foo4; C:
Bar[I: 4, F:4, N:Bar0; P:1]
]
Foo[I:10, F:3, N:Foo2; C:
Bar[I: 12, F:4, N:Bar0; P:10]
]
--------------
(Press any key to continue)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment