Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/sharepoint/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 表达式-如何重用业务逻辑?如何组合它们?_C#_Linq_Entity Framework_Lambda_Expression - Fatal编程技术网

C# 表达式-如何重用业务逻辑?如何组合它们?

C# 表达式-如何重用业务逻辑?如何组合它们?,c#,linq,entity-framework,lambda,expression,C#,Linq,Entity Framework,Lambda,Expression,注意:这是一篇很长的文章,请滚动到底部查看问题-希望这将使我更容易理解我的问题。谢谢 我有一个成员模型,其定义如下: public class Member { public string FirstName { get; set; } public string LastName { get; set; } public string ScreenName { get; set; } [NotMapped] public string RealName

注意:这是一篇很长的文章,请滚动到底部查看问题-希望这将使我更容易理解我的问题。谢谢

我有一个成员模型,其定义如下:

public class Member
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ScreenName { get; set; }

    [NotMapped]
    public string RealName
    {
         get { return (FirstName + " " + LastName).TrimEnd(); }
    }

    [NotMapped]
    public string DisplayName
    {
        get
        {
            return string.IsNullOrEmpty(ScreenName) ? RealName : ScreenName;
        }
    }
}
这是现有的项目和模型,我不想改变它。现在,我们收到一个请求,要求按DisplayName启用配置文件检索:

此代码无效,因为DisplayName未映射到数据库中的字段。好吧,那我就做个表情:

public Member GetMemberByDisplayName(string displayName)
{
     Expression<Func<Member, bool>> displayNameSearchExpr = m => (
                string.IsNullOrEmpty(m.ScreenName) 
                    ? (m.Name + " " + m.LastName).TrimEnd() 
                    : m.ScreenName
            ) == displayName;

     var member = this.memberRepository
                      .FirstOrDefault(displayNameSearchExpr);

     return member;
}
这很有效。唯一的问题是,生成显示名称的业务逻辑被复制/粘贴到两个不同的位置。我想避免这种情况。但我不明白如何做到这一点。我最好的选择是:

  public class Member
    {

        public static Expression<Func<Member, string>> GetDisplayNameExpression()
        {
            return m => (
                            string.IsNullOrEmpty(m.ScreenName)
                                ? (m.Name + " " + m.LastName).TrimEnd()
                                : m.ScreenName
                        );
        }

        public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
        {
            return m => (
                string.IsNullOrEmpty(m.ScreenName)
                    ? (m.Name + " " + m.LastName).TrimEnd()
                    : m.ScreenName
            ) == displayName;
        }

        private static readonly Func<Member, string> GetDisplayNameExpressionCompiled = GetDisplayNameExpression().Compile();

        [NotMapped]
        public string DisplayName
        {
            get
            {
                return GetDisplayNameExpressionCompiled(this);
            }
        }

        [NotMapped]
        public string RealName
        {
             get { return (FirstName + " " + LastName).TrimEnd(); }
        }

   }
问题: 1如何在FilterMemberByDisplayNameExpression内重用GetDisplayNameExpression?我尝试了表达式。调用:

但是我从提供者那里得到了以下错误:

LINQ to中不支持LINQ表达式节点类型“Invoke” 实体

2在DisplayName属性中使用Expression.Compile是否是一种好方法?有什么问题吗

3如何在GetDisplayNameExpression中移动RealName逻辑?我想我必须创建另一个表达式和另一个编译表达式,但我不明白如何从GetDisplayNameExpression内部调用RealNameExpression


谢谢。

我可以修复您的表达式生成器,并可以编写GetDisplayNameExpression so 1和3

它是如何工作的?魔法,倒影,仙尘。。。 它是否支持引用其他属性的属性?对 它需要什么? 它需要每个名为Foo的特殊属性都有一个对应的名为FooExpression的静态字段/静态属性,该属性返回一个表达式

在具体化/枚举之前的某个点,需要通过扩展方法Expand对查询进行转换。因此:

public class Member
{
    // can be private/protected/internal
    public static readonly Expression<Func<Member, string>> RealNameExpression =
        m => (m.Name + " " + m.LastName).TrimEnd();

    // Here we are referencing another "special" property, and it just works!
    public static readonly Expression<Func<Member, string>> DisplayNameExpression =
        m => string.IsNullOrEmpty(m.ScreenName) ? m.RealName : m.ScreenName;

