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 },
+ };
+ }
+}