Is there a way to select all the properties in a collection and set one without verbosly remapping the entire property list?
return =
_input.Where(
w => w.MaterialNumber.Contains("foo")
).Select(s => new Material.list() { ID = s.ID, MaterialNumber = s.MaterialNumber, orColor = "#303030" }).ToList()
);
Looking for something simpler.
return =
_input.Where(
w => w.MaterialNumber.Contains("foo")
).Select(s => new Material.list() {s, new orColor = "#303030" }).ToList()
);
You mentioned EF 6 but you don't actually use it in the question. Also you mentioned mapping, but are you sure if it is required? If it is possible for other code to use same instance without remapping, just use any approach listed below to set property of the original object
using System.Linq;
namespace ConsoleApp1
{
class DTO
{
public string Color { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
var input = Enumerable.Repeat(new DTO(), 10);
// select statement
var selectOutput = input.Select(x =>
{
x.Color = "#303030";
return x;
});
// ForEach extension
var listOutput = input.ToList();
listOutput.ForEach(x => x.Color = "#303030");
// regular foreach
foreach (var inputItem in input)
{
inputItem.Color = "#303030";
}
}
}
}
Related
Net 6.0
Our company uses a metric layout that takes records that look like this
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
and transforms it into this
AAA|BLANK|BLANK|BLANK
AAA|0000A|BLANK|BLANK
AAA|0000B|BLANK|BLANK
AAA|0000A|000AA|BLANK
AAA|0000A|000AB|BLANK
AAA|0000B|000BA|BLANK
AAA|0000B|000BB|BLANK
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AA|0003A
AAA|0000A|000AA|0004A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
I have a custom object
public class HierarchyStructure
{
public string Employer { get; set; }
public string Level1 { get; set; }
public string Level2 { get; set; }
public string Level3 { get; set; }
public string Level4 { get; set; }
public bool isRxlvl3 { get; set; }
public bool isExpired { get; set; }
public string lvl4SubType { get; set; }
public HierarchyStructure(string employer,
string? level1 = null,
string? level2 = null,
string? level3 = null,
string? level4 = null,
bool isRxlvl3 = false,
bool isExpired = false,
string? lvl4SubType = null)
{
this.Employer = employer;
this.Level1 = string.IsNullOrEmpty(level1) ? string.Empty : level1;
this.Level2 = string.IsNullOrEmpty(level2) ? string.Empty : level2;
this.Level3 = string.IsNullOrEmpty(level3) ? string.Empty : level3;
this.Level4 = string.IsNullOrEmpty(level4) ? string.Empty : level4;
this.isRxlvl3 = isRxlvl3;
this.isExpired = isExpired;
this.lvl4SubType = string.IsNullOrEmpty(lvl4SubType) ? string.Empty : lvl4SubType;
}
}
I am trying to populate from a static level into a formatted output list
private List<HierarchyStructure> AllLevels => _allLevels;
private List<HierarchyStructure> LocationLevels => _LocationLevels;
I developed methods for each level call, but I can't figure out how to merge them into one method that either calls itself recursively or dynamically selects the right values. I do not think it is possible to combine these due to the different comparisons and return values.
public bool GetDistinctEmployers()
{
var selectedLevels = (from levels in AllLevels
select new HierarchyStructure(levels.Employer));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel1(string emp)
{
var selectedLevels = (from levels in AllLevels
where levels.Employer == emp
select new HierarchyStructure(levels.Employer,levels.Level1));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel2(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level1 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel3(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level2 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel4(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level3 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
When I try and combine all these together, it ends up being soooo nested and I feel like there must be a better way to accomplish this. I am fairly certain this level of nesting will error my quality gate on my pipeline.
public void ProcessLevels()
{
if(GetDistinctEmployers())
{
foreach(string employer in AllLevels.Select(x => x.Employer))
{
if(GetDistinctLevel1(employer))
{
foreach(string level1 in AllLevels.Select(x => x.Level1))
{
if(GetDistinctLevel2(level1))
{
foreach(string level2 in AllLevels.Select(x => x.Level2))
{
if(GetDistinctLevel3(level2))
{
foreach(string level3 in AllLevels.Select(x => x.Level3))
{
if(GetDistinctLevel4(level3))
{
SaveData();
}
}
}
}
}
}
}
}
}
}
I am also getting another inline quality flag that my expressions in this block can be shortened using LINQ. I dont understand how my one line can be simplified.
...AllLevels.Select(x => x.Employer)
...AllLevels.Select(x => x.Level1)
... etc
A view of the data output I am trying to get.
Update: Thank you for the help offered - using the answers provided in this thread, I have coded my solution like this eliminating a lot of loops.
public void ProcessLevels()
{
var employers = AllLevels.Where(x=> !x.isExpired ).Select(x => x.Employer).Distinct();
var lvl1 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1 }).Distinct();
var lvl2 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2 }).Distinct();
var lvl3 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3 }).Distinct();
var lvl4 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3, x.Level4 }).Distinct();
foreach(var emp in employers) { LocationLevels.Add(new LevelHierarchyStructure(emp)); }
foreach(var lvl in lvl1) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1)); }
foreach(var lvl in lvl2) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2)); }
foreach(var lvl in lvl3) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3)); }
foreach(var lvl in lvl4) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3, lvl.Level4)); }
SaveData();
}
I assume someone will flag me for posting my update, but im posting it so ppl understand what i did and see my final solution
Perhaps you need something that first expands each source row into all of the possible levels, and runs those results through a .Distinct() and an .OrderBy().
Given:
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
The expanded data would be:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0002A
...
AAA
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
Then a .Distinct() and .OrderBy()
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
...
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
...
You would need a generating function to expand each source row into an enumerated list than can be fed into a .SelectMany(), and may also need to define custom comparators to be used by the .Distinct() and a .OrderBy() functions.
Something like:
private static IEnumerable<HierarchyStructure> HierarchyGenerator(this HierarchyStructure level)
{
yield return new HierarchyStructure(levels.Employer);
yield return new HierarchyStructure(levels.Employer,levels.Level1);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4);
}
...
LocationLevels = AllLevels
.SelectMany(levels => levels.HierarchyGenerator())
.Distinct(...custom HierarchyStructure IEqualityComparer...)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There are other ways, such as generating distinct values are each level, unioning them all together, and then feeding them to the sort.
Something like:
var level0 = AllLevels
.Select(levels => newHierarchyStructure(levels.Employer))
.Distinct(IEqualityComparer...);
...
var level4 = AllLevels
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4))
.Distinct(IEqualityComparer...);
LocationLevels = level0
.Union(level1)
.Union(level2)
.Union(level3)
.Union(level4)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There may be some performance tradeoffs in selecting where the distinct operations are applied. Using intermediate anonymous objects might also help, such as:
var level1 = AllLevels
.Select(levels => new {levels.Employer, levels.Level1})
.Distinct()
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1));
Here the .Distinct() uses the default comparator for the anonymous object, which compares each contained value.
The custom comparers can also be incorporated into the HierarchyStructure class by implementing the IComparable interface. The HierarchyGenerator() function could also be made a member function within the HierarchyStructure class.
(My apologies in advance for any syntax errors. This is untested. I'll update the above given any comments.)
This seems to be able to be solved quite easily with LINQ.
I'll start with this input:
string[] input = new[]
{
"AAA|0000A|000AA|0001A",
"AAA|0000A|000AA|0002A",
"AAA|0000A|000AB|0001B",
"AAA|0000A|000AB|0002B",
"AAA|0000B|000BA|0001A",
"AAA|0000B|000BB|0001B",
};
Now I can transform this into the output like this:
string[][] parts = input.Select(x => x.Split('|')).ToArray();
int max = parts.Max(p => p.Length);
string[][] expanded =
Enumerable
.Range(0, max)
.SelectMany(i => parts.Select(p => p.Take(i + 1).ToArray()).ToArray())
.DistinctBy(xs => String.Join("|", xs))
.OrderBy(xs => String.Join("|", xs))
.ToArray();
string[] output =
expanded
.Select(e => String.Join("|", e))
.ToArray();
That gives me:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B
AAA|0000B|000BA
AAA|0000B|000BA|0001A
AAA|0000B|000BB
AAA|0000B|000BB|0001B
If you just want to stop to fill a grid, just use expanded.
With this approach it doesn't matter how many levels deep the source data is.
I'm playing around with Elasticsearch and NEST.
I do have some trouble understanding the various classes and interfaces which can be used to create and build static queries.
Here's a simplified example of what I want to achieve:
using Nest;
using System;
using System.Text;
namespace NestTest
{
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
public class ProductFilter
{
public string[] IncludeNames { get; set; }
public string[] ExcludeNames { get; set; }
public int MaxPrice { get; set; }
}
class Program
{
static void Main(string[] args)
{
var filter = new ProductFilter();
filter.MaxPrice = 100;
filter.IncludeNames = new[] { "Notebook", "Workstation" };
filter.ExcludeNames = new[] { "Router", "Modem" };
var query = CreateQueryFromFilter(filter);
var client = new ElasticClient();
// Test Serialization
var serialized = Encoding.UTF8.GetString(client.Serializer.Serialize(query));
Console.WriteLine(serialized);
// TODO: How to convert the IQuery to QueryContainer?
//client.Search<Product>(s => s.Query(q => query));
}
private static IQuery CreateQueryFromFilter(ProductFilter filter)
{
var baseBoolean = new BoolQueryDescriptor<Product>();
if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
{
foreach (var include in filter.IncludeNames)
{
// TODO: This overwrites the previous must
baseBoolean.Must(q => q.Term(t => t.Name, include));
}
}
if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
{
foreach (var exclude in filter.ExcludeNames)
{
// TODO: This overwrites the previous must
baseBoolean.MustNot(q => q.Term(t => t.Name, exclude));
}
}
if (filter.MaxPrice > 0)
{
// TODO: This overwrites the previous must
baseBoolean.Must(q => q.Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)));
}
return baseBoolean;
}
}
}
As you can see, I'd like to create some kind of query object (most likely BoolQuery) and then fill this object later on. I've added some TODOS in code where I have the actual problems. But in general, there are just too many possibilities (IQuery, QueryContainer, XXXQueryDescriptor, SearchDescriptor, SearchRequest) and I cannot figure out how to successfully "build" a query part by part.
Anybody who could enlighten me?
Combinding boolean queries is described in the documentation here:
http://nest.azurewebsites.net/nest/writing-queries.html
That page is slightly outdated and will be updated soon although most of it still applies I updated your CreateQueryFromFilter method to showcase the several ways you can formulate queries:
private static IQueryContainer CreateQueryFromFilter(ProductFilter filter)
{
QueryContainer queryContainer = null;
if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
{
foreach (var include in filter.IncludeNames)
{
//using object initializer syntax
queryContainer &= new TermQuery()
{
Field = Property.Path<Product>(p => p.Name),
Value = include
};
}
}
if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
{
foreach (var exclude in filter.ExcludeNames)
{
//using static Query<T> to dispatch fluent syntax
//note the ! support here to introduce a must_not clause
queryContainer &= !Query<Product>.Term(p => p.Name, exclude);
}
}
if (filter.MaxPrice > 0)
{
//fluent syntax through manually newing a descriptor
queryContainer &= new QueryDescriptor<Product>()
.Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)
);
}
return queryContainer;
}
Here's how you can pass that to a search operation:
static void Main(string[] args)
{
//using the object initializer syntax
client.Search<Product>(new SearchRequest()
{
Query = query
});
//using fluent syntax
client.Search<Product>(s => s.Query(query));
}
I want to merge two lists with different attributes into one list, but while merging it, I want to check if there is, in this particular example, exact date that is in the both lists, and if there is, I want to take both attributes from that elements, and merge them into one element in another list
List 1:
List<object> r1 = (from x in sp1 select new
{
x.Imported,
x.Period
}).ToList<object>();
L1 result:
List 2:
List<object> r2 = (from x in sp2 select new
{
x.Dissolution,
x.Period
}).ToList<object>();
L2 result:
Wanted result:
For now, this is how i merge r1 and r2:
List<object> r3 = new List<object>(r1.Concat(r2));
You could transform them into same type and use stuff like this
r1
.Select(x => new { Imported = x.Imported, Dissolution = null, Period = x.Period)
.Concat(
r2.Select(x => new { Imported = null, Dissolution = x.Dissolution, Period = x.Period))
.GroupBy(x => x.Period)
.Select(x => new { Imported = x.Max(e => e.Imported),
Dissolution = x.Max(e => e.Dissolution),
Period = x.Key);
Create a Dictionary
Dictionary MyDict<String, List<Object>>;
MyDict[object.Perdiod].Add(object);
For each date there will be an entry in the dictionnary, and it will at this "date index" keep a list of all object that happens at that period.
Easiest way IMO and it does not need to make a O(n) check for every entry added
Just make sure when you add data that it is not null IE
MyDict[Object.Period] != null
Plus, has Nikhil Agrawal said I would not use Object to keep a list of thing... it feels wrong and is error prone. you might want to declare an abstract class that will be used like an Interface or simply an interface for those items (object).
AFAK you need reflection to achieve this as the name for Anonymous type is assigned at Compile time here an example on how you can achieve what you want
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<ImportedType> sp1 = new List<ImportedType>();
List<DissolutionType> sp2 = new List<DissolutionType>();
sp1.AddRange( new ImportedType[]{new ImportedType() { Imported = 2, Period = "2024-02" }, new ImportedType() { Imported = 2, Period = "2014-11" }, new ImportedType() { Imported = 2, Period = "2024-12" }});
sp2.AddRange(new DissolutionType[] { new DissolutionType() { Dissolution = 2, Period = "2024-02" }, new DissolutionType() { Dissolution = 2, Period = "2034-02" }, new DissolutionType() { Dissolution = 2, Period = "2024-12" } });
var r1 = (from x in sp1
select new
{
x.Imported,
x.Period
}).ToList<object>();
var r2 = (from x in sp2
select new
{
x.Dissolution,
x.Period
}).ToList<object>();
var r3 = r1.Concat(r2).Except(r1.Where(res =>
{
object vp2 = r2.SingleOrDefault(res2 => GetValue(res2) == GetValue(res));
if (vp2!=null)
{
return true;
}
return false;
}));
}
private static object GetValue(object res)
{
Type t = res.GetType();
PropertyInfo p = t.GetProperty("Period");
object v = p.GetValue(res, null);
return v;
}
}
}
//here I suppose that you implements 2 classes like this
public class ImportedType
{
public int Imported { get; set; }
public string Period { get; set; }
}
public class DissolutionType
{
public int Dissolution { get; set; }
public string Period { get; set; }
}
Result
I agree with Nikhil Agrawal in that the code now really needs some fixing up, as it is really hard to work with anonymous types, especially since they have been cast to object.
Ignoring that, and accepting it as a challenge (use anonymous types cast to object), this is what I have come up with:
Code that merges does a full outer join:
Func<object, object> getPeriodKey = first =>
{
var periodProperty = first.GetType().GetProperty("Period");
return periodProperty.GetValue(first);
};
var temp = r1.GroupJoin(r2, getPeriodKey, getPeriodKey, (obj, tInner) =>
{
dynamic right = tInner.FirstOrDefault();
if (right == null)
return (object)(new
{
Period = ((dynamic)obj).Period,
Imported = ((dynamic)obj).Imported,
});
else
return (object)(new
{
Period = ((dynamic)obj).Period,
Imported = ((dynamic)obj).Imported,
Dissolution = (int?)right.Dissolution,
});
});
var merged = temp.Union(r2, new RComparer());
and the required comparer is below:
class RComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
var xPeriodProperty = x.GetType().GetProperty("Period");
var yPeriodProperty = y.GetType().GetProperty("Period");
if (xPeriodProperty != null && yPeriodProperty != null)
{
var xPeriod = (string)xPeriodProperty.GetValue(x);
var yPeriod = (string)yPeriodProperty.GetValue(y);
return xPeriod == yPeriod;
}
else
return base.Equals(y);
}
public int GetHashCode(object obj)
{
var periodProperty = obj.GetType().GetProperty("Period");
if (periodProperty != null)
//This will essentially hash the string value of the Period
return periodProperty.GetValue(obj).GetHashCode();
else
return obj.GetHashCode();
;
}
}
I need to test logic that executes a function, after which it changes a property on the parameter and then executes the same function with the updated parameter.
To help illustrate this, here is some sample code:
Interface:
public interface IWorker
{
MyObjectB DoWork(MyObject myObject);
}
MyObjectB:
public class MyObjectB
{
public string Message { get; set; }
}
Implementation:
public MyObjectB DoWork(IWorker worker, MyObject myObject)
{
worker.DoWork(myObject);
myObject.Name = "PersonB";
worker.DoWork(myObject);
return new MyObjectB() { Message = "Done" };
}
Test:
public void RhinoMocksSampleTest()
{
var workerStub = MockRepository.GenerateStub<IWorker>();
workerStub.Stub(n => n.DoWork(Arg<MyObject>.Is.Anything));
var myObj = new MyObject { Id = 1, Name = "PersonA" };
var p = new Program();
p.DoWork(workerStub, myObj);
workerStub.AssertWasCalled(d => d.DoWork(Arg<MyObject>.Matches(r => r.Name == "PersonA")));
workerStub.AssertWasCalled(d => d.DoWork(Arg<MyObject>.Matches(r => r.Name == "PersonB")));
}
The first AssertWasCalled fails because the value is ‘PersonB’.
Is there a function/call I can use to test the state of the object for the first call?
Here's how I would do what you're trying to do:
public void RhinoMocksSampleTest()
{
var workerMock = MockRepository.GenerateMock<IWorker>();
workerMock.Expect(d => d.DoWork(Arg<MyObject>.Matches(r => r.Name == "PersonA")));
workerMock.Expect(d => d.DoWork(Arg<MyObject>.Matches(r => r.Name == "PersonB")));
var myObj = new MyObject { Id = 1, Name = "PersonA" };
var p = new Program();
p.DoWork(workerMock , myObj);
workerMock.VerifyAllExpectations();
}
In addition to nice answer what aprescott has provided I can show another one approach which could be useful in some situations:
The idea is to manually store values which you are interesting in into some collection. Then you will be able to assert that collection contains required sequense.
public void MyTest()
{
// that is list which will contain passed names
var actualNames = new List<string>();
// stub only saves passed Name property into list
var workerStub = MockRepository.GenerateStub<IWorker>();
workerStub
.Stub(w => w.DoWork(Arg<MyObject>.Is.Anything))
.Do((Action<MyObject>)(mo => actualNames.Add(mo.Name)));
var myObject = new MyObject { Name = "PersonA" };
var target = new MyWorker();
target.DoWork(workerStub, myObject);
// here we do assert that names list contains what is required
Assert.That(actualNames, Is.EqualTo(new[] { "PersonA", "PersonB" }));
}
PS. Yeah, it works for case when you need check that calls order is correct :)
I try do implement a user dynamic filter, where used selects some properties, selects some operators and selects also the values.
As I didn't find yet an answer to this question, I tried to use LINQ expressions.
Mainly I need to identify all houses which main rooms are kitchens(any sens, I know).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//using System.Linq.Dynamic;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Room aRoom = new Room() { Name = "a Room" };
Room bRoom = new Room() { Name = "b Room" };
Room cRoom = new Room() { Name = "c Room" };
House myHouse = new House
{
Rooms = new List<Room>(new Room[] { aRoom }),
MainRoom = aRoom
};
House yourHouse = new House()
{
Rooms = new List<Room>(new Room[] { bRoom, cRoom }),
MainRoom = bRoom
};
House donaldsHouse = new House()
{
Rooms = new List<Room>(new Room[] { aRoom, bRoom, cRoom }),
MainRoom = aRoom
};
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
//var kitchens = houses.AsQueryable<House>().Where("MainRoom.Type = RoomType.Kitchen");
//Console.WriteLine("kitchens count = {0}", kitchens.Count());
var houseParam = Expression.Parameter(typeof(House), "house");
var houseMainRoomParam = Expression.Property(houseParam, "MainRoom");
var houseMainRoomTypeParam = Expression.Property(houseMainRoomParam, "Type");
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
var comparison = Expression.Lambda(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType)))
);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Console.WriteLine("kitchens count = {0}", kitchens.Count());
Console.ReadKey();
}
}
public class House
{
public string Address { get; set; }
public double Area { get; set; }
public Room MainRoom { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public double Area { get; set; }
public string Name { get; set; }
public RoomType Type { get; set; }
}
public enum RoomType
{
Kitchen,
Bedroom,
Library,
Office
}
}
var kitchens = from h in houses
where h.MainRoom.Type == RoomType.Kitchen
select h;
But you must set the RoomType property on the rooms before.
Ok, edit:
so you must redefine:
var comparison = Expression.Lambda<Func<House, bool>>(...
Then, when you use it:
var kitchens = houses.AsQueryable().Where(comparison.Compile());
Edit #2:
Ok, here you go:
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
// ???????????????????????? DOES NOT WORK
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant(Enum.Parse(typeof(RoomType), "Kitchen"), typeof(RoomType))), houseParam);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Edit #3: Of, for your needs, I am out of ideas for now. I give you one last one:
Declare an extension method on the String type:
internal static object Prepare(this string value, Type type)
{
if (type.IsEnum)
return Enum.Parse(type, value);
return value;
}
Then use it in that expression like:
Expression.Constant("Kitchen".Prepare(typeof(RoomType)), typeof(RoomType))
That's because apparently enums are treated differently. That extension will leave the string unaltered for other types. Drawback: you have to add another typeof() there.
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
The Where method takes a Func<House, bool> or a Expression<Func<House, bool>> as the parameter, but the variable comparison is of type LambdaExpression, which doesn't match. You need to use another overload of the method:
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType))));
//now the type of comparison is Expression<Func<House, bool>>
//the overload in Expression.cs
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);
I wouldn't build the where clause in that way - I think it's more complex than it needs to be for your needs. Instead, you can combine where clauses like this:
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
// A basic predicate which always returns true:
Func<House, bool> housePredicate = h => 1 == 1;
// A room name which you got from user input:
string userEnteredName = "a Room";
// Add the room name predicate if appropriate:
if (!string.IsNullOrWhiteSpace(userEnteredName))
{
housePredicate += h => h.MainRoom.Name == userEnteredName;
}
// A room type which you got from user input:
RoomType? userSelectedRoomType = RoomType.Kitchen;
// Add the room type predicate if appropriate:
if (userSelectedRoomType.HasValue)
{
housePredicate += h => h.MainRoom.Type == userSelectedRoomType.Value;
}
// MainRoom.Name = \"a Room\" and Rooms.Count = 3 or
// ?????????????????????????
var aRoomsHouses = houses.AsQueryable<House>().Where(housePredicate);
I tested this one, honest :)
what about this
var kitchens = houses
.SelectMany(h => h.Rooms, (h, r) => new {House = h, Room = r})
.Where(hr => hr.Room.Type == RoomType.Kitchen)
.Select(hr => hr.House);
To add a new Enum type to dynamic Linq, you must add the following code :
typeof(Enum),
typeof(T)
T : Enum type
in predefined types of dynamic. That works for me.