【问题标题】:Expression to convert IQueryable<t> int List<SelectListItem>用于转换 IQueryable<t> int List<SelectListItem> 的表达式
【发布时间】:2015-12-21 16:30:58
【问题描述】:

我想创建一个这样的存储库方法:

public List<SelectListItem> AllAsSelectListItems(
    Expression<Func<T, string>> valueProperty, 
    Expression<Func<T, string>> textProperty, 
    string selectedValue = "")
{
    // what goes here? I am having serious trouble with this bit!
}

这将使我可以这样称呼它:

List<SelectListItem> selectListItems = PersonRepository.AllAsSelectListItems(
    m => m.ID,
    m => m.Name,
    selectedIDAsString
);

而且,selectedValue 参数为“1”,它应该产生如下结果:

List<SelectListItem>(){
    {Value: "1", Text: "Ted", Selected: true},
    {Value: "2", Text: "Sam", Selected: false},
    {Value: "3", Text: "Tracy", Selected: false}
};

我在使用通用 AllAsSelectListItems() 方法时遇到问题。

到目前为止,您可以在下面的代码中看到我的尝试。但这并不理想。

我使用了硬编码字符串来使用T 属性填充SelectListItem 属性。我认为表达式树是解决方案,但我正在努力正确编码。

另外,分配ID 属性会破坏它,因为它是int 而不是string

最后,我还在努力将selectedValue 参数与SelectListItem.Value 属性进行比较。


人员类

public class Person
{
    public int ID {get;set;}
    public string Name {get;set;}
}

控制器

public class PersonController : Controller 
{
    public IPersonRepository Repository {get;set;}

    public PersonController(IPersonRepository repository) 
    {
        Repository = repository;
    }

    public ActionResult SelectPerson(int selectedID)
    {
        string selectedIDAsString = selectedID.ToString();
        var selectListItems = Repository.AllAsSelectListItems(
            m => m.ID,
            m => m.Name,
            selectedIDAsString
        );
        return View(selectListItems);
    }
}

存储库

public class PersonRepository : Repository
{
     // various specialised methods 
}

public class Repository<T> : IRepository<T> where T : DBEntity
{
    private ApplicationDbContext db = null;
    private DbSet<T> table = null;

    public RepositoryBase()
    {
        this.db = new ApplicationDbContext();
        table = db.Set<T>();
    }
    public RepositoryBase(ApplicationDbContext db)
    {
        this.db = db;
        table = db.Set<T>();
    }

    protected virtual IQueryable<T> AllAsQueryable(
        params Expression<Func<T, object>>[] includeExpressions)
    {
        return includeExpressions.Aggregate<Expression<Func<T, object>>, IQueryable<T>>
            (table, (current, expression) => current.Include(expression));
    }

    public List<SelectListItem> AllAsSelectListItems(
        Expression<Func<T, string>> valueProperty, 
        Expression<Func<T, string>> textProperty, 
        string selectedValue = "")
    {
        // temp hard coded values until we learn how to use the expression parameters properly
        string valuePropertyHardCoded = "Name";
        string textPropertyHardCoded = "Name";
        Type currentType = typeof(T);
        var itemParam = Expression.Parameter(currentType, "x");
        var valueMember = Expression.PropertyOrField(itemParam, valuePropertyHardCoded);
        var textMember = Expression.PropertyOrField(itemParam, textPropertyHardCoded);

        var selector = Expression.MemberInit(Expression.New(typeof(SelectListItem)),
            Expression.Bind(typeof(SelectListItem).GetMember("Value").Single(), valueMember),
            Expression.Bind(typeof(SelectListItem).GetMember("Text").Single(), textMember)
        );
        var lambda = Expression.Lambda<Func<T, SelectListItem>>(
            selector, itemParam);

        return AllAsQueryable().Select(lambda.Compile()).ToList();
    }
}

【问题讨论】:

  • 我有点迷茫,AllAsSelectListItemsSpecifyProperties你是怎么写的AllAsSelectListItems?还是其他一些可行的方法?
  • 抱歉,这是复制粘贴时的拼写错误。感谢您指出。应该是AllAsSelectListItems()。现已更新。
  • string selectedValue = "" 参数的目的是什么?
  • 与每个SelectListItem.Value 进行比较。如果它们相同,我们将SelectListItem.Selected 标记为true

标签: c# linq lambda linq-to-entities expression-trees


【解决方案1】:

你快到了。有几件事需要实现:

