Limit queried columns based on list set in runtime - c#

I have the following query:
// Type T is constrained to a class that contains "ID" property
// propertiesToQuery is a list constructed based on type T
var set = AppContext.Set<T>();
var result = set.SelectMany(x => propertiesToQuery.Select(p => new { x.ID, Value = x.GetType().GetProperty(p.Name).GetValue(x) })
.Where(p => p.Value != null)
.Select(p => new SearchIndexItem
{
Key = p.Value.ToString(),
Url = Url.Action("Edit", type.Name, new { p.ID }),
Type = type
}));
Now because linq to entities does not allow to use PropertyInfo in queries, I need to run a ToList() on the set in order to execute the query on the db first and then perform the desired SelectMany().
This queries more than it needs to from the database which will be a problem when there's a lot of data (queried columns are of type string, others can be blobs and these are the ones I don't want to pull the data from)
So the question is how can I limit the columns queried from the db based on a list constructed at runtime?
I was trying to create an expression tree, and pass it to the Select() method on the set, but the problem was with creating the anonymous type, which can be different depenging on type T.

Your observation here:
the problem was with creating the anonymous type, which can be different depenging on type T
is accurate; it is hugely problematic to construct the result columns at runtime. The only simply way to do that is to make it look like you are populating a projection of a type that has all the members, for example the equivalent of:
// where our list is "Foo", "Bar":
x => new SomeType {
Foo = x.Foo,
Bar = x.Bar
// but other SomeType properties exist, but are't mapped
}
The obvious contender would be the entity type, so you are partially mapping a set of Customer rows to Customer objects - but most ORMs won't let you do that: if the projection is an entity type they want the entire type (i.e. x => x). You might be able to create a second version of the entity type that is a regular POCO/DTO but isn't part of the entity model, i.e.
Customer x => new CustomerDto {
Foo = x.Foo,
Bar = x.Bar
}
which you can do as part of Expression.MemberInit at runtime. Example:
class Foo
{
public string A { get; set; }
public int B { get; set; }
public DateTime C { get; set; }
}
class FooDto
{
public string A { get; set; }
public int B { get; set; }
public DateTime C { get; set; }
}
class Program
{
static void Main(string[] args)
{
var data = new[] { new Foo { A = "a", B = 1, C = DateTime.Now}}
.AsQueryable();
var mapped = PartialMap<Foo, FooDto>(data, "A", "C").ToList();
}
static IQueryable<TTo> PartialMap<TFrom, TTo>(
IQueryable<TFrom> source, params string[] members)
{
var p = Expression.Parameter(typeof(TFrom));
var body = Expression.MemberInit(Expression.New(typeof(TTo)),
from member in members
select (MemberBinding)Expression.Bind(
typeof(TTo).GetMember(member).Single(),
Expression.PropertyOrField(p, member))
);
return source.Select(Expression.Lambda<Func<TFrom, TTo>>(body, p));
}
}
in the output, A and C have values, but B does not.

Related

Filter a list of objects given a single object

