在构建Linq2Sql查询时如何将对象的属性(而不是对象)传递给表达式?

时间:2014-01-05 22:33:29

标签: c# .net linq linq-to-sql expression

tl; dr :我正在寻找一种在构建Linq2Sql查询时将对象的属性image.Member(而不是对象image)传递到表达式的方法。这样我就会Expression<Func<Member, bool>>而不是Expression<Func<Image, bool>>


这是我的域名模型(EF 5,如果重要的话):

class Image
{
    public string Url { get; set;}
    public Member Member { get; set;}   // user who uploaded the image
}

class Member
{
    public int Id { get; set;}

    public bool IsPrivate { get; set;}
    public string AboutMe { get; set;}  
    public string ScreenName { get; set;}   

    [NotMapped]
    public bool IsSearchable
    {
        get
        {
            return IsPrivate == false && 
                   string.IsNullOrEmpty(AboutMe) == false &&
                   string.IsNullOrEmpty(ScreenName) == false;
        }
    }
}

接下来,我希望使用以下逻辑从数据库中获取所有图像:

  

如果用户已登录,请拍摄all his images以及所有其他图像   成员IsSearchable。如果用户未登录,请执行   所有其他IsSearchable成员的所有图像。

在Linq2Objects中,这将是这样的:

var images = imagesRepository.All().ToList().AsIEnumerable();

if (this.User == null)
{
    images = images.Where(x => x.Member.IsSearchable == true);
}
else
{
    var currentMemberId = this.User.Id;
    images = images.Where(x => x.Member.IsSearchable == true || x.Member.Id == currentMemberId);
}

当然这是低效的 - 我不想从DB加载10000个图像只是为了得到10个。这导致我 Linq2Sql 注意:我知道我可以将'IsSearchable'中的逻辑复制到'Where'子句中,我会得到正确的'IQueryable'但我有2个问题:

  1. IsSearchable逻辑很容易在以后更改 - 我永远不会记得这个复制/粘贴
  2. 我必须将它复制TWICE(因为currentMemberId处于OR状态) - 这将使代码难以阅读。
  3. 这让我相信我唯一的解决方案是使用Expression。我们走了(感谢this Expression.Or question):

    var parameterExpression = Expression.Parameter(typeof(Image));
    
    Expression<Func<Image, bool>> memberIsSearchable = 
        i => i.Member.IsPrivate == false && 
             string.IsNullOrEmpty(i.Member.AboutMe) == false && 
             string.IsNullOrEmpty(i.Member.ScreenName) == false;  
    
    Expression<Func<Image, bool>> memberById = 
        i => i.Member.MemberId == currentMemberId;
    
    var combined = Expression.Lambda<Func<Image, bool>>(
        Expression.Or(
             Expression.Invoke(memberIsSearchable, parameterExpression),
             Expression.Invoke(memberById, parameterExpression)), 
        parameterExpression);
    
    var images = imagesRepository.All();
    
    if (this.User == null)
    {
        images = images.Where(memberIsSearchable);
    }
    else
    {
        images = images.Where(combined);
    }
    

    这很好地转换为SQL。

    以下是问题:

    是否可以在表达式定义中删除Image类型

    Expression<Func<Image, bool>> memberIsSearchable = ...
    

    并将其替换为Member并以某种方式告诉表达式在创建查询时使用'Image'对象中的'Member':

    Expression<Func<Member, bool>> memberIsSearchable = 
        m => m.IsPrivate == false && 
             string.IsNullOrEmpty(m.AboutMe) == false && 
             string.IsNullOrEmpty(m.ScreenName) == false;
    
    images = images.Where(x => memberIsSearchable(x.Member));  // pseudocode
    

    这样做的原因是使memberIsSearchable表达式通用并在项目的其他部分使用它。

1 个答案:

答案 0 :(得分:0)

我花了一段时间才弄明白你的问题究竟是什么 - 我想,我现在明白了。

尝试从成员开始(而不是在图像处)。也许你需要引入额外的导航属性:

Expression<Func<Member, bool>> IsSearchable =  member => !member.IsPrivate
        && !String.IsNullOrEmpty(member.AboutMe)
        && !String.IsNullOrEmpty(member.ScreenName);

Member currentUser = null;
using (var container = new /**/())
{
    var images = container.Members.Where(IsSearchable).SelectMany(member => member.Images);
    if (currentUser != null)
        images = images.Concat(currentUser.Images);

    var result = images.ToArray();
}