[英]Custom Sorting using c# Linq Expressions
我正在嘗試使C#Application和C#Web API更容易排序。 我正在使用Entity Framework Core進行持久性和測試。
在我的應用程序或Web API中,我確定了Order,Descending或Ascending,Property Name。
我將這些知識傳遞到我的存儲庫中,其中創建並執行了Linq查詢。 問題是,當我有一個十進制列時,它執行字符串順序而不是十進制順序。
public static class SortingExtensions
{
public static IQueryable<T> SortBy<T>(
this IQueryable<T> queryable,
Sorting sorting)
{
IOrderedQueryable<T> orderedQueryable = null;
sorting.SortableEntities
.OrderBy(x => x.Order)
.ToList()
.ForEach(sortableEntity =>
{
Expression<Func<T, object>> expression = QueryHelper.GetDefaultSortExpression<T>(sortableEntity);
if (expression != null)
{
orderedQueryable = orderedQueryable == null
? queryable.OrderBy(expression, sortableEntity.Descending)
: orderedQueryable.OrderBy(expression, sortableEntity.Descending);
}
});
return orderedQueryable;
}
private static IOrderedQueryable<T> OrderBy<T, TKey>(
this IOrderedQueryable<T> query,
Expression<Func<T, TKey>> keySelector,
bool descending) => descending ? query.ThenByDescending(keySelector) : query.ThenBy(keySelector);
private static IOrderedQueryable<T> OrderBy<T, TKey>(
this IQueryable<T> query,
Expression<Func<T, TKey>> keySelector,
bool descending) => descending ? query.OrderByDescending(keySelector) : query.OrderBy(keySelector);
}
public static class QueryHelper
{
public static Expression<Func<T, object>> GetDefaultSortExpression<T>(SortableEntity sortableEntity)
{
Type entityType = typeof(T);
ParameterExpression arg = Expression.Parameter(entityType, "x");
string[] fieldNames = sortableEntity.Name.Split('.');
MemberExpression memberExpression = null;
foreach (string name in fieldNames)
{
Expression expressionToUse = memberExpression ?? (Expression) arg;
memberExpression = Expression.Property(expressionToUse, name.ToProperCase());
}
Expression propertyExpression = Expression.Convert(memberExpression, typeof(object));
Expression<Func<T, object>>
complexExpression = Expression.Lambda<Func<T, object>>(propertyExpression, arg);
return complexExpression;
}
}
public class SortableEntity
{
public int Order { get; set; }
public bool Descending { get; set; }
public string Name { get; set; }
}
public class Sorting
{
IEnumerable<SortableEntity> SortableEntities { get; }
}
public class TestDecimalPropertyClass : Entity
{
public TestDecimalPropertyClass(decimal @decimal) => Decimal = @decimal;
protected TestDecimalPropertyClass()
{
}
public decimal Decimal { get; set; }
}
public class TestDecimalPropertyClassRepository
{
private readonly DbContext _dbContext;
public TestDecimalPropertyClassRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IEnumerable<TestDecimalPropertyClass>> GetAllAsync(Sorting sorting)
{
List<TestDecimalPropertyClass> entities = await _dbContext.Set<TestDecimalPropertyClass>()
.SortBy(sorting)
.ToListAsync();
return entities;
}
public async Task SaveAsync(TestDecimalPropertyClass testDecimalPropertyClass)
{
_dbContext.Set<TestDecimalPropertyClass>().Add(testDecimalPropertyClass);
await _dbContext.SaveChangesAsync();
}
}
這是我為它寫的測試:
[TestFixture]
public class GenericSortingTests
{
private SqliteConnection SqliteConnection { get; set; }
[SetUp]
public void DbSetup()
{
SqliteConnectionStringBuilder sqliteConnectionStringBuilder = new SqliteConnectionStringBuilder
{
Mode = SqliteOpenMode.Memory,
Cache = SqliteCacheMode.Private
};
SqliteConnection = new SqliteConnection(sqliteConnectionStringBuilder.ToString());
SqliteConnection.Open();
}
[TearDown]
public void DbTearDown()
{
SqliteConnection.Close();
}
[Test]
public async Task GivenADecimalProperty_WhenISortByColumn_ThenItSorts()
{
decimal[] decimals = new[] {7m, 84.3m, 13.4m};
using (DbContext dbContext = GetDbContext())
{
TestDecimalPropertyClassRepository testRepository = new TestDecimalPropertyClassRepository(dbContext);
foreach (decimal @decimal in decimals)
{
TestDecimalPropertyClass entity = new TestDecimalPropertyClass(@decimal);
await testRepository.SaveAsync(entity);
}
}
IEnumerable<TestDecimalPropertyClass> entities;
using (DbContext dbContext = GetDbContext())
{
TestDecimalPropertyClassRepository testRepository = new TestDecimalPropertyClassRepository(dbContext);
entities = await testRepository.GetAllAsync(new Sorting
{
SortableEntities = new[]
{
new SortableEntity
{
Descending = false,
Name = "decimal",
Order = 0
}
}
});
}
List<TestDecimalPropertyClass> list = entities.ToList();
Assert.That(list.Count(), Is.EqualTo(decimals.Length));
Assert.That(list.ToArray()[0].Decimal, Is.EqualTo(7m));
Assert.That(list.ToArray()[1].Decimal, Is.EqualTo(13.4m));
Assert.That(list.ToArray()[2].Decimal, Is.EqualTo(84.3m));
}
private class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TestDecimalPropertyClass>();
base.OnModelCreating(modelBuilder);
}
}
private DbContext GetDbContext()
{
DbContextOptions<TestDbContext> options = new DbContextOptionsBuilder<TestDbContext>()
.UseSqlite(SqliteConnection)
.EnableSensitiveDataLogging()
.Options;
TestDbContext dbContext = new TestDbContext(options);
dbContext.Database.EnsureCreated();
return dbContext;
}
}
我希望它能按順序對物品進行分類:7米,13.4米,84.3米,但它將其分為13.4米,7米,84.3米
任何人都可以幫助我理解為什么它這樣做,所以我可以解決它?
謝謝,克里斯
首先,我以前曾試圖像這樣重新發明這種輪子,它永遠不會像你想的那樣真正起作用。 如果你需要那種動態的靈活性,那么可能已經有一個庫在某處, 或者你也可以實際手動制作SQL或其他東西(它很糟糕,但有時它是唯一實用的方法)..除此之外.. 。
我認為你的問題實際上與SQLite有關 - 由於拼寫錯誤或版本不相同,我無法讓SQLite的東西工作(例如,SQLite的默認nuget包有一個SQLiteConnectionStringBuilder
而不是一個SqliteConnectionStringBuilder
,這不是' t似乎與你的例子具有相同的屬性)所以我在某種程度上破解了你的代碼以刪除SQL的東西並擺脫異步的東西(因為我希望這真的不相關),所以我有這個存儲庫:
public class TestDecimalPropertyClassRepository
{
private readonly IList<TestDecimalPropertyClass> list;
public TestDecimalPropertyClassRepository(IEnumerable<TestDecimalPropertyClass> repo)
{
list = repo.ToList();
}
public IEnumerable<TestDecimalPropertyClass> GetAll(Sorting sorting)
{
List<TestDecimalPropertyClass> entities = list
.AsQueryable()
.SortBy(sorting)
.ToList();
return entities;
}
public void Save(TestDecimalPropertyClass testDecimalPropertyClass)
{
list.Add(testDecimalPropertyClass);
}
}
這使得測試看起來像這樣
[Test]
public void GivenADecimalProperty_WhenISortByColumn_ThenItSorts()
{
decimal[] decimals = new[] { 7m, 84.3m, 13.4m };
var repo = decimals.Select(x => new TestDecimalPropertyClass(x));
TestDecimalPropertyClassRepository testRepository = new TestDecimalPropertyClassRepository(repo);
var entities = testRepository.GetAll(new Sorting
{
SortableEntities = new[]
{
new SortableEntity
{
Descending = false,
Name = "decimal",
Order = 0
}
}
});
List<TestDecimalPropertyClass> list = entities.ToList();
Assert.That(list.Count(), Is.EqualTo(decimals.Length));
Assert.That(list.ToArray()[0].Decimal, Is.EqualTo(7m));
Assert.That(list.ToArray()[1].Decimal, Is.EqualTo(13.4m));
Assert.That(list.ToArray()[2].Decimal, Is.EqualTo(84.3m));
}
並且所有擴展內容都保持不變,因此它仍然以相同的方式反射等。
這個測試通過很好。 現在,這並不是完全有效,因為它當然不再完全相同,但它確實意味着它可能不是框架錯誤解釋小數屬性的類型,或者與拳擊相關的某種混亂/取消裝箱意味着它無法計算出類型,並進行.ToString()進行比較。
假設SQLite EF提供程序正確地將其轉換為SQL ORDER BY子句,您是否檢查過此SQL? 在過去,我做過類似的事情(使用SQLite編寫測試),並發現它不像SQL Server或類似的一些模糊方法那么完整。 也許提供者有一個bug,或者生成的表達式樹中有一個怪癖,它不能很好地理解。
所以我先研究一下,而不是你寫的代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.