Is it possible to make the LINQ SELECT more flexible by not working with properties but with the column name?
Maybe an example will help..I'm trying to do the following (pseudocode):
From x In Entities
Where ...
Select("ID", "Value" , "Date")
but depending on certain choices, I would like to have the result in different order
From x In Entities
Where ...
Select("Value", "Date", "ID" )
Or another amount of SELECT results
From x In Entities
Where ...
Select("Value")
Any help to make this as dynamic as possible would be AWESOME! tnx
Maybe this will help you
from x In Entities
where ... select new {
Value = x["Value"],
Date = x["Date"],
ID = x["ID"]
}
Like I said in my comment (handles subproperties, like Type.Name, but not multiple fields)
I let the fun to make the multi field version ;)
public static class DynamicLinkExtensions
{
public static IEnumerable<dynamic> Select<T>(this IQueryable<T> source, string memberAccess)
{
var propNames = memberAccess.Split('.');
var type = typeof(T);
var props = new List<PropertyInfo>();
foreach (var propName in propNames)
{
var prop = type.GetProperty(propName);
props.Add(prop);
type = prop.PropertyType;
}
return source.Select(props.ToArray());
}
public static IEnumerable<dynamic> Select<T>(this IQueryable<T> source, PropertyInfo[] props)
{
var parameter = Expression.Parameter(typeof(T));
var member = Expression.MakeMemberAccess(parameter, (MemberInfo)props.First());
for (var i = 1; i < props.Length; i++)
{
member = Expression.MakeMemberAccess(member, (MemberInfo)props[i]);
}
Expression<Func<T, object>> expression = Expression.Lambda<Func<T, object>>(member, new[] { parameter });
return source.Select(expression);
}
}
Usage:
var names = DataContext.Customers.Select("Name").Cast<string>().ToList();
Related
how to pass a values dynamically to an Marionette.CompositeView during run time? like in java we create a method like the following
package com.test.poc;
public class SampleMethod {
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
SampleMethod method = new SampleMethod();
int firstValue = 90, secondValue = 90;
System.out.println("add : " + method.add(firstValue, secondValue));
}
}
the above is the simple java code anybody can understand like the above how to create and pass arguments to Marionette.CompositeView and work on them?
Best Regards
at the moment you instanciate a view, you can pass whatever arguments you want. normally you pass the model and the collection to be rendered in the compositeView, but you can pass more data if you need.
var MyCompositeView = Backbone.Mationette.CompositeView.extend({
initialize : function (options){
this.dataValue = options.dataValue1;
this.helperObject = options.helperObject;
this.useValues();
},
useValues: function () {
console.log(this.dataValue);
}
});
var helperObject = {
value3 : "I have a value",
value4 : "I dont!"
}; /// a js object literal
var myModel = new MyModel();
var myCollection = new MyCollection();
var myCompositeView = new MyCompositeView({model:myModel,
collection:myCollection,
helperObject:helperObject,
dataValue1:"Hi there"});
notice that Im passing 4 values in the at the time to intanciate the view, and Im reading just two of them, the model and the collection will be handled by marionette, but the other two you can read them in your initialize function.
hope that helps.
This question already has answers here:
How do you create a dropdownlist from an enum in ASP.NET MVC?
(36 answers)
Closed 8 years ago.
I've been finding all over the place that the common way to bind Enums to DropDowns is through helper methods, which seems a bit overbearing for such a seemingly simple task.
What is the best way to bind Enums to DropDownLists in ASP.Net MVC 4?
You can to this:
#Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
I think it is about the only (clean) way, which is a pity, but at least there are a few options out there. I'd recommend having a look at this blog: http://paulthecyclist.com/2013/05/24/enum-dropdown/
Sorry, it's too long to copy here, but the gist is that he created a new HTML helper method for this.
All the source code is available on GitHub.
Enums are supported by the framework since MVC 5.1:
#Html.EnumDropDownListFor(m => m.Palette)
Displayed text can be customized:
public enum Palette
{
[Display(Name = "Black & White")]
BlackAndWhite,
Colour
}
MSDN link: http://www.asp.net/mvc/overview/releases/mvc51-release-notes#Enum
In my Controller:
var feedTypeList = new Dictionary<short, string>();
foreach (var item in Enum.GetValues(typeof(FeedType)))
{
feedTypeList.Add((short)item, Enum.GetName(typeof(FeedType), item));
}
ViewBag.FeedTypeList = new SelectList(feedTypeList, "Key", "Value", feed.FeedType);
In my View:
#Html.DropDownList("FeedType", (SelectList)ViewBag.FeedTypeList)
The solution from PaulTheCyclist is spot on. But I wouldn't use RESX (I'd have to add a new .resx file for each new enum??)
Here is my HtmlHelper Expression:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TEnum>> expression, object attributes = null)
{
//Get metadata from enum
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumType = GetNonNullableModelType(metadata);
var values = Enum.GetValues(enumType).Cast<TEnum>();
//Convert enumeration items into SelectListItems
var items =
from value in values
select new SelectListItem
{
Text = value.ToDescription(),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
};
//Check for nullable value types
if (metadata.IsNullableValueType)
{
var emptyItem = new List<SelectListItem>
{
new SelectListItem {Text = string.Empty, Value = string.Empty}
};
items = emptyItem.Concat(items);
}
//Return the regular DropDownlist helper
return htmlHelper.DropDownListFor(expression, items, attributes);
}
Here is how I declare my enums:
[Flags]
public enum LoanApplicationType
{
[Description("Undefined")]
Undefined = 0,
[Description("Personal Loan")]
PersonalLoan = 1,
[Description("Mortgage Loan")]
MortgageLoan = 2,
[Description("Vehicle Loan")]
VehicleLoan = 4,
[Description("Small Business")]
SmallBusiness = 8,
}
And here is the call from a Razor View:
<div class="control-group span2">
<div class="controls">
#Html.EnumDropDownListFor(m => m.LoanType, new { #class = "span2" })
</div>
</div>
Where #Model.LoanType is an model property of the LoanApplicationType type
UPDATE: Sorry, forgot to include code for the helper function ToDescription()
/// <summary>
/// Returns Description Attribute information for an Enum value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string ToDescription(this Enum value)
{
if (value == null)
{
return string.Empty;
}
var attributes = (DescriptionAttribute[]) value.GetType().GetField(
Convert.ToString(value)).GetCustomAttributes(typeof (DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : Convert.ToString(value);
}
Technically, you don't need a helper method, since Html.DropdownListFor only requires a SelectList or Ienumerable<SelectListItem>. You can just turn your enums into such an output and feed it in that way.
I use a static library method to convert enums into List<SelectListItem> with a few params/options:
public static List<SelectListItem> GetEnumsByType<T>(bool useFriendlyName = false, List<T> exclude = null,
List<T> eachSelected = null, bool useIntValue = true) where T : struct, IConvertible
{
var enumList = from enumItem in EnumUtil.GetEnumValuesFor<T>()
where (exclude == null || !exclude.Contains(enumItem))
select enumItem;
var list = new List<SelectListItem>();
foreach (var item in enumList)
{
var selItem = new SelectListItem();
selItem.Text = (useFriendlyName) ? item.ToFriendlyString() : item.ToString();
selItem.Value = (useIntValue) ? item.To<int>().ToString() : item.ToString();
if (eachSelected != null && eachSelected.Contains(item))
selItem.Selected = true;
list.Add(selItem);
}
return list;
}
public static class EnumUtil
{
public static IEnumerable<T> GetEnumValuesFor<T>()
{
return Enum.GetValues(typeof(T)).Cast<T>();
}
// other stuff in here too...
}
/// <summary>
/// Turns Camelcase or underscore separated phrases into properly spaces phrases
/// "DogWithMustard".ToFriendlyString() == "Dog With Mustard"
/// </summary>
public static string ToFriendlyString(this object o)
{
var s = o.ToString();
s = s.Replace("__", " / ").Replace("_", " ");
char[] origArray = s.ToCharArray();
List<char> newCharList = new List<char>();
for (int i = 0; i < origArray.Count(); i++)
{
if (origArray[i].ToString() == origArray[i].ToString().ToUpper())
{
newCharList.Add(' ');
}
newCharList.Add(origArray[i]);
}
s = new string(newCharList.ToArray()).TrimStart();
return s;
}
Your ViewModel can pass in the options you want. Here's a fairly complex one:
public IEnumerable<SelectListItem> PaymentMethodChoices
{
get
{
var exclusions = new List<Membership.Payment.PaymentMethod> { Membership.Payment.PaymentMethod.Unknown, Membership.Payment.PaymentMethod.Reversal };
var selected = new List<Membership.Payment.PaymentMethod> { this.SelectedPaymentMethod };
return GetEnumsByType<Membership.Payment.PaymentMethod>(useFriendlyName: true, exclude: exclusions, eachSelected: selected);
}
}
So you wire your View's DropDownList against that IEnumerable<SelectListItem> property.
Extending the html helper to do it works well, but if you'd like to be able to change the text of the drop down values based on DisplayAttribute mappings, then you would need to modify it similar to this,
(Do this pre MVC 5.1, it's included in 5.1+)
public static IHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> html, Expression<Func<TModel, TEnum>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var enumType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;
var enumValues = Enum.GetValues(enumType).Cast<object>();
var items = enumValues.Select(item =>
{
var type = item.GetType();
var member = type.GetMember(item.ToString());
var attribute = member[0].GetCustomAttribute<DisplayAttribute>();
string text = attribute != null ? ((DisplayAttribute)attribute).Name : item.ToString();
string value = ((int)item).ToString();
bool selected = item.Equals(metadata.Model);
return new SelectListItem
{
Text = text,
Value = value,
Selected = selected
};
});
return html.DropDownListFor(expression, items, string.Empty, null);
}
Is there a way in EntityFramework (and the resulting LINQ) to query against a property of an entity that is not hard-coded?
Let's say, something that can be used for a search function.
public IList<Entity> Search (string propertyName, object value) {
// something that'll do the following
return context.Set<Entity>()
.Where(x => x.propertyName == value)
.ToList()
;
}
How about Property Descriptor?
The following code seems to do what you require:
string propertyName = "Length";
List<string> testList = new List<string>();
testList.Add("String1");
testList.Add("String10");
testList.Add("String100");
testList.Add("String1000");
System.ComponentModel.PropertyDescriptorCollection props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(string));
System.ComponentModel.PropertyDescriptor desc = props.Find(propertyName, false);
IEnumerable<object> obj = from env in testList
select desc.GetValue(env);
foreach (object it in obj)
{
Console.WriteLine(it.ToString());
}
You can build equals expression manually like this
private static Expression<Func<TEntity, bool>> BuildEqualExpression<TEntity>(string propertyName, object value)
{
var param = Expression.Parameter(typeof(TEntity), "x");
var body = Expression.MakeBinary(ExpressionType.Equal,
Expression.Property(param, propertyName), Expression.Constant(value));
return Expression.Lambda<Func<TEntity, bool>>(body, new ParameterExpression[] { param });
}
and then use it in your LINQ query
var expression = BuildEqualExpression<TEntity>(propertyName, value);
return context.Set<TEntity>().Where(expression).ToList();
I'm attempting to perform dynamic sorting of data that I'm putting into grids into our MVC UI. Since MVC is abstracted from everything else via WCF, I've created a couple utility classes and extensions to help with this. The two most important things (slightly simplified) are as follows:
public static IQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
var sortedSortOptions = (from o in sortOptions
orderby o.Priority ascending
select o).ToList();
var results = collection;
foreach (var option in sortedSortOptions)
{
var currentOption = option;
var propertyName = currentOption.Property.MemberWithoutInstance();
var isAscending = currentOption.IsAscending;
if (isAscending)
{
results = from r in results
orderby propertyName ascending
select r;
}
else
{
results = from r in results
orderby propertyName descending
select r;
}
}
return results;
}
public interface ISortOption<TModel, TProperty> where TModel : class
{
Expression<Func<TModel, TProperty>> Property { get; set; }
bool IsAscending { get; set; }
int Priority { get; set; }
}
I've not given you the implementation for MemberWithoutInstance() but just trust me in that it returns the name of the property as a string. :-)
Following is an example of how I would consume this (using a non-interesting, basic implementation of ISortOption<TModel, TProperty>):
var query = from b in CurrentContext.Businesses
select b;
var sortOptions = new List<ISortOption<Business, object>>
{
new SortOption<Business, object>
{
Property = (x => x.Name),
IsAscending = true,
Priority = 0
}
};
var results = query.ApplySortOptions(sortOptions);
As I discovered with this question, the problem is specific to my orderby propertyName ascending and orderby propertyName descending lines (everything else works great as far as I can tell). How can I do this in a dynamic/generic way that works properly?
You should really look at using Dynamic LINQ for this. In fact, you may opt to simply list the properties by name instead of using an expression, making it somewhat easier to construct.
public static IQueryable<T> ApplySortOptions<T, TModel, TProperty>(this IQueryable<T> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
var results = collection;
foreach (var option in sortOptions.OrderBy( o => o.Priority ))
{
var currentOption = option;
var propertyName = currentOption.Property.MemberWithoutInstance();
var isAscending = currentOption.IsAscending;
results = results.OrderBy( string.Format( "{0}{1}", propertyName, !isAscending ? " desc" : null ) );
}
return results;
}
While I think #tvanfosson's solution will function perfectly, I'm also looking into this possibility:
/// <summary>
/// This extension method is used to help us apply ISortOptions to an IQueryable.
/// </summary>
/// <param name="collection">This is the IQueryable you wish to apply the ISortOptions to.</param>
/// <param name="sortOptions">These are the ISortOptions you wish to have applied. You must specify at least one ISortOption (otherwise, don't call this method).</param>
/// <returns>This returns an IQueryable object.</returns>
/// <remarks>This extension method should honor deferred execution on the IQueryable that is passed in.</remarks>
public static IOrderedQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
Debug.Assert(sortOptions != null, "ApplySortOptions cannot accept a null sortOptions input.");
Debug.Assert(sortOptions.Count() > 0, "At least one sort order must be specified to ApplySortOptions' sortOptions input.");
var firstSortOption = sortOptions.OrderBy(o => o.Priority).First();
var propertyName = firstSortOption.Property.MemberWithoutInstance();
var isAscending = firstSortOption.IsAscending;
// Perform the first sort action
var results = isAscending ? collection.OrderBy(propertyName) : collection.OrderByDescending(propertyName);
// Loop through all of the rest ISortOptions
foreach (var sortOption in sortOptions.OrderBy(o => o.Priority).Skip(1))
{
// Make a copy of this or our deferred execution will bite us later.
var currentOption = sortOption;
propertyName = currentOption.Property.MemberWithoutInstance();
isAscending = currentOption.IsAscending;
// Perform the additional orderings.
results = isAscending ? results.ThenBy(propertyName) : results.ThenByDescending(propertyName);
}
return results;
}
using the code from this question's answer:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder(source, property, "ThenByDescending");
}
private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
var props = property.Split('.');
var type = typeof (T);
var arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (var prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
var pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
var delegateType = typeof (Func<,>).MakeGenericType(typeof (T), type);
var lambda = Expression.Lambda(delegateType, expr, arg);
var result = typeof (Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof (T), type)
.Invoke(null, new object[] {source, lambda});
return (IOrderedQueryable<T>) result;
}
Let's say we have two tables with a many-to-many relationship:
public class Left{ /**/ }
public class Right{ /**/ }
public class LeftRight{ /**/ }
is the following sufficient to unhook these records (ignore the possibility of more than one relationship or no relationship defined)?
public void Unhook(Left left, Right right){
var relation = from x in Left.LeftRights where x.Right == right;
left.LeftRrights.Remove(relation.First());
Db.SubmitChanges();
}
Or do I have to do it on both parts? What's required here?
Here is a 'little' extension method I wrote to simplify this problem:
public static class EntitySetExtensions
{
public static void UpdateReferences<FK, FKV>(
this EntitySet<FK> refs,
Func<FK, FKV> fkvalue,
Func<FKV, FK> fkmaker,
Action<FK> fkdelete,
IEnumerable<FKV> values)
where FK : class
where FKV : class
{
var fks = refs.Select(fkvalue).ToList();
var added = values.Except(fks);
var removed = fks.Except(values);
foreach (var add in added)
{
refs.Add(fkmaker(add));
}
foreach (var r in removed)
{
var res = refs.Single(x => fkvalue(x) == r);
refs.Remove(res);
fkdelete(res);
}
}
}
It could probably be improved, but it has served me well :)
Example:
Left entity = ...;
IEnumerable<Right> rights = ...;
entity.LeftRights.UpdateReferences(
x => x.Right, // gets the value
x => new LeftRight { Right = x }, // make reference
x => { x.Right = null; }, // clear references
rights);
Algorithm description:
Suppose A and B is many-to-many relationship, where AB would be the intermediary table.
This will give you:
class A { EntitySet<B> Bs {get;} }
class B { EntitySet<A> As {get;} }
class AB { B B {get;} A A {get;} }
You now have an object of A, that reference many B's via AB.
Get all the B from A.Bs via 'fkvalue'.
Get what was added.
Get what was removed.
Add all the new ones, and construct AB via 'fkmaker'.
Delete all the removed ones.
Optionally, remove other referenced objects via 'fkdelete'.
I would like to improve this by using Expression instead, so I could 'template' the method better, but it would work the same.
Take two, using expressions:
public static class EntitySetExtensions
{
public static void UpdateReferences<FK, FKV>(
this EntitySet<FK> refs,
Expression<Func<FK, FKV>> fkexpr,
IEnumerable<FKV> values)
where FK : class
where FKV : class
{
Func<FK, FKV> fkvalue = fkexpr.Compile();
var fkmaker = MakeMaker(fkexpr);
var fkdelete = MakeDeleter(fkexpr);
var fks = refs.Select(fkvalue).ToList();
var added = values.Except(fks);
var removed = fks.Except(values);
foreach (var add in added)
{
refs.Add(fkmaker(add));
}
foreach (var r in removed)
{
var res = refs.Single(x => fkvalue(x) == r);
refs.Remove(res);
fkdelete(res);
}
}
static Func<FKV, FK> MakeMaker<FKV, FK>(Expression<Func<FK, FKV>> fkexpr)
{
var me = fkexpr.Body as MemberExpression;
var par = Expression.Parameter(typeof(FKV), "fkv");
var maker = Expression.Lambda(
Expression.MemberInit(Expression.New(typeof(FK)),
Expression.Bind(me.Member, par)), par);
var cmaker = maker.Compile() as Func<FKV, FK>;
return cmaker;
}
static Action<FK> MakeDeleter<FK, FKV>(Expression<Func<FK, FKV>> fkexpr)
{
var me = fkexpr.Body as MemberExpression;
var pi = me.Member as PropertyInfo;
var par = Expression.Parameter(typeof(FK), "fk");
var maker = Expression.Lambda(
Expression.Call(par, pi.GetSetMethod(),
Expression.Convert(Expression.Constant(null), typeof(FKV))), par);
var cmaker = maker.Compile() as Action<FK>;
return cmaker;
}
}
Now the usage is uber simple! :)
Left entity = ...;
IEnumerable<Right> rights = ...;
entity.LeftRights.UpdateReferences(x => x.Right, rights);
The first expression is now used to establish the 'relationship'. From there I can infer the 2 previously required delegates. Now no more :)
Important:
To get this to work properly in Linq2Sql, you need to mark the associations from intermediary table with 'DeleteOnNull="true"' in the dbml file. This will break the designer, but still works correctly with SqlMetal.
To unbreak the designer, you need to remove those additional attributes.
Personally, I'd replace
left.LeftRrights.Remove(relation.First());
with
Db.LeftRights.DeleteAllOnSubmit(relation)
because it seems more obvious what's going to happen. If you are wondering what the behaviour of ".Remove" is now, you'll be wondering anew when you look at this code in 6 months time.