Я использую Entity Framework с Postgre через Npg sql, часть моей конфигурации для типа Cashout
, который отображается в таблицу Cashouts
, включает:
public void Configure(EntityTypeBuilder<Cashout> builder)
{
builder.ToTable("Cashouts");
builder.ConfigureGuidEntity();
builder.Property(c => c.RecipientAccountId).IsRequired();
builder.Property(c => c.State).IsRequired().HasConversion(
v => v!.Name,
v => Enumeration.FromDisplayName<CashoutState>(v));
builder.HasIndex(e => e.State);
builder.HasIndex(e => e.RecipientAccountId);
builder.UseXminAsConcurrencyToken();
}
Cashout
тип определяется следующим образом:
public class Cashout : GuidEntity
{
public int RecipientAccountId { get; set; }
public string? RecipientAccountName { get; set; }
public decimal Amount { get; set; }
public string? Comment { get; set; }
public CashoutState? State { get; set; } = CashoutState.Pending;
public string? Reason { get; set; }
public Cashout()
{
}
public Cashout(Guid id)
: base(id)
{
}
}
и наследуется от типа GuidEntity
:
public abstract class GuidEntity : Entity<Guid>
{
protected GuidEntity()
{
Id = Guid.Empty;
}
protected GuidEntity(Guid id)
{
Id = id;
}
public DateTimeOffset CreatedOn { get; private set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? UpdatedOn { get; private set; }
}
, который сам по себе наследуется от Entity
:
public abstract class Entity<TKey> : IEntity<TKey>, IEquatable<Entity<TKey>>
where TKey: struct
{
private readonly Lazy<int> _requestedHashCode;
private readonly Lazy<int> _requestedTransientHashCode;
public virtual TKey Id { get; protected set; }
protected Entity()
{
_requestedHashCode = new Lazy<int>(() => Id.GetHashCode() ^ 31);
_requestedTransientHashCode = new Lazy<int>(() => Guid.NewGuid().GetHashCode());
}
public bool IsTransient() =>
EqualityComparer<TKey>.Default.Equals(Id, default);
public override bool Equals(object obj) =>
Equals((obj as Entity<TKey>)!);
public bool Equals(Entity<TKey> other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (other.IsTransient() || this.IsTransient())
{
return false;
}
return EqualityComparer<TKey>.Default.Equals(other.Id, Id);
}
public override int GetHashCode() => IsTransient() ?
_requestedTransientHashCode.Value :
_requestedHashCode.Value;
}
Типом свойства State
является CashoutState
:
public abstract class CashoutState : Enumeration
{
public static CashoutState Transferred = new TransferredCashoutState();
public static CashoutState TransferFailed = new TransferFailedCashoutState();
public static CashoutState Withdrawn = new WithdrawnCashoutState();
public static CashoutState WithdrawalFailed = new WithdrawalFailedCashoutState();
public static CashoutState Pending = new PendingCashoutState();
public static CashoutState Cancelled = new CancelledCashoutState();
protected CashoutState(int id, string name)
: base(id, name) { }
private class TransferredCashoutState : CashoutState
{
public TransferredCashoutState()
: base(1, nameof(Transferred)) { }
}
private class WithdrawnCashoutState : CashoutState
{
public WithdrawnCashoutState()
: base(2, nameof(Withdrawn)) { }
}
private class WithdrawalFailedCashoutState : CashoutState
{
public WithdrawalFailedCashoutState()
: base(3, nameof(WithdrawalFailed)) { }
}
private class TransferFailedCashoutState : CashoutState
{
public TransferFailedCashoutState()
: base(4, nameof(TransferFailed)) { }
}
private class PendingCashoutState : CashoutState
{
public PendingCashoutState()
: base(5, nameof(Pending)) { }
}
private class CancelledCashoutState : CashoutState
{
public CancelledCashoutState()
: base(6, nameof(Cancelled)) { }
}
}
, а затем есть еще один бит наследования с типом Enumeration
:
public abstract class Enumeration : IComparable
{
public string Name { get; private set; }
public int Id { get; private set; }
protected Enumeration(int id, string name)
{
Id = id;
Name = name;
}
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
typeof(T)
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null)).Cast<T>();
public override bool Equals(object obj)
{
if (!(obj is Enumeration otherValue))
return false;
var typeMatches = GetType() == obj.GetType();
var valueMatches = Id.Equals(otherValue.Id);
return typeMatches && valueMatches;
}
public override int GetHashCode() =>
Id.GetHashCode();
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) =>
Math.Abs(firstValue.Id - secondValue.Id);
public static T FromValue<T>(int value) where T : Enumeration =>
Parse<T, int>(value, "value", item => item.Id == value);
public static T FromDisplayName<T>(string displayName) where T : Enumeration =>
Parse<T, string>(displayName, "display name", item =>
string.Equals(item.Name, displayName, StringComparison.InvariantCultureIgnoreCase));
private static T Parse<T, TValue>(TValue value, string description, Func<T, bool> predicate) where T : Enumeration =>
GetAll<T>().FirstOrDefault(predicate) ??
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
public int CompareTo(object obj) =>
Id.CompareTo(((Enumeration)obj).Id);
}
Конфигурация работает для постоянных данных:
var cashout = new Cashout
{
Amount = command.Amount,
RecipientAccountId = command.RecipientAccountId,
Comment = command.Comment,
State = CashoutState.Pending
};
dbContext.Cashouts.Add(cashout);
dbContext.SaveChanges();
, но когда дело доходит до запроса данных, основанных на этом состоянии, это с треском проваливается, я попытался соответственно:
Запрос с указанным c состоянием
var cancelledByState = await _dbContext.Cashouts.Where(x => x.State == CashoutState.Cancelled).FirstAsync();
бросков:
System.InvalidOperationException: The LINQ expression 'DbSet<Cashout>
.Where(c => c.State.Name == __Cancelled_Name_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to
either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancella
tionToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
Запросы с указанием указанного c имени состояния
var cancelledByStateName = await _dbContext.Cashouts.Where(x => x.State.Name == CashoutState.Cancelled.Name).FirstAsync();
бросков:
System.InvalidOperationException: The LINQ expression 'DbSet<Cashout>
.Where(c => c.State.Name == __Cancelled_Name_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to
either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancella
tionToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
Запросы даны конкретный c идентификатор состояния
var cancelledByStateId = await _dbContext.Cashouts.Where(x => x.State.Id == CashoutState.Cancelled.Id).FirstAsync();
throws:
System.InvalidOperationException: The LINQ expression 'DbSet<Cashout>
.Where(c => c.State.Id == __Cancelled_Id_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to eith
er AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancella
tionToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
Я хотел бы найти способ запросить DbSet<Cashout>
, используя свойство состояния.
Примечание 1: кажется, что он работает как чудо при использовании режима In-memory и не срабатывает, как описано выше, когда контекст связан с реальным postgresql сервером базы данных.
Примечание 2: Я также создал 2 выпуска на GitHub: