Is there a way to expose 1 column from the database to 2 properties in a model class?
Example use case [NOT WORKING, HYPOTHETICAL EXAMPLE]:
public interface ISearcheable
{
public string SearcheableField { get; set; }
}
public class User : ISearcheable
{
public string Username { get; set; }
[DisplayName("Username")]
public string SearcheableField { get; set; }
}
public class Book : ISearcheable
{
public string Title { get; set; }
[DisplayName("Title")]
public string SearcheableField { get; set; }
}
Now if I wanted to search on each, I could just use:
db.entity<T>.Where(w => w.SearcheableField == "Username to check")
With a good bit of coding, some help from #AaronV over in How to specify dynamic field names in a Linq where clause? I was able to get this put together.
You will need to create a custom SearchableProperty attribute and apply it to the correct property in your model. Then extend Linq's IQueryable by adding a Search method. This Search method will take as parameters the type of search you want to do (being Equal to, Contains, Greater than, etc.) and the value you are searching for. The Search method will need to use reflection to look over the properties in your model and find the property that has the SearchableProperty attribute. The Search method will then dynamically build a .Where clause using that property, the operator you passed and the search value you passed.
First, create a custom SearchableProperty Attribute.
[System.AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class SearchableProperty : Attribute
{
public SearchableProperty() { }
}
Second, apply this attribute to your Model classes:
class Book
{
[Key]
public int Id { get; set; }
[SearchableProperty]
public string Author { get; set; }
}
Third, Add an enum of type Operator to your project:
public enum Operator
{
Gt,
Lt,
Eq,
Le,
Ge,
Ne,
Contains,
StartsWith,
EndsWith
}
Fourth , Use the following Search Linq Extension:
public static class LinqExtensions
{
/// <summary>
/// Queries the database for the entity which matches the provided search parameters.
/// </summary>
/// <typeparam name="TEntity">The type of entity being searched for.</typeparam>
/// <typeparam name="TDataType">The data type of the value being searched for.</typeparam>
/// <param name="op">The <see cref="Operator"/> operation that qualifies a match.</param>
/// <param name="query">The query that must be matched for the entity to be returned. </param>
/// <returns>A filtered sequence of elements. </returns>
internal static IQueryable<TEntity> Search<TEntity, TDataType>(this IQueryable<TEntity> #this, Operator op, TDataType query)
{
// Get the property that is supposed to be searchable.
PropertyInfo[] pInfo =
typeof(TEntity)
.GetProperties()
.Where(prop => prop.GetCustomAttribute<SearchableProperty>() != null)
.ToArray();
if (pInfo.Length != 1)
{
throw new InvalidOperationException($"Unable to search entity of type: {typeof(TEntity)} as the entity exposes none or more than one properties with SearchableProperty Attribute");
}
else
{
PropertyInfo searchableProperty = pInfo[0];
var dynamicExpression = CreateDynamicExpression<TEntity, TDataType>(
searchableProperty.Name,
op,
query);
return #this.Where(dynamicExpression);
}
}
/// <summary>
/// A method to create an expression dynamically given a generic entity, and a propertyName, operator and value.
/// </summary>
/// <typeparam name="TEntity">
/// The class to create the expression for. Most commonly an entity framework entity that is used
/// for a DbSet.
/// </typeparam>
/// <param name="propertyName">The string value of the property.</param>
/// <param name="op">An enumeration type with all the possible operations we want to support.</param>
/// <param name="value">A string representation of the value.</param>
/// <returns>An expression that can be used for querying data sets</returns>
private static Expression<Func<TEntity, bool>> CreateDynamicExpression<TEntity, TValueType>(string propertyName,
Operator op, TValueType value)
{
Type type = typeof(TEntity);
var p = Expression.Parameter(type, "x");
var property = Expression.Property(p, propertyName);
MethodInfo method;
Expression q;
switch (op)
{
case Operator.Gt:
q = Expression.GreaterThan(property, Expression.Constant(value));
break;
case Operator.Lt:
q = Expression.LessThan(property, Expression.Constant(value));
break;
case Operator.Eq:
q = Expression.Equal(property, Expression.Constant(value));
break;
case Operator.Le:
q = Expression.LessThanOrEqual(property, Expression.Constant(value));
break;
case Operator.Ge:
q = Expression.GreaterThanOrEqual(property, Expression.Constant(value));
break;
case Operator.Ne:
q = Expression.NotEqual(property, Expression.Constant(value));
break;
case Operator.Contains:
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(value, typeof(string)));
break;
case Operator.StartsWith:
method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(value, typeof(string)));
break;
case Operator.EndsWith:
method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(value, typeof(string)));
break;
default:
throw new ArgumentOutOfRangeException(nameof(op), op, null);
}
return Expression.Lambda<Func<TEntity, bool>>(q, p);
}
}
Finally, use the magic:
using (DemoDbContext _context = new DemoDbContext())
{
// Sample where query to show the base sql that EF generates.
var whereQuery = _context.People.Where(x => x.Name == "John");
// Sql using the new Search method.
var searchQuery = _context.People.Search(Operator.Eq, "John");
// Sql using the new search method.
var bookSearch = _context.Books.Search(Operator.Contains, "John");
// Log everything to the console for viewing.
Console.WriteLine(whereQuery.ToQueryString());
Console.WriteLine("------------------------------------");
Console.WriteLine(searchQuery.ToQueryString());
Console.WriteLine("------------------------------------");
Console.Write(bookSearch.ToQueryString());
Console.ReadKey();
}
I would use some code similar to the following:
public interface ISearcheable
{
// This is a read-only property.
public string SearcheableField{get;}
}
public class User:ISearcheable{
public string Username {get;set;}
// Let this return the property that you want to search by and do not map it to the database.
[NotMapped]
public string SearcheableField
{
get { return Username; }
}
}
public class Book:ISearcheable{
public string Title {get;set;}
// Let this return the property that you want to search by and do not map it to the database.
[NotMapped]
public string SearcheableField
{
get { return Title; }
}
}
This would avoid duplicate of data and fields in the database.
However, just be mindful of this approach. For Example, a SQL Select statement that said SELECT author FROM book WHERE book.Title LIKE '%TEST%' would work, in Entity Framework LINQ-to-Sql this could produce unexpected results if using the SearchableField but the Username (for example) is not included in the LINQ. This should only be a concern if using Dynamic Linq and selecting only specific properties.
Also note I would personally call this field SearchPropertyValue.
I don't know if you can map the same table column to two properties on your model but I'm quite sure it's not the right way to do it.
For example, suppose the column "user_name" of the database is mapped in both User properties. Now look at this code:
var user = db.Entity<User>().First(); // Username = "Niko", SearcheableField = "Niko"
user.Username = "Joe";
var list = db.Entity<User>().Where(w=> w.SearcheableField == "Joe");
Now, what do you expect to have in your database? Joe or Niko?
And in the list array, do you want the user you just changed the name?
From my point of view, is better if you use a different approach to the problem.
For example, if you can query the data in memory, you can use this approach:
public interface ISearcheable {
string SearcheableField { get; }
}
public class User : ISearcheable {
public string Username { get; set; }
public string SearcheableField
{
get
{
return Username;
}
}
}
If you can't and won't change your DB for save a new column, you can use an Extension Method to it:
public static IQueryable<User> Search(this IQueryable<Usery> query, string value)
{
return query.Where(x => x.Username == value);
}
And then you can use it in this way:
db.Entity<User>().AsQueryable().Search("username to check");
Pay attention: I wrote it by hand and whith method names I remember. There can be few errors in synstax and code can't compile.
Related
As a stop measure, I'd like developers to get data based on specific criteria but I don't want to expose the entire object for a where clause.
dbContext.BigThing
.Select(s => new LesserThing { FieldA= s.FieldA, FieldB = s.FieldB })
.Where(someQueryExpression)
.TakeTheEntireEntity();
The someQueryExpression will be of type Expression<Func<LesserThing, bool>>.
The TakeTheEntireEntity is sudo code for, how do I get the entire data model? I can use the dbContext again as an inner Where clause but this would trigger 2 queries and evaluate client side, which is bad. One trip to the db is required.
The idea here is to allow developers to consume this service but prevent them from querying Where SomeNonIndexedField cannot be used.
There are many ways to accomplish this (none out-of-the box though), with all they requiring expression tree manipulation.
But if the goal is just to limit the fields available in the Where conditions, there is much simpler approach which works out-of-the-box and requires less coding to apply.
Just make the LesserThing interface and let BiggerThing implement it implicitly. e.g.
public interface ISomeEntityFilter
{
string FieldA { get; }
DateTime FieldB { get; }
}
public class SomeEntity : ISomeEntityFilter
{
public int Id { get; set; }
public string FieldA { get; set; }
public DateTime FieldB { get; set; }
// ... many others
}
Now, given
Expression<Func<ISomeEnityFilter, bool>> filter
coming from the caller, what you do is simply applying it and then casting back to the original type (the latter is needed because the first operation changes the generic type of the result from IQueryable<SomeEntity> to IQueryable<ISomeEntityFilter>, but you know that it sill actually holds SomeEntity elements):
var query = dbContext.Set<SomeEntity>()
.Where(filter)
.Cast<SomeEntity>();
And yes (you can easily verify that), the result is server (SQL) translatable EF Core query.
What you want to do requires working with the Expression tree to transform a lambda of the form pl => pl.prop == x to a new lambda p => p.prop == x.
This code works with either properties or fields, but assumes that the LesserThing will have only member names that also exist in the BigThing. Of course, you could also create a Dictionary and map the LesserThing member names to BigThing member names.
Since you can't infer generic type parameters from the return, you have to manually pass in the types.
var ans = dbContext.BigThing.Where(someQueryExpression.TestBigThing<BigThing,LesserThing>());
Since the type of someQueryExpression must be Expression<Func<LesserThing,bool>> it is only possible to access fields or properties of LesserThing or outside variables or constants.
public static class TestExt {
public static Expression<Func<TBig, bool>> TestBigThing<TBig, TLesser>(this Expression<Func<TLesser, bool>> pred) {
// (T p)
var newParm = Expression.Parameter(typeof(TBig), "p");
var oldMemberExprs = pred.Body.CollectMemberExpressions();
var newMemberExprs = oldMemberExprs.Select(m => Expression.PropertyOrField(newParm, m.Member.Name));
var newBody = pred.Body;
foreach (var me in oldMemberExprs.Zip(newMemberExprs))
newBody = newBody.Replace(me.First, me.Second);
// p => {newBody}
return Expression.Lambda<Func<TBig,bool>>(newBody, newParm);
}
/// <summary>
/// Collects all the MemberExpressions in an Expression
/// </summary>
/// <param name="e">The original Expression.</param>
/// <returns>List<MemberExpression></returns>
public static List<MemberExpression> CollectMemberExpressions<T>(this T e) where T : Expression {
new CollectMemberVisitor().Visit(e);
return CollectMemberVisitor.MemberExpressions;
}
/// <summary>
/// ExpressionVisitor to collect all MemberExpressions.
/// </summary>
public class CollectMemberVisitor : ExpressionVisitor {
public static List<MemberExpression> MemberExpressions;
public CollectMemberVisitor() {
MemberExpressions = new List<MemberExpression>();
}
protected override Expression VisitMember(MemberExpression node) {
MemberExpressions.Add(node);
return node;
}
}
}
I'm trying to write a repository method for Entity Framework Core 2.0 that can handle returning child collections of properties using .ThenInclude, but I'm having trouble with the second expression. Here is a working method for .Include, which will return child properties (you supply a list of lambdas) of your entity.
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
Now here is my attempt at writing a method that will take a Tuple of two Expressions and feed those into a .Include(a => a.someChild).ThenInclude(b => b.aChildOfSomeChild) chain. This isn't a perfect solution because it only handles one child of a child, but it's a start.
public T GetSingle(Expression<Func<T, bool>> predicate, params Tuple<Expression<Func<T, object>>, Expression<Func<T, object>>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty.Item1).ThenInclude(includeProperty.Item2);
}
return query.Where(predicate).FirstOrDefault();
}
Intellisense returns an error saying "The type cannot be inferred from the usage, try specifying the type explicitly". I have a feeling it's because the expression in Item2 needs to be classified as somehow related to Item1, because it needs to know about the child relationship it has.
Any ideas or better techniques for writing a method like this?
I found this repository method online and it does exactly what I wanted. Yared's answer was good, but not all the way there.
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public TResult GetFirstOrDefault<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (orderBy != null)
{
return orderBy(query).Select(selector).FirstOrDefault();
}
else
{
return query.Select(selector).FirstOrDefault();
}
}
Usage:
var affiliate = await affiliateRepository.GetFirstOrDefaultAsync(
predicate: b => b.Id == id,
include: source => source
.Include(a => a.Branches)
.ThenInclude(a => a.Emails)
.Include(a => a.Branches)
.ThenInclude(a => a.Phones));
I had the same issue since EF Core doesn't support lazy loading but i tried to get workaround in the following way:
First create an attribute class to mark our desired navigation properties from other properties of a given class.
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class NavigationPropertyAttribute : Attribute
{
public NavigationPropertyAttribute()
{
}
}
Extension methods to filter out navigation properties and apply Include/ThenInclude using string based Eager loading.
public static class DbContextHelper
{
public static Func<IQueryable<T>, IQueryable<T>> GetNavigations<T>() where T : BaseEntity
{
var type = typeof(T);
var navigationProperties = new List<string>();
//get navigation properties
GetNavigationProperties(type, type, string.Empty, navigationProperties);
Func<IQueryable<T>, IQueryable<T>> includes = ( query => {
return navigationProperties.Aggregate(query, (current, inc) => current.Include(inc));
});
return includes;
}
private static void GetNavigationProperties(Type baseType, Type type, string parentPropertyName, IList<string> accumulator)
{
//get navigation properties
var properties = type.GetProperties();
var navigationPropertyInfoList = properties.Where(prop => prop.IsDefined(typeof(NavigationPropertyAttribute)));
foreach (PropertyInfo prop in navigationPropertyInfoList)
{
var propertyType = prop.PropertyType;
var elementType = propertyType.GetTypeInfo().IsGenericType ? propertyType.GetGenericArguments()[0] : propertyType;
//Prepare navigation property in {parentPropertyName}.{propertyName} format and push into accumulator
var properyName = string.Format("{0}{1}{2}", parentPropertyName, string.IsNullOrEmpty(parentPropertyName) ? string.Empty : ".", prop.Name);
accumulator.Add(properyName);
//Skip recursion of propert has JsonIgnore attribute or current property type is the same as baseType
var isJsonIgnored = prop.IsDefined(typeof(JsonIgnoreAttribute));
if(!isJsonIgnored && elementType != baseType){
GetNavigationProperties(baseType, elementType, properyName, accumulator);
}
}
}
}
Sample POCO classes implementing NavigationPropertyAttribute
public class A : BaseEntity{
public string Prop{ get; set; }
}
public class B : BaseEntity{
[NavigationProperty]
public virtual A A{ get; set; }
}
public class C : BaseEntity{
[NavigationProperty]
public virtual B B{ get; set; }
}
Usage in Repository
public async Task<T> GetAsync(Expression<Func<T, bool>> predicate)
{
Func<IQueryable<T>, IQueryable<T>> includes = DbContextHelper.GetNavigations<T>();
IQueryable<T> query = _context.Set<T>();
if (includes != null)
{
query = includes(query);
}
var entity = await query.FirstOrDefaultAsync(predicate);
return entity;
}
Json result for sample class C would be:
{
"B" : {
"A" : {
"Prop" : "SOME_VALUE"
}
}
}
Back in EF6 we could write something like this:
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));
And it was perfect and simple. We could expose it in an repository without dragging references from the EF assembly to other projects.
This was removed from EF Core, but since EF6 is open-source, the method that transforms the lambda expressions in paths can easily be extracted to use in EF Core so you can get the exact same behavior.
Here's the complete extension method.
/// <summary>
/// Provides extension methods to the <see cref="Expression" /> class.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Converts the property accessor lambda expression to a textual representation of it's path. <br />
/// The textual representation consists of the properties that the expression access flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The property selector expression.</param>
/// <returns>The extracted textual representation of the expression's path.</returns>
public static string AsPath(this LambdaExpression expression)
{
if (expression == null)
return null;
TryParsePath(expression.Body, out var path);
return path;
}
/// <summary>
/// Recursively parses an expression tree representing a property accessor to extract a textual representation of it's path. <br />
/// The textual representation consists of the properties accessed by the expression tree flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The expression tree to parse.</param>
/// <param name="path">The extracted textual representation of the expression's path.</param>
/// <returns>True if the parse operation succeeds; otherwise, false.</returns>
private static bool TryParsePath(Expression expression, out string path)
{
var noConvertExp = RemoveConvertOperations(expression);
path = null;
switch (noConvertExp)
{
case MemberExpression memberExpression:
{
var currentPart = memberExpression.Member.Name;
if (!TryParsePath(memberExpression.Expression, out var parentPart))
return false;
path = string.IsNullOrEmpty(parentPart) ? currentPart : string.Concat(parentPart, ".", currentPart);
break;
}
case MethodCallExpression callExpression:
switch (callExpression.Method.Name)
{
case nameof(Queryable.Select) when callExpression.Arguments.Count == 2:
{
if (!TryParsePath(callExpression.Arguments[0], out var parentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
if (!(callExpression.Arguments[1] is LambdaExpression subExpression))
return false;
if (!TryParsePath(subExpression.Body, out var currentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
path = string.Concat(parentPart, ".", currentPart);
return true;
}
case nameof(Queryable.Where):
throw new NotSupportedException("Filtering an Include expression is not supported");
case nameof(Queryable.OrderBy):
case nameof(Queryable.OrderByDescending):
throw new NotSupportedException("Ordering an Include expression is not supported");
default:
return false;
}
}
return true;
}
/// <summary>
/// Removes all casts or conversion operations from the nodes of the provided <see cref="Expression" />.
/// Used to prevent type boxing when manipulating expression trees.
/// </summary>
/// <param name="expression">The expression to remove the conversion operations.</param>
/// <returns>The expression without conversion or cast operations.</returns>
private static Expression RemoveConvertOperations(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
expression = ((UnaryExpression)expression).Operand;
return expression;
}
}
Then you can use it like this (put it in an QueryableExtensions class or something like that):
/// <summary>
/// Specifies related entities to include in the query result.
/// </summary>
/// <typeparam name="T">The type of entity being queried.</typeparam>
/// <param name="source">The source <see cref="IQueryable{T}" /> on which to call Include.</param>
/// <param name="paths">The lambda expressions representing the paths to include.</param>
/// <returns>A new <see cref="IQueryable{T}" /> with the defined query path.</returns>
internal static IQueryable<T> Include<T>(this IQueryable<T> source, params Expression<Func<T, object>>[] paths)
{
if (paths != null)
source = paths.Aggregate(source, (current, include) => current.Include(include.AsPath()));
return source;
}
And then in your repository you call it normally like you would do in EF6:
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));
References:
How to pass lambda 'include' with multiple levels in Entity Framework Core?
https://github.com/aspnet/EntityFramework6
When I need .ThenInclude I add my dbcontext class as dependency injection and write my query directly from dbcontext reference. I don't know if it is good or bad practice.
I have a class Product with a set of properties:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public string Categories { get; set; }
}
From a component, I obtain a List<Product> and, for several reasons, I need to use Reflection to get the properties of Product and then get the Distinct values and their Count() for each property.
Is it possible to achieve my goal through reflection? If not is there any other way to do it? Thanks!
UPDATE
The problem is that I do not know in advance which properties I have to use and which properties are in the Product class. That's why I think reflection is the best option.
I can achieve the same result by using a Switch - Case construct where the switch compare the Property Name extarcted from the class and each Case corresponds to a specific Property Name. But the flexibility of this solution is not enough for my problem
So it sounds like you're asking for something slightly different than the rest of us previously thought. You're not looking for the number of distinct values, or you're looking for the number of duplicates of each distinct value, which is essentially a group-by with a count of each group.
private static Dictionary<string, Dictionary<object, int>>
getDistinctValues<T>(List<T> list)
{
var properties = typeof(T).GetProperties();
var result = properties
//The key of the first dictionary is the property name
.ToDictionary(prop => prop.Name,
//the value is another dictionary
prop => list.GroupBy(item => prop.GetValue(item, null))
//The key of the inner dictionary is the unique property value
//the value if the inner dictionary is the count of that group.
.ToDictionary(group => group.Key, group => group.Count()));
return result;
}
At one point I had broken this up into two methods, but I condensed it down a bit to the point where I don't think it's needed. If you have trouble wrapping your head around all of the levels of nesting of this query feel free to ask for further clarifications.
private static int DistinctCount<T>(IEnumerable<T> items, string property)
{
var propertyInfo = typeof(T).GetProperty(property);
return items.Select(x => propertyInfo.GetValue(x, null)).Distinct().Count();
}
Usage:
List<Product> prods = GetProductsFromSomeplace();
int distinctCountById = DistinctCount(prods, "Id");
int distinctCountByName = DistinctCount(prods, "Name");
int distinctCountByCategories = DistinctCount(prods, "Categories");
If you want to allow for a custom IEqualityComparer for the properties, you can have an overload:
private static int DistinctCount<TItems, TProperty>(IEnumerable<TItems> items,
string property,
IEqualityComparer<TProperty> propertyComparer)
{
var propertyInfo = typeof(TItems).GetProperty(property);
return items.Select(x => (TProperty)propertyInfo.GetValue(x, null))
.Distinct(propertyComparer).Count();
}
And use like so:
List<Product> prods = GetProductsFromSomeplace();
int distinctCountById = DistinctCount(prods, "Id", new MyCustomIdComparer());
Where MyCustomIdComparer implements IEqualityComparer<TProperty> (in this case IEC<int>)
I present a solution below - but ideally you should look at breaking creating an abstraction for this problem that allows an object returning an IEnumerable<T> to provide a list of 'filterable' properties of the T, along with the values that are to be used. That way whatever is returning the data from the data source can do this with full knowledge. It pushes more of the work back to your data source/service/whatever, but it makes your UI much simpler.
Since you don't know the properties - then you can do this (I'm assuming assuming an IEnumerable because I'm assuming a generic solution is out - since you say you need reflection). If you do have a typed expression (i.e. you actually have a List<Product>) then a generic solution would be better as it would remove the need to get the first item:
public Dictionary<string, IEnumerable<object>>
GetAllPropertyDistincts(IEnumerable unknownValues)
{
//need the first item for the type:
var first = unknownValues.Cast<object>().First(); //obviously must NOT be empty :)
var allDistinct = first.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => new
{
PropName = p.Name,
Distinct = unknownValues.Cast<object>().Select(
o => property.GetValue(o, null)
).Distinct()
}).ToDictionary(v => v.PropName, v => v.Distinct);
}
Now you have a dictionary, keyed by the property name of every distinct value for each property of every object in your untyped enumerable (well - assuming they're all of the same type or base). Note - there might be some issues with properties of certain types and the default IEqualityComparer that the Distinct extension method uses - because it's a generic method and at the moment it'll be using EqualityComparer<object>.Default - which won't necessarily work for some types.
To turn this into a generic solution you can just change the first four lines to:
public Dictionary<string, IEnumerable<object>>
GetAllPropertyDistincts<T>(IEnumerable<T> unknownValues)
{
var allDistinct = typeof(T)
With the .GetProperties(BindingFlags.Public | BindingFlags.Instance) line following, and then change the inner call unknownValues.Cast<object>().Select( to just unknownValues.Select(.
If the list is not typed with Product but indeed with an open generic parameter T and this parameter has no restriction (where T : Product) then casting can help
int count = list
.Cast<Product>()
.Select(p => p.Id)
.Distinct()
.Count();
Okay, so I got slightly carried out with my answer, but here it is... a fully fledged distinct value counter. This doesn't completely answer your question, but should be a good start towards counting a property on a given object. Using this, in conjunction to looping through all of the properties on an object, should do the trick :p
/// <summary>
/// A distinct value counter, using reflection
/// </summary>
public class DistinctValueCounter<TListItem>
{
/// <summary>
/// Gets or sets the associated list items
/// </summary>
private IEnumerable<TListItem> ListItems { get; set; }
/// <summary>
/// Constructs a new distinct value counter
/// </summary>
/// <param name="listItems">The list items to check</param>
public DistinctValueCounter(IEnumerable<TListItem> listItems)
{
this.ListItems = listItems;
}
/// <summary>
/// Gets the distinct values, and their counts
/// </summary>
/// <typeparam name="TProperty">The type of the property expected</typeparam>
/// <param name="propertyName">The property name</param>
/// <returns>A dictionary containing the distinct counts, and their count</returns>
public Dictionary<TProperty, int> GetDistinctCounts<TProperty>(string propertyName)
{
var result = new Dictionary<TProperty, int>();
// check if there are any list items
if (this.ListItems.Count() == 0)
{
return result;
}
// get the property info, and check it exists
var propertyInfo = this.GetPropertyInfo<TProperty>(this.ListItems.FirstOrDefault(), propertyName);
if (propertyInfo == null)
{
return result;
}
// get the values for the property, from the list of items
return ListItems.Select(item => (TProperty)propertyInfo.GetValue(item, null))
.GroupBy(value => value)
.ToDictionary(value => value.Key, value => value.Count());
}
/// <summary>
/// Gets the property information, for a list item, by its property name
/// </summary>
/// <typeparam name="TProperty">The expected property type</typeparam>
/// <param name="listItem">The list item</param>
/// <param name="propertyName">The property name</param>
/// <returns>The property information</returns>
private PropertyInfo GetPropertyInfo<TProperty>(TListItem listItem, string propertyName)
{
// if the list item is null, return null
if (listItem == null)
{
return null;
}
// get the property information, and check it exits
var propertyInfo = listItem.GetType().GetProperty(propertyName);
if (propertyInfo == null)
{
return null;
}
// return the property info, if it is a match
return propertyInfo.PropertyType == typeof(TProperty) ? propertyInfo : null;
}
}
Usage:
var counter = new DistinctValueCounter<Person>(people);
var resultOne = counter.GetDistinctCounts<string>("Name");
If I understand the goal, you should be able to just use LINQ:
List<Product> products = /* whatever */
var distinctIds = products.Select(p=>p.Id).Distinct();
var idCount = distinctIds.Count();
...
I am trying to use Expression Trees and anonymous types to achieve the following.
Let's say I have this class:
class Person
{
public string FirstName {get;set;}
public string MiddleName {get;set;}
public string LastName {get;set;}
public DateTime DateOfBirth {get;set;}
}
Now I want to be able to call the following:
string[] names = Foo<Person>(x=> new { x.LastName, x.DateOfBirth });
I want names to contain 2 items, "LastName" and "DateOfBirth".
I am trying to extend PetaPoco, in a compile time safe way rather than writing string sql, so that I can specify a list of properties/columns I want to include in the SQL, rather than it selecting everything. I have some pretty large entities and there are cases where I do not want to select all the columns for performance reasons.
Try this out for size:
public static string[] Foo<T, TResult>(Expression<Func<T, TResult>> func)
{
return typeof(TResult).GetProperties().Select(pi => pi.Name).ToArray();
}
As you are returning an anonymous type from your lamda, you are able loop over all the properties of this anonymous type and use the inferred names of the properties. However when using this the syntax would be more like:
Foo((Person x) => new { x.LastName, x.DateOfBirth });
This is because the second generic argument is an anoymous type.
I'm lazy so this code handles only public properties. But it should be a good base to get you started.
public static string[] Foo<T>(Expression<Func<T, object>> func)
{
var properties = func.Body.Type.GetProperties();
return typeof(T).GetProperties()
.Where(p => properties.Any(x => p.Name == x.Name))
.Select(p =>
{
var attr = (ColumnAttribute) p.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
return (attr != null ? attr.Name : p.Name);
}).ToArray();
}
The answers given here work when either only single property is selected, OR when multiple properties are selected. None of them work for both. The answer by Lukazoid only works for multiple properties, the rest for single property, as of writing my answer.
The code below considers both the case, that is, you can use it for selecting single property AND multiple properties. Please note that I haven't added any sanity checking here, so feel free to add your own.
string[] Foo<T>(Expression<Func<Person, T>> func)
{
if (func.Body is NewExpression)
{
// expression selects multiple properties,
// OR, single property but as an anonymous object
// extract property names right from the expression itself
return (func.Body as NewExpression).Members.Select(m => m.Name).ToArray();
// Or, simply using reflection, as shown by Lukazoid
// return typeof(T).GetProperties().Select(p => p.Name).ToArray();
}
else
{
// expression selects only a single property of Person,
// and not as an anonymous object.
return new string[] { (func.Body as MemberExpression).Member.Name };
}
}
Or more succinctly, using a ternary operator it all becomes just this:
string[] Foo<T>(Expression<Func<Person, T>> func)
{
return (func.Body as NewExpression) != null
? typeof(T).GetProperties().Select(p => p.Name).ToArray()
: new string[] { (func.Body as MemberExpression).Member.Name };
}
Download LinkPad file: LinkPad
See it online: Repl.it
Please feel free to point out anything that I may have missed.
A page of code is a thousand words, so here's how Microsoft does it in Prism:
///<summary>
/// Provides support for extracting property information based on a property expression.
///</summary>
public static class PropertySupport
{
/// <summary>
/// Extracts the property name from a property expression.
/// </summary>
/// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
/// <param name="propertyExpression">The property expression (e.g. p => p.PropertyName)</param>
/// <returns>The name of the property.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="propertyExpression"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the expression is:<br/>
/// Not a <see cref="MemberExpression"/><br/>
/// The <see cref="MemberExpression"/> does not represent a property.<br/>
/// Or, the property is static.
/// </exception>
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException(Resources.PropertySupport_NotMemberAccessExpression_Exception, "propertyExpression");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException(Resources.PropertySupport_ExpressionNotProperty_Exception, "propertyExpression");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException(Resources.PropertySupport_StaticExpression_Exception, "propertyExpression");
}
return memberExpression.Member.Name;
}
}
If you want to take attributes into account it's going to be slightly more complicated, but the general idea of accepting an Expression<Func<T>> and fishing out the name of the property being targeted is the same.
Update: As is, the method will accept only one parameter; I only provided it as a guideline. The idea can be generalized of course:
public static string[] ExtractPropertyNames<T>(
Expression<Func<T, object>> propertyExpression)
This method will accept an expression that takes a T and returns an anonymous type which you can then reflect upon. You could substitute a second type parameter for object but that doesn't really do anything here because the only thing you want to do is reflect on the type.
I guess you have to disassemble code for Html.LabelFor(LabelExtensions.LabelFor<TModel,TValue> from System.Web.Mvc assembly).
For example, look at ExpressionHelper.GetExpressionText
As for replacing member name with attribute member value - you'll have to use old fashioned reflection.
I am working in a MVC3 project, and i am using LINQ to SQL. I have a database schema that uses a field to indicate if the record is active or deleted (field is boolean named "Active").
Now suppose there are two table linked such as State, and City, where City references State.
Let's say i have a method that returns a list of states:
public ActionResult ListStates()
{
return View(_repository.ListStates());
}
Now, i have implemented the repository method to return all states, and i could implement it in the following way:
public class Repository
{
public IQueryable<State> ListStates()
{
return dataContext.States.Where(p => p.Active == true)
}
}
In the view i could be sure i'm using only active states. But to be sure i'm using only active cities i would need to filter it in view, which makes the view uglier, or implement a custom view model. Both cases are valid, but they require a lot of work.
I have seen there are methods in data context where we can implement certain operations before an object gets inserted/updated into database, as this examle:
public partial class DatabaseDataContext
{
partial void InsertState(State instance)
{
instance.Active = true;
this.ExecuteDynamicInsert(instance);
}
}
The above method gets executed whenever an insert of the State object is happening.
My question is, is there a way to implement a condition only in one place for an object, for example to return only active records whenever a select is performed?
If I understood correctly, you're trying to eliminate the need of specifying .Where(p => p.Active == true) on methods of your repositories and you want to define it only once.
I'm not sure whether you can achieve this without creating a data context wrapper, because for each query you have to combine two logical expressions, the expression that comes from repository and p => p.Active == true.
The most simplest solution would be as follows:
/// <summary>
/// A generic class that provides CRUD operations againts a certain database
/// </summary>
/// <typeparam name="Context">The Database context</typeparam>
/// <typeparam name="T">The table object</typeparam>
public class DataContextWrapper<Context> where Context : DataContext, new()
{
Context DataContext;
/// <summary>
/// The name of the connection string variable in web.config
/// </summary>
string ConnectionString
{
get
{
return "Connection String";
}
}
/// <summary>
/// Class constructor that instantiates a new DataContext object and associates the connection string
/// </summary>
public DataContextWrapper()
{
DataContext = new Context();
DataContext.Connection.ConnectionString = ConnectionString;
}
protected IEnumerable<T> GetItems<T>([Optional] Expression<Func<T, bool>> query) where T : class, new()
{
//get the entity type
Type entity = typeof(T);
//get all properties
PropertyInfo[] properties = entity.GetProperties();
Expression<Func<T, bool>> isRowActive = null;
//we are interested in entities that have Active property ==> to distinguish active rows
PropertyInfo property = entity.GetProperties().Where(prop => prop.Name == "Active").SingleOrDefault();
//if the entity has the property
if (property != null)
{
//Create a ParameterExpression from
//if the query is specified then we need to use a single ParameterExpression for the whole final expression
ParameterExpression para = (query == null) ? Expression.Parameter(entity, property.Name) : query.Parameters[0];
var len = Expression.PropertyOrField(para, property.Name);
var body = Expression.Equal(len, Expression.Constant(true));
isRowActive = Expression.Lambda<Func<T, bool>>(body, para);
}
if (query != null)
{
//combine two expressions
var combined = Expression.AndAlso(isRowActive.Body, query.Body);
var lambda = Expression.Lambda<Func<T, bool>>(combined, query.Parameters[0]);
return DataContext.GetTable<T>().Where(lambda);
}
else if (isRowActive != null)
{
return DataContext.GetTable<T>().Where(isRowActive);
}
else
{
return DataContext.GetTable<T>();
}
}
}
And then you can create your repositories like this:
/// <summary>
/// States Repository
/// </summary>
public class StatesRepository : DataContextWrapper<DEMODataContext>
{
/// <summary>
/// Get all active states
/// </summary>
/// <returns>All active states</returns>
public IEnumerable<State> GetStates()
{
return base.GetItems<State>();
}
/// <summary>
/// Get all active states
/// </summary>
/// <param name="pattern">State pattern</param>
/// <returns>All active states tha contain the given pattern</returns>
public IEnumerable<State> GetStates(string pattern)
{
return base.GetItems<State>(s=>s.Description.Contains(pattern));
}
}
The usage:
StatesRepository repo = new StatesRepository();
var activeStates = repo.GetStates();
and
var filtered = repo.GetStates("Al");
Hope this helps ;)
You are looking for the dynamic linq library:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I've used this before to insert a Where IsActive = true into all select statements before.