    public string RealName
    {
        get 
        { 
            // return the real name however you want, probably reusing
            // the expression through a compiled readonly 
            // RealNameExpressionCompiled as you had done
        }  
    }

    public string DisplayName
    {
        get
        {
        }
    }
}

// Note the use of .Expand();
var res = (from p in ctx.Member 
          where p.RealName == "Something" || p.RealName.Contains("Anything") ||
                p.DisplayName == "Foo"
          select new { p.RealName, p.DisplayName, p.Name }).Expand();

// now you can use res normally.
限制1:一个问题是SingleExpression、FirstExpression、AnyExpression和类似的方法不返回IQueryable。首先使用WhereExpression.Expand.Single进行更改

限制2:特殊属性不能在循环中引用自身。所以如果A使用B,B就不能使用A,而像使用三元表达式这样的技巧也不能使它起作用


我可以修复你的表达式生成器,我可以编写你的GetDisplayNameExpression 1和3

它是如何工作的?魔法,倒影,仙尘。。。 它是否支持引用其他属性的属性?对 它需要什么? 它需要每个名为Foo的特殊属性都有一个对应的名为FooExpression的静态字段/静态属性,该属性返回一个表达式

在具体化/枚举之前的某个点,需要通过扩展方法Expand对查询进行转换。因此:

public class Member
{
    // can be private/protected/internal
    public static readonly Expression<Func<Member, string>> RealNameExpression =
        m => (m.Name + " " + m.LastName).TrimEnd();

    // Here we are referencing another "special" property, and it just works!
    public static readonly Expression<Func<Member, string>> DisplayNameExpression =
        m => string.IsNullOrEmpty(m.ScreenName) ? m.RealName : m.ScreenName;

    public string RealName
    {
        get 
        { 
            // return the real name however you want, probably reusing
            // the expression through a compiled readonly 
            // RealNameExpressionCompiled as you had done
        }  
    }

    public string DisplayName
    {
        get
        {
        }
    }
}

// Note the use of .Expand();
var res = (from p in ctx.Member 
          where p.RealName == "Something" || p.RealName.Contains("Anything") ||
                p.DisplayName == "Foo"
          select new { p.RealName, p.DisplayName, p.Name }).Expand();

// now you can use res normally.
限制1:一个问题是SingleExpression、FirstExpression、AnyExpression和类似的方法不返回IQueryable。首先使用WhereExpression.Expand.Single进行更改

限制2:特殊属性不能在循环中引用自身。所以如果A使用B,B就不能使用A,而像使用三元表达式这样的技巧也不能使它起作用


最近,我面临着在表达式中保留一些业务逻辑的需要,这些表达式允许在SQL查询和.net代码中使用它。我已经将一些有助于此的代码移到了。我已经实现了组合和重用表达式的简单方法。参见我的示例:

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

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public int Age { get; set; }

    public Company Company { get; set; }

    public static Expression<Func<Person, string>> FirstNameExpression
    {
        get { return x => x.FirstName; }
    }

    public static Expression<Func<Person, string>> LastNameExpression
    {
        get { return x => x.LastName; }
    }

    public static Expression<Func<Person, string>> FullNameExpression
    {
        //get { return FirstNameExpression.Plus(" ").Plus(LastNameExpression); }
        // or
        get { return x => FirstNameExpression.Wrap(x) + " " + LastNameExpression.Wrap(x); }
    }

    public static Expression<Func<Person, string>> SearchFieldExpression
    {
        get
        {
            return
                p => string.IsNullOrEmpty(FirstNameExpression.Wrap(p)) ? LastNameExpression.Wrap(p) : FullNameExpression.Wrap(p);
        }
    }

    public static Expression<Func<Person, bool>> GetFilterExpression(string q)
    {
        return p => SearchFieldExpression.Wrap(p) == q;
    }
}
控制台输出:

原件:p=>Person.SearchFieldExpression.Wrapp== valueQueryMapper.Examples.Person+c__DisplayClass0.q

取消包装:p=>IIFIsNullOrEmptyp.FirstName,p.LastName, p、 FirstName++p.LastName== valueQueryMapper.Examples.Person+c__DisplayClass0.q

SQL查询1:选择[Extent1].[Id]作为[Id],[Extent1].[FirstName]作为[Id] [FirstName],[Extent1]。[LastName]作为[LastName],[Extent1]。[Age]作为 [年龄],[扩展名1]。[公司Id]作为[公司Id]从[dbo]。[人员]作为 [Extent1]其中[Extent1].[FirstName]为NULL或 CASTLEN[Extent1].[Firs tName]为int=0,则 [Extent1].[LastName]其他[Extent1].[FirstName]+N''+ [Extent1][LastName]END=@p_linq_0

