diff --git a/.editorconfig b/.editorconfig index efe5c4a..e8730b4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -75,4 +75,7 @@ csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true \ No newline at end of file +csharp_new_line_before_members_in_anonymous_types = true + +# CS1570: XML עͳ XML ʽ +dotnet_diagnostic.CS1570.severity = none diff --git a/src/UnitOfWork/Collections/EnumerableExtensionMethod.cs b/src/UnitOfWork/Collections/EnumerableExtensionMethod.cs new file mode 100644 index 0000000..9dfd2c5 --- /dev/null +++ b/src/UnitOfWork/Collections/EnumerableExtensionMethod.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Arch.EntityFrameworkCore.UnitOfWork.Collections +{ + /// + /// Class EnumerableExtensionMethod. + /// + public static class EnumerableExtensionMethod + { + /// + /// 包裹泛型IEnumerable实例, 避免可能的数组实例造成EF动态过滤失败 + /// + /// + /// The source. + /// IEnumerable<T>. + public static IEnumerable WrapEnumerable(this IEnumerable source) => new EnumerableWrapper(source); + + /// + /// Class EnumerableWrapper. + /// Implements the + /// + /// + /// + private class EnumerableWrapper : IEnumerable + { + /// + /// The underlying instance + /// + private readonly IEnumerable _underlyingInstance; + + /// + /// Initializes a new instance of the class. + /// + /// The underlying instance. + public EnumerableWrapper(IEnumerable underlyingInstance) => _underlyingInstance = underlyingInstance; + + /// + /// 返回一个循环访问集合的枚举器。 + /// + /// 可用于循环访问集合的 + IEnumerator IEnumerable.GetEnumerator() => _underlyingInstance.GetEnumerator(); + + /// + /// Gets the enumerator. + /// + /// IEnumerator. + IEnumerator IEnumerable.GetEnumerator() => _underlyingInstance.GetEnumerator(); + } + } +} diff --git a/src/UnitOfWork/Internals/PredicateConcater.cs b/src/UnitOfWork/Internals/PredicateConcater.cs new file mode 100644 index 0000000..f2bac43 --- /dev/null +++ b/src/UnitOfWork/Internals/PredicateConcater.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq.Expressions; + +namespace Arch.EntityFrameworkCore.UnitOfWork.Internals +{ + /// + /// Class PredicateConcater. + /// + internal static class PredicateConcater + { + /// + /// Ors the specified expr2. + /// + /// + /// The expr1. + /// The expr2. + /// Expression<Func<T, System.Boolean>>. + public static Expression> Or(this Expression> expr1, Expression> expr2) + { + var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]); + return Expression.Lambda>(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters); + } + + /// + /// Ands the specified expr2. + /// + /// + /// The expr1. + /// The expr2. + /// Expression<Func<T, System.Boolean>>. + public static Expression> And(this Expression> expr1, Expression> expr2) + { + var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]); + return Expression.Lambda>(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters); + } + + /// + /// Replaces the specified search ex. + /// + /// The expression. + /// The search ex. + /// The replace ex. + /// Expression. + public static Expression Replace(this Expression expression, Expression searchEx, Expression replaceEx) + { + return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); + } + + /// + /// Class ReplaceVisitor. + /// Implements the + /// + /// + private class ReplaceVisitor : ExpressionVisitor + { + /// + /// From + /// + private readonly Expression _from, _to; + + /// + /// Initializes a new instance of the class. + /// + /// From. + /// To. + public ReplaceVisitor(Expression from, Expression to) + { + _from = from; + _to = to; + } + + /// + /// Visits the specified node. + /// + /// The node. + /// Expression. + public override Expression Visit(Expression node) + { + return node == _from ? _to : base.Visit(node); + } + } + } +} \ No newline at end of file diff --git a/src/UnitOfWork/PredicateBuilder.cs b/src/UnitOfWork/PredicateBuilder.cs new file mode 100644 index 0000000..6779165 --- /dev/null +++ b/src/UnitOfWork/PredicateBuilder.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Arch.EntityFrameworkCore.UnitOfWork.Collections; + +namespace Arch.EntityFrameworkCore.UnitOfWork +{ + /// + /// Class PredicateBuilder. This class cannot be inherited. + /// + /// + public sealed class PredicateBuilder + { + /// + /// The instance + /// + public static readonly PredicateBuilder Instance = new PredicateBuilder(); + + /// + /// Prevents a default instance of the class from being created. + /// + private PredicateBuilder() + { + } + + /// + /// Customs the specified predicate. + /// + /// The predicate. + /// PredicateWrap<T>. + public PredicateWrap Custom(Expression> predicate) => predicate; + + /// + /// Ins the specified selector. + /// + /// The type of the tv. + /// The selector. + /// The check in. + /// PredicateWrap<T>. + public PredicateWrap In(Expression> selector, IEnumerable checkIn) + { + var c = Expression.Constant(checkIn.WrapEnumerable()); + var p = selector.Parameters[0]; + var call = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(TV) }, c, selector.Body); + var exp = Expression.Lambda>(call, p); + return exp; + } + + /// + /// Nots the in. + /// + /// The type of the tv. + /// The selector. + /// The check in. + /// PredicateWrap<T>. + public PredicateWrap NotIn(Expression> selector, IEnumerable checkIn) + { + var p = selector.Parameters[0]; + var values = Expression.Constant(checkIn.WrapEnumerable()); + var @in = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(TV) }, values, selector.Body); + var not = Expression.Not(@in); + var exp = Expression.Lambda>(not, p); + return exp; + } + + /// + /// Strings the contains. + /// + /// The selector. + /// The contains. + /// PredicateWrap<T>. + /// ILL SYSTEM LIB? string.Contains(string) NOT EXIST?? + public PredicateWrap StringContains(Expression> selector, string contains) + { + if (string.IsNullOrWhiteSpace(contains)) return null; + + var stringContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }) + ?? throw new SystemException("ILL SYSTEM LIB? string.Contains(string) NOT EXIST??"); + + var parameterExp = selector.Parameters[0]; + var valExp = Expression.Constant(contains, typeof(string)); + + var callExp = Expression.Call(selector.Body, stringContainsMethod, valExp); + var lambda = Expression.Lambda>(callExp, parameterExp); + + return lambda; + } + + /// + /// Betweens the specified selector. + /// + /// The type of the tv. + /// The selector. + /// The minimum. + /// The maximum. + /// if set to true [include]. + /// PredicateWrap<T>. + public PredicateWrap Between(Expression> selector, TV min, TV max, bool include = true) + { + if (null == min && null == max) return null; + + PredicateWrap predicateWrap = null; + + if (null != min) + { + predicateWrap = include + ? GreaterThanOrEqual(selector, min) + : GreaterThan(selector, min); + } + + if (null != max) + { + predicateWrap &= include + ? LessThanOrEqual(selector, max) + : LessThan(selector, max); + } + + return predicateWrap; + } + + /// + /// Equals the specified selector.(==) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap Equal(Expression> selector, TV valueToCompare) => BinOp(Expression.Equal, selector, valueToCompare); + + /// + /// Nots the equal.(!=) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap NotEqual(Expression> selector, TV valueToCompare) => BinOp(Expression.NotEqual, selector, valueToCompare); + + /// + /// Lesses the than.(<) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap LessThan(Expression> selector, TV valueToCompare) => BinOp(Expression.LessThan, selector, valueToCompare); + + /// + /// Lesses the than or equal.(<=) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap LessThanOrEqual(Expression> selector, TV valueToCompare) => BinOp(Expression.LessThanOrEqual, selector, valueToCompare); + + /// + /// Greaters the than.(>) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap GreaterThan(Expression> selector, TV valueToCompare) => BinOp(Expression.GreaterThan, selector, valueToCompare); + + /// + /// Greaters the than or equal.(>=) + /// + /// The type of the tv. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + public PredicateWrap GreaterThanOrEqual(Expression> selector, TV valueToCompare) => BinOp(Expression.GreaterThanOrEqual, selector, valueToCompare); + + /// + /// Bins the op. + /// + /// The type of the tv. + /// The op. + /// The selector. + /// The value to compare. + /// PredicateWrap<T>. + private static PredicateWrap BinOp(Func op, Expression> selector, TV valueToCompare) + { + var parameterExp = selector.Parameters[0]; + var valToCompareExp = Expression.Constant(valueToCompare, typeof(TV)); + + var callExp = op(selector.Body, valToCompareExp); + var lambda = Expression.Lambda>(callExp, parameterExp); + + return lambda; + } + } +} diff --git a/src/UnitOfWork/PredicateWrap.cs b/src/UnitOfWork/PredicateWrap.cs new file mode 100644 index 0000000..63bf78a --- /dev/null +++ b/src/UnitOfWork/PredicateWrap.cs @@ -0,0 +1,178 @@ +using System; +using System.Linq.Expressions; +using Arch.EntityFrameworkCore.UnitOfWork.Internals; + +namespace Arch.EntityFrameworkCore.UnitOfWork +{ + /// + /// Class PredicateWrap. + /// + public static class PredicateWrap + { + /// + /// Ops the specified exp. + /// + /// + /// The exp. + /// PredicateWrap<T>. + public static PredicateWrap Op(this Expression> exp) => new PredicateWrap(exp); + } + + /// + /// Class PredicateWrap. + /// + /// + public class PredicateWrap + { + /// + /// The expression + /// + private readonly Expression> _expression; + + /// + /// Initializes a new instance of the class. + /// + /// The expression. + internal PredicateWrap(Expression> expression) => _expression = expression; + + /// + /// Whens the specified condition. + /// + /// if set to true [condition]. + /// PredicateWrap<T>. + public PredicateWrap When(bool condition) => condition ? this : null; + + /// + /// Performs an implicit conversion from to Expression. + /// + /// Me. + /// The result of the conversion. + public static implicit operator Expression>(PredicateWrap me) + { + var expression = me?._expression; + if (true == expression?.CanReduce) return (Expression>)expression.Reduce(); + return expression; + } + + /// + /// Performs an implicit conversion from Expression to . + /// + /// Me. + /// The result of the conversion. + public static implicit operator PredicateWrap(Expression> me) => me == null ? null : new PredicateWrap(me); + + /// + /// Implements the & operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator &(PredicateWrap a, PredicateWrap b) + { + var aIsNull = null == a?._expression; + var bIsNull = null == b?._expression; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a._expression.And(b._expression)); + } + + /// + /// Implements the & operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator &(PredicateWrap a, Expression> b) + { + var aIsNull = null == a?._expression; + var bIsNull = null == b; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a._expression.And(b)); + } + + /// + /// Implements the & operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator &(Expression> a, PredicateWrap b) + { + var aIsNull = null == a; + var bIsNull = null == b?._expression; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a.And(b._expression)); + } + + /// + /// Implements the | operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator |(PredicateWrap a, PredicateWrap b) + { + var aIsNull = null == a?._expression; + var bIsNull = null == b?._expression; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a._expression.Or(b._expression)); + } + + /// + /// Implements the | operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator |(PredicateWrap a, Expression> b) + { + var aIsNull = null == a?._expression; + var bIsNull = null == b; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a._expression.Or(b)); + } + + /// + /// Implements the | operator. + /// + /// a. + /// The b. + /// The result of the operator. + public static PredicateWrap operator |(Expression> a, PredicateWrap b) + { + var aIsNull = null == a; + var bIsNull = null == b?._expression; + + if (aIsNull && bIsNull) return null; + + if (aIsNull) return b; + if (bIsNull) return a; + + return new PredicateWrap(a.Or(b._expression)); + } + } +} diff --git a/test/UnitOfWork.Tests/TestPredicate.cs b/test/UnitOfWork.Tests/TestPredicate.cs new file mode 100644 index 0000000..9f8d523 --- /dev/null +++ b/test/UnitOfWork.Tests/TestPredicate.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Arch.EntityFrameworkCore.UnitOfWork.Tests.Entities; +using Xunit; + +namespace Arch.EntityFrameworkCore.UnitOfWork.Tests +{ + public class TestPredicate + { + private static readonly InMemoryContext db; + + static TestPredicate() + { + db = new InMemoryContext(); + if (db.Countries.Any() == false) + { + db.AddRange(TestCountries); + db.AddRange(TestCities); + db.AddRange(TestTowns); + db.SaveChanges(); + } + } + + + [Fact] + public async void TestGetFirstOrDefaultAsync() + { + var repository = new Repository(db); + var exp = PredicateWrap.Op(t => t.Name == "A"); + var city = await repository.GetFirstOrDefaultAsync(predicate: exp); + Assert.NotNull(city); + Assert.Equal(1, city.Id); + + var predicate = PredicateBuilder.Instance; + var exp1 = predicate.Custom(t => t.Id == 3); + var city1 = await repository.GetFirstOrDefaultAsync(predicate: exp1); + Assert.NotNull(city1); + Assert.Equal(3, city1.Id); + } + + [Fact] + public async void TestGetAll() + { + var repository = new Repository(db); + var predicate = PredicateBuilder.Instance; + var exp = predicate.Equal(t => t.Name,"A" ); + exp &= predicate.Equal(t => t.Id, 3); + var cityList = await repository.GetAllAsync(predicate: exp); + Assert.Equal(0,cityList.Count); + + + exp = predicate.Equal(t => t.Name, "A"); + exp |= predicate.Equal(t => t.Id, 3); + cityList = await repository.GetAllAsync(predicate: exp); + Assert.Equal(2,cityList.Count); + } + + + protected static List TestCountries => new List + { + new Country {Id = 1, Name = "A"}, + new Country {Id = 2, Name = "B"} + }; + + public static List TestCities => new List + { + new City { Id = 1, Name = "A", CountryId = 1}, + new City { Id = 2, Name = "B", CountryId = 2}, + new City { Id = 3, Name = "C", CountryId = 1}, + new City { Id = 4, Name = "D", CountryId = 2}, + new City { Id = 5, Name = "E", CountryId = 1}, + new City { Id = 6, Name = "F", CountryId = 2}, + }; + + public static List TestTowns => new List + { + new Town { Id = 1, Name="TownA", CityId = 1 }, + new Town { Id = 2, Name="TownB", CityId = 2 }, + new Town { Id = 3, Name="TownC", CityId = 3 }, + new Town { Id = 4, Name="TownD", CityId = 4 }, + new Town { Id = 5, Name="TownE", CityId = 5 }, + new Town { Id = 6, Name="TownF", CityId = 6 }, + }; + } +}