I want a method that accepts a list of objects, and then the 'filter object' (which is the same type as the list of objects). I'm able to do it (inefficiently) on a small scale but also quite fixed - I'd like it to be a generic method so I can pass in the type so it can apply to anything.
Example:
public class Program {
public void Main(string[] args) {
var listOfObjects = new List<MyClass> {
new MyClass { ID = 1, Name = "Object 1" },
new MyClass { ID = 2, Name = "Object 2" },
new MyClass { ID = 3, Name = "Object 2" }
};
var filter = new MyClass { Name = "Object 2" };
// Should return only the first object in the list, since
// the filter's Name = "Object 2", all items in the list
// where the property equals "Object 2" will be filtered out
var filteredList = FilterObjects(listOfObjects, filter);
}
}
public class MyClass {
public int ID { get; set; }
public string Name { get; set; }
}
public class MyTest {
public List<MyClass> FilterObjects(List<MyClass> objects, MyClass filter) {
// first check if the filter is just an empty new instance
// if a user passes in an empty filter then they are not
// filtering anything, therefore
if (filter == new MyClass()) return objects;
var filteredList = new List<MyClass>();
foreach (var item in objects) {
// if this item passes the test for the filter
// (check if any of the properties are equal to the
// filter properties - if so, then this item is not
// is not a match, and we cannot add it to the list)
if (item.ID != filter.ID && item.Name != filter.Name)
filteredList.Add(item);
// What I want is a faster and more consolidated way of
// checking all the properties.
}
return filteredList;
}
}
EDIT: Is there any way to do this also using reflection?
EDIT2: Just wanted to clarify that my example here is just a simple template. I am working with an object that has 20+ properties and would love to not have to make a huge if statement if possible.
EDIT3: I should also mention that the filter object the user passes in can be incomplete, e.g. they can pass in a MyClass object without the ID (just the Name property) because when it reaches my Controller, that MyClass object will automagically fill in ID with a default value. I can check if it is a default value by creating a new instance of MyClass - new MyClass() and for every property, if it equals what the default value would be then ignore that property for filtering because the user didn't want that property filtered. But think of this concept on a larger scale where I have 20+ properties and a user wants to filter out all objects but ONLY wants to use 3 of those properties to filter from. The other 17+ properties will not have an equality check.
It sounds like what you want are generic statements.
It isn't super straightforward but something like this should work:
public static IEnumerable<T> Filter<T>(this IEnumerable<T> results, Filter filter)
{
var types = results.GetType().GetProperties();
foreach (var filter in filter.Filters)
{
Type type = results.GetType();
filter.ColumnName = filter.ColumnName.Replace(" ", "");
var pred = BuildPredicate<T>(filter.ColumnName, filter.FilterValue);
if (filter.ColumnName != null && filter.FilterValue != null)
{
results = results.Where(w =>
{
return w.GetType().GetProperty(filter.ColumnName).GetValue(w, null).ToString().ToLowerInvariant().Contains(filter.FilterValue.ToLowerInvariant());
});
}
}
return results;
}
The filter object looks something like:
public class Filter
{
public string ColumnName {get; set; }
public string Value { get; set; }
//Other properties for Asc / Desc and more
}
And then on any List like List or List you would essentially do:
var results = MyList.Filter(new Filter() { ColumnName = "LastName"; Value = "Smith" });
which gets translated to a Function that if typed manually would look like:
var results = MyList.Where(w => w.LastName == "Smith");
This example however is rough, no type checking for starters.
I would go with a custom IsMatch method:
static bool IsMatch (MyClass toTest, MyClass filter)
{
if (filter.Prop1 != null // or whatever value means "skip this property"
&& filter.Prop1 == toTest.Prop1)
return true;
if (filter.Prop2 != null & filter.Prop2 == toTest.Prop2)
return true;
...
return false;
}
and then let Linq do the lookup for you:
List<MyClass> filtered = listOfObjects.Where(x => !IsMatch(x, filter)).ToList();
A unit test checking (with reflection) that this method is always up to date, checking all properties now and in future can be helpful in not introducing bugs when you add properties to the class
Why not just use methods already part of the System.Generic.Collections library.
var filteredList= new List<MyClass>(listOfObjects);
filteredList.RemoveWhere(n => n.Name == "Object 2");
If you want to use another class as your filter:
MyClass filter = new MyClass() {Name = "Object 2", Id=2 };
var filteredList= new List<MyClass>(listOfObjects);
filteredList.RemoveWhere(n => (n.Name == filter.Name || n.Id == filter.Id)); // you can modify predicate based on whatever you wish to compare

Automapper map array of objects