SQL查询2:选择[Extent1].[Id]作为[Id],[Extent1].[Name]作为[Name] 从[dbo].[companys]作为[Extent1]选择存在的位置 1 AS[C1] 来自[dbo].[People]作为[Extent2] 其中[Extent1].[Id]=[Extent2].[Company_Id]和[Extent2].[FirstName]为NULL或 CASTLEN[Extent2].[FirstName]作为int=0第n个 [Extent2].[LastName]其他[Extent2].[FirstName]+N''+ [Extent2].[LastName]END=@p_linq_0

因此,主要思想是使用.Wrap方法将表达式世界转换为非表达式世界,这为重用表达式提供了方便的方法


如果您需要更多解释,请告诉我。

最近,我需要在表达式中保留一些业务逻辑,以便在SQL查询和.net代码中使用。我已经移动了一些代码来帮助这一点 到我已经实现了组合和重用表达式的简单方法。参见我的示例:

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

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public int Age { get; set; }

    public Company Company { get; set; }

    public static Expression<Func<Person, string>> FirstNameExpression
    {
        get { return x => x.FirstName; }
    }

    public static Expression<Func<Person, string>> LastNameExpression
    {
        get { return x => x.LastName; }
    }

    public static Expression<Func<Person, string>> FullNameExpression
    {
        //get { return FirstNameExpression.Plus(" ").Plus(LastNameExpression); }
        // or
        get { return x => FirstNameExpression.Wrap(x) + " " + LastNameExpression.Wrap(x); }
    }

    public static Expression<Func<Person, string>> SearchFieldExpression
    {
        get
        {
            return
                p => string.IsNullOrEmpty(FirstNameExpression.Wrap(p)) ? LastNameExpression.Wrap(p) : FullNameExpression.Wrap(p);
        }
    }

    public static Expression<Func<Person, bool>> GetFilterExpression(string q)
    {
        return p => SearchFieldExpression.Wrap(p) == q;
    }
}
控制台输出:

原件:p=>Person.SearchFieldExpression.Wrapp== valueQueryMapper.Examples.Person+c__DisplayClass0.q

取消包装:p=>IIFIsNullOrEmptyp.FirstName,p.LastName, p、 FirstName++p.LastName== valueQueryMapper.Examples.Person+c__DisplayClass0.q

SQL查询1:选择[Extent1].[Id]作为[Id],[Extent1].[FirstName]作为[Id] [FirstName],[Extent1]。[LastName]作为[LastName],[Extent1]。[Age]作为 [年龄],[扩展名1]。[公司Id]作为[公司Id]从[dbo]。[人员]作为 [Extent1]其中[Extent1].[FirstName]为NULL或 CASTLEN[Extent1].[Firs tName]为int=0,则 [Extent1].[LastName]其他[Extent1].[FirstName]+N''+ [Extent1][LastName]END=@p_linq_0

SQL查询2:选择[Extent1].[Id]作为[Id],[Extent1].[Name]作为[Name] 从[dbo].[companys]作为[Extent1]选择存在的位置 1 AS[C1] 来自[dbo].[People]作为[Extent2] 其中[Extent1].[Id]=[Extent2].[Company_Id]和[Extent2].[FirstName]为NULL或 CASTLEN[Extent2].[FirstName]作为int=0第n个 [Extent2].[LastName]其他[Extent2].[FirstName]+N''+ [Extent2].[LastName]END=@p_linq_0

因此,主要思想是使用.Wrap方法将表达式世界转换为非表达式世界,这为重用表达式提供了方便的方法


如果你需要更多的解释,请告诉我。