(A) 将传递的valuePropertytextProperty 表达式绑定到一个公共参数。由于假设它们代表一个属性/字段访问器,因此传递的表达式Body 应该是MemberExpression 类型,并且可以从MemberExpression.Member 属性中提取实际的成员信息。

(B) 使用Expression.Equal 生成Selected 赋值

把它们放在一起,它看起来像这样

public List<SelectListItem> AllAsSelectListItems(
        Expression<Func<T, string>> valueProperty,
        Expression<Func<T, string>> textProperty,
        string selectedValue = "")
{
    if (valueProperty == null) throw new ArgumentNullException("valueProperty");
    if (textProperty == null) throw new ArgumentNullException("textProperty");
    if (!(valueProperty.Body is MemberExpression)) throw new ArgumentException("Must be a field or property.", "valueProperty");
    if (!(textProperty.Body is MemberExpression)) throw new ArgumentException("Must be a field or property.", "textProperty");
    var item = Expression.Parameter(typeof(T), "x");
    var valueMember = Expression.MakeMemberAccess(item, ((MemberExpression)valueProperty.Body).Member);
    var textMember = Expression.MakeMemberAccess(item, ((MemberExpression)textProperty.Body).Member);
    var targetType = typeof(SelectListItem);
    var bindings = new List<MemberBinding>
    {
        Expression.Bind(targetType.GetProperty("Value"), valueMember),
        Expression.Bind(targetType.GetProperty("Text"), textMember)
    };
    if (!string.IsNullOrEmpty(selectedValue))
        bindings.Add(Expression.Bind(targetType.GetProperty("Selected"), Expression.Equal(valueMember, Expression.Constant(selectedValue))));
    var selector = Expression.Lambda<Func<T, SelectListItem>>(
        Expression.MemberInit(Expression.New(targetType), bindings), item);
    var query = AllAsQueryable().Select(selector);
    var result = query.ToList();
    return result;
}

更新:不幸的是,SelectListItem.Value 的类型是 string,而且大多数时候源属性(通常是某种 Id)不是 string。所以让valueProperty 重命名为valueSelector 并允许传递类似x =&gt; x.Id.ToString() 的东西。虽然我们不能轻松地重新绑定传递的表达式,但我们可以轻松地使用它而不用创建新参数,只需重用该表达式的参数即可。

现在修改后的方法是这样的

public List<SelectListItem> AllAsSelectListItems(
        Expression<Func<T, string>> valueSelector,
        Expression<Func<T, string>> textProperty,
        string selectedValue = "")
{
    if (valueSelector == null) throw new ArgumentNullException("valueSelector");
    if (textProperty == null) throw new ArgumentNullException("textProperty");
    if (!(textProperty.Body is MemberExpression)) throw new ArgumentException("Must be a field or property.", "textProperty");
    var item = valueSelector.Parameters[0];
    var itemValue = valueSelector.Body;
    var itemText = Expression.MakeMemberAccess(item, ((MemberExpression)textProperty.Body).Member);
    var targetType = typeof(SelectListItem);
    var bindings = new List<MemberBinding>
    {
        Expression.Bind(targetType.GetProperty("Value"), itemValue),
        Expression.Bind(targetType.GetProperty("Text"), itemText)
    };
    if (!string.IsNullOrEmpty(selectedValue))
        bindings.Add(Expression.Bind(targetType.GetProperty("Selected"), Expression.Equal(itemValue, Expression.Constant(selectedValue))));
    var selector = Expression.Lambda<Func<T, SelectListItem>>(Expression.MemberInit(Expression.New(targetType), bindings), item);
    var query = AllAsQueryable().Select(selector);
    var result = query.ToList();
    return result;
}

【讨论】:

  • 非常感谢!!现在我明白了,而且有了你的解释,这是有道理的,但我自己永远也想不出这个
  • 如果我想将int 属性作为valueProperty 参数传递,我该怎么做呢?我尝试使用Expression&lt;Func&lt;T, int&gt;&gt;Expression&lt;Func&lt;T, object&gt;&gt; 创建重载,但我不明白如何编辑绑定部分。
  • @MartinHansenLennox 让我想一想。问题是它需要转换为string 我猜?
  • 我想是的。我正在尝试各种方法,但我不确定我应该尝试在代码中的哪个位置进行转换。
  • 如果这很痛苦,我可以作为一个单独的问题提出。我很高兴现在可以让它在字符串上工作:)
猜你喜欢
  • 1970-01-01
  • 2010-11-18
  • 2012-11-15
  • 1970-01-01
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 2010-10-19
相关资源
最近更新 更多