I'm trying to map an array of objects containing source classes, which has to be mapped to an array of objects containing destination classes. But It does work out of the box for my code.
class Class1ChildClass
{
public int Value { get; set; }
}
class Class1
{
// This array contains classes of type Class1ChildClass
public object[] ClassesAsObjects { get; set; }
}
class Class2ChildClass
{
public int Value { get; set; }
}
class Class2
{
// This array should contain classes of type Class2ChildClass
public object[] ClassesAsObjects { get; set; }
}
The straight-forward way in mind mind would be this code:
var cl1 = new Class1
{
ClassesAsObjects =
new object[] {
new Class1ChildClass
{
Value = 999
}
}
};
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1ChildClass, Class2ChildClass>();
cfg.CreateMap<Class1, Class2>();
}
);
var mapper = config.CreateMapper();
var cl2 = mapper.Map<Class2>(cl1);
Whatever I do, I always get an array of Class1ChildClass in the destination class Class2.
I tried to use ForMember with no success.
I do not know why you want to use Automapper in this case instead of just manually doing the mapping. From my understanding, Automapper looks at the types and their properties, and does its magic with those.
However in your case, there is not much to leverage Automapper. You are using object which has no Value property. You want to map members of object[] but actually want the content to be somehow converted from one (object sub-)class to another.
But if you want to use Automapper, you can use the AfterMap to map the ClassesAsObjects member:
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1, Class2>()
.AfterMap((src, dest) =>
{
dest.ClassesAsObjects = src.ClassesAsObjects
.Select(x =>
{
return (x is Class1ChildClass)
? (object)(new Class2ChildClass { Value = (x as Class1ChildClass).Value })
: null;
}).ToArray();
});
}
);
According to this: https://github.com/AutoMapper/AutoMapper/wiki/Lists-and-arrays
AutoMapper still requires explicit configuration for child mappings, as AutoMapper cannot "guess" which specific child destination mapping to use.
So you may need to add something like this:
Mapper.Initialize(c=> {
c.CreateMap<Class1, Class2>()
.Include<Class1ChildClass, Class2ChildClass>();
c.CreateMap<Class1, Class2>();
});

Use Lambda expression to find all objects with matching id