A没有好的解决方案。B你所做的是一场噩梦,不是因为你不知道你在做什么,而是因为FilterMemberByDisplayNameExpression是一场等待你去梦想的噩梦。C没有好的解决办法。@xanatos为什么B是一场噩梦?你能解释一下最大的问题是什么吗?如果你真的想做点什么,你可以创建一个这样的过滤器:静态IQueryable MakeMeyExpressionWorkthis IQueryable exp.这个神奇的过滤器,通过表达式重写,反射,mumbo jumbo etc将修改表达式。@xanatos您所说的对A和C的情况是正确的-我知道我需要以某种方式修改表达式,这是我的问题。但我对B感到困惑-编译表达式并在属性中使用它有什么不对,只是为了避免业务逻辑复制/粘贴?如果您使用Sql Server作为db,您介意使用计算列吗?这将使这类事情变得更容易我们不久前还面临着同样的问题!A没有好的解决办法。B你所做的是一场噩梦,不是因为你不知道你在做什么,而是因为FilterMemberByDisplayNameExpression是一场等待你去梦想的噩梦。C没有好的解决办法。@xanatos为什么B是一场噩梦?你能解释一下最大的问题是什么吗?如果你真的想做点什么,你可以创建一个这样的过滤器:静态IQueryable MakeMeyExpressionWorkthis IQueryable exp.这个神奇的过滤器,通过表达式重写,反射,mumbo jumbo etc将修改表达式。@xanatos您所说的对A和C的情况是正确的-我知道我需要以某种方式修改表达式,这是我的问题。但我对B感到困惑-编译表达式并在属性中使用它有什么不对,只是为了避免业务逻辑复制/粘贴?如果您使用Sql Server作为db,您介意使用计算列吗?这将使这类事情变得更容易我们不久前还面临着同样的问题!
public class Member
{
    // can be private/protected/internal
    public static readonly Expression<Func<Member, string>> RealNameExpression =
        m => (m.Name + " " + m.LastName).TrimEnd();

    // Here we are referencing another "special" property, and it just works!
    public static readonly Expression<Func<Member, string>> DisplayNameExpression =
        m => string.IsNullOrEmpty(m.ScreenName) ? m.RealName : m.ScreenName;

    public string RealName
    {
        get 
        { 
            // return the real name however you want, probably reusing
            // the expression through a compiled readonly 
            // RealNameExpressionCompiled as you had done
        }  
    }

    public string DisplayName
    {
        get
        {
        }
    }
}

// Note the use of .Expand();
var res = (from p in ctx.Member 
          where p.RealName == "Something" || p.RealName.Contains("Anything") ||
                p.DisplayName == "Foo"
          select new { p.RealName, p.DisplayName, p.Name }).Expand();

// now you can use res normally.
public class Person
{
    public int Id { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public int Age { get; set; }

    public Company Company { get; set; }

    public static Expression<Func<Person, string>> FirstNameExpression
    {
        get { return x => x.FirstName; }
    }

    public static Expression<Func<Person, string>> LastNameExpression
    {
        get { return x => x.LastName; }
    }

    public static Expression<Func<Person, string>> FullNameExpression
    {
        //get { return FirstNameExpression.Plus(" ").Plus(LastNameExpression); }
        // or
        get { return x => FirstNameExpression.Wrap(x) + " " + LastNameExpression.Wrap(x); }
    }

    public static Expression<Func<Person, string>> SearchFieldExpression
    {
        get
        {
            return
                p => string.IsNullOrEmpty(FirstNameExpression.Wrap(p)) ? LastNameExpression.Wrap(p) : FullNameExpression.Wrap(p);
        }
    }

    public static Expression<Func<Person, bool>> GetFilterExpression(string q)
    {
        return p => SearchFieldExpression.Wrap(p) == q;
    }
}
public static TDest Wrap<TSource, TDest>(this Expression<Func<TSource, TDest>> expr, TSource val)
{
    throw new NotImplementedException("Used only as expression transform marker");
}
using (var context = new Entities())
{
    var originalExpr = Person.GetFilterExpression("ivan");
    Console.WriteLine("Original: " + originalExpr);
    Console.WriteLine();

    var expr = Person.GetFilterExpression("ivan").Unwrap();
    Console.WriteLine("Unwrapped: " + expr);
    Console.WriteLine();

    var persons = context.Persons.Where(Person.GetFilterExpression("ivan").Unwrap());
    Console.WriteLine("SQL Query 1: " + persons);
    Console.WriteLine();

    var companies = context.Companies.Where(x => x.Persons.Any(Person.GetFilterExpression("abc").Wrap())).Unwrap(); // here we use .Wrap method without parameters, because .Persons is the ICollection (not IQueryable) and we can't pass Expression<Func<T, bool>> as Func<T, bool>, so we need it for successful compilation. Unwrap method expand Wrap method usage and convert Expression to lambda function.
    Console.WriteLine("SQL Query 2: " + companies);
    Console.WriteLine();

    var traceSql = persons.ToString();
}