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();
}