I'm working with Lambda expressions for the first time and trying to understand how to do this. I'm following a pattern that exists already in this test file, but my call has an extra layer of complexity in that I need to look inside each of the objects to select all that have an id that will be provided at the time of the test.
myobject.cs
public class myObject
{
public myObject()
{
this.id = Guid.Empty;
this.poolId = Guid.Empty;
this.name = string.Empty;
this.URL = string.Empty;
}
public Guid id { get; set; }
public Guid poolId { get; set; }
public string name { get; set; }
public string URL { get; set; }
}
testfile.cs
Mock<IMyObjectRepository> mock = new Mock<IMyObjectRepository>(MockBehavior.Strict);
List<myObject> objects = new List<myObject>();
mock.Setup(r => r.LoadByPoolId(It.IsAny<Guid>()))
.Returns<IEnumerable<myObject>>(objList => objects.Where(obj => objList.Contains(obj.id));
The problem is, this only searches surface level objects, it does not search the properties of the objects. What's the piece I'm missing to select all myObjects with a matching id?
The repository interface
public interface IMyObjectRepository
{
void Put(myObject object)
void Delete(Guid appId);
myObject Load(Guid appId)
IEnumerable<myObject> LoadByPoolId(Guid poolId);
}
Using provided example
var mock = new Mock<IMyObjectRepository>();
var objects = new List<myObject>();
//...populate objects
// access invocation arguments when returning a value
mock.Setup(r => r.LoadByPoolId(It.IsAny<Guid>()))
.Returns((Guid arg) => objects.Where(obj => obj.poolId == arg));
In the Returns expression the provided argument is passed to the expression used to filter the objects list.
Reference : Moq Quickstart
LINQ's Where method accepts a lambda expression that allows you to perform any check you wish. Write an expression that evaluates to true for rows you wish to include and false otherwise.
In your case the expression would be o.id == targetGUID so just insert it into the lambda body:
var filteredList = list.Where(o => o.id == targetGUID);

group by using anonymous type in Linq

Let say I have an Employee class, and GetAllEmployees() return a list of employee instance. I want to group employees by Department and Gender, so the answer I have is
var employeeGroup = Employee.GetAllEmployees()
.GroupBy(x => new { x.Department, x.Gender }) // I don't understand this anonymous type
.OrderBy(g => g.Key.Department)
.ThenBy(g => g.Key.Gender)
.Select(g => new { //I can understand this anonymous type
Dept = g.Key.Department,
Gender = g.Key.Gender,
Employees = g.OrderBy(x => x.Name)
});
I have two questions:
Why an anonymous type allows a multiple keys group by?
I don't understand the first anonymous type, because from my understanding, an anonymous type's format should be like this
new { field1 = x.Department, field2 = x.Gender }
How come the first anonymous type can have without fields? I mean, it's correct syntax to write something like this:
var anonymous = new {field1 = 1,field2 =2}
But there will be compile error if I write it like this:
var anonymous = new {1, 2} //compile error !!!
Anonymous types can be used here to group by multiple fields, because GroupBy uses the default equality comparer.
The default equality comparer for anonymous types uses the default equality comparer for each property of the anonymous type.
So for the first anonymous type, two instances are equal if both Departments and both Genders are equal (according to their default equality comparers).
You can imagine the anonymous type being something like that:
public class AnonymousType1
{
public int Department { get; set; } // I don't know your department type
public int Gender { get; set; } // neither your gender type
public int GetHashCode() { return Department.GetHashCode() ^ Gender.GetHashCode(); }
public bool Equals(AnonymousType1 other)
{
if (ReferenceEquals(other, null)) return false;
return Department == other.Department && Gender == other.Gender;
}
}
The second question is easy, too: The compiler uses the property names (Department from x.Department and Gender from x.Gender) as names for the properties of the anonymous type.
So
var anon = new { employee.Department, employee.Gender }
creates a type with a property called Department and a property called Gender.
Of course this can only work with existing properties/names, not with constant values like
var anon = new {1,2}; // fails to compile, no names provided.

Returning Anonymous Type

I am using a trick to return Anonymous Type but, i m not sure whether it will
work in all scenario. If there is any problem using this trick plz let me know
so that i will not use this code in my project
class A
{
public int ID { get; set; }
public string Name { get; set; }
}
class B
{
public int AID { get; set; }
public string Address { get; set; }
}
private List<object> GetJoin()
{
var query = from a in objA
join b in objB
on a.ID equals b.AID
select new { a.ID, a.Name, b.Address };
List<object> lst = new List<object>();
foreach (var item in query)
{
object obj = new { ID = item.ID, Name = item.Name, Address = item.Address };
lst.Add(obj);
}
return lst;
}
T Cast<T>(object obj, T type)
{
return (T)obj;
}
//call Anonymous Type function
foreach (var o in GetJoin())
{
var typed = Cast(o, new { ID = 0, Name = "", Address = "" });
int i = 0;
}
Yes, that's guaranteed to work so long as everything is within the same assembly. If you cross the assembly boundary, it won't work though - anonymous types are internal, and the "identity" is based on:
Assembly it's being used in
Properties:
Name
Type
Order
Everything has to be just right for the types to be considered the same.
It's not nice though. For one thing, your GetJoin method can be simplified to:
return (from a in objA
join b in objB
on a.ID equals b.AID
select (object) new { a.ID, a.Name, b.Address })
.ToList();
... and for another, you've lost compile-time type safety and readability.
I think you'd be a lot better off creating a real type to encapsulate this data. Admittedly it would be lovely if we could create a named type which was the equivalent to the anonymous type, just with a name... but that's not available at the moment :(
You cannot return Anonymous Types without using Object type. And using Object type, you lose member access.
You have two options:
Create a type. (Recommended)
Use dynamic type to access members

Categories

Resources