基になるデータベースから必要なデータのみを取得する条件付きクエリのようなものを構築しようとしています。
現在、私は次のクエリを持っています(これはうまくいきます)
var eventData = dbContext.Event.Select(t => new
{
Address = true ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
});
に変更した場合
var includeAddress = true; // this will normally be passed as param
var eventData = dbContext.Event.Select(t => new
{
Address = includeAddress ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
});
次のエラーが表示されます。
型 'AnonymousEventGetAddress'は、単一のLINQ to Entitiesクエリ内の2つの構造的に互換性のない初期化に表示されます。同じクエリの2つの場所で型を初期化できますが、両方の場所で同じプロパティが設定され、それらのプロパティが同じ順序で設定されている場合のみです。
ここで何が間違っていますか(true
が機能しているので)、どうすれば修正できますか?
else
- partを
new AnonymousEventGetAddress
{
AddressLine1 = null,
CityName = null
}
働くでしょう。しかし、プロパティの順序を変更すると、これも失敗します。
使用されるクラスは次のように定義されます:
public class AnonymousEventGetAddress : BaseAnonymousObject<AnonymousEventGetAddress>
{
public string AddressLine1 { get; set; }
public string CityName { get; set; }
}
一方、BaseAnonymousObject<AnonymousEventGetAddress>
が定義されています:
public abstract class BaseAnonymousObject<TAnonymous>
where TAnonymous : BaseAnonymousObject<TAnonymous>
{
// this is used in case I have to return a list instead of a single anonymous object
public static Expression<Func<IEnumerable<TAnonymous>>> Empty => () => new TAnonymous[]{}.AsEnumerable();
}
EFにそのような要件がある理由はわかりませんが、重要なことは、要件が存在し、それを考慮する必要があるということです。
最初のコードは、true
がコンパイル時定数であるため機能します。したがって、コンパイラーはコンパイル時にそれを解決し、2つの式のいずれかで終了します(基本的に三項演算子を削除します)。 2番目のケースでは変数であるため、式ツリーには元の式が含まれ、前述のEF要件のために実行時に失敗します。
しばらく前に、bool変数を解決しようとするカスタムメソッドを実装することにより、この問題や類似の問題(正直なところ、主に動的where
フィルター)を解決しようとしていました。最初のケースでコンパイラを実行します。もちろん、コードは実験的であり、テストされていませんが、そのようなシナリオを適切に処理するようですので、試してみてください。使い方はとても簡単です:
var eventData = dbContext.Event.Select(t => new
{
Address = includeAddress ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
}).ReduceConstPredicates();
そして、ここに使用されるヘルパーメソッドがあります。
public static partial class QueryableExtensions
{
public static IQueryable<T> ReduceConstPredicates<T>(this IQueryable<T> source)
{
var visitor = new ConstPredicateReducer();
var expression = visitor.Visit(source.Expression);
if (expression != source.Expression)
return source.Provider.CreateQuery<T>(expression);
return source;
}
class ConstPredicateReducer : ExpressionVisitor
{
int evaluateConst;
private ConstantExpression TryEvaluateConst(Expression node)
{
evaluateConst++;
try { return Visit(node) as ConstantExpression; }
finally { evaluateConst--; }
}
protected override Expression VisitConditional(ConditionalExpression node)
{
var testConst = TryEvaluateConst(node.Test);
if (testConst != null)
return Visit((bool)testConst.Value ? node.IfTrue : node.IfFalse);
return base.VisitConditional(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.Type == typeof(bool))
{
var leftConst = TryEvaluateConst(node.Left);
var rightConst = TryEvaluateConst(node.Right);
if (leftConst != null || rightConst != null)
{
if (node.NodeType == ExpressionType.AndAlso)
{
if (leftConst != null) return (bool)leftConst.Value ? (rightConst ?? Visit(node.Right)) : Expression.Constant(false);
return (bool)rightConst.Value ? Visit(node.Left) : Expression.Constant(false);
}
else if (node.NodeType == ExpressionType.OrElse)
{
if (leftConst != null) return !(bool)leftConst.Value ? (rightConst ?? Visit(node.Right)) : Expression.Constant(true);
return !(bool)rightConst.Value ? Visit(node.Left) : Expression.Constant(true);
}
else if (leftConst != null && rightConst != null)
{
var result = Expression.Lambda<Func<bool>>(Expression.MakeBinary(node.NodeType, leftConst, rightConst)).Compile().Invoke();
return Expression.Constant(result);
}
}
}
return base.VisitBinary(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (evaluateConst > 0)
{
var objectConst = node.Object != null ? TryEvaluateConst(node.Object) : null;
if (node.Object == null || objectConst != null)
{
var arguments = new object[node.Arguments.Count];
bool canEvaluate = true;
for (int i = 0; i < arguments.Length; i++)
{
var argumentConst = TryEvaluateConst(node.Arguments[i]);
if (canEvaluate = (argumentConst != null))
arguments[i] = argumentConst.Value;
else
break;
}
if (canEvaluate)
{
var result = node.Method.Invoke(objectConst != null ? objectConst.Value : null, arguments);
return Expression.Constant(result, node.Type);
}
}
}
return base.VisitMethodCall(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (evaluateConst > 0 && (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked))
{
var operandConst = TryEvaluateConst(node.Operand);
if (operandConst != null)
{
var result = Expression.Lambda(node.Update(operandConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
object value;
if (evaluateConst > 0 && TryGetValue(node, out value))
return Expression.Constant(value, node.Type);
return base.VisitMember(node);
}
static bool TryGetValue(MemberExpression me, out object value)
{
object source = null;
if (me.Expression != null)
{
if (me.Expression.NodeType == ExpressionType.Constant)
source = ((ConstantExpression)me.Expression).Value;
else if (me.Expression.NodeType != ExpressionType.MemberAccess
|| !TryGetValue((MemberExpression)me.Expression, out source))
{
value = null;
return false;
}
}
if (me.Member is PropertyInfo)
value = ((PropertyInfo)me.Member).GetValue(source);
else
value = ((FieldInfo)me.Member).GetValue(source);
return true;
}
}
}
状況によっては、単純な回避策が可能な場合があります。タイプを異なるタイプとして表示することです。例えば。元のクラスから2つのサブクラスを作成します。この回避策はもちろんかなり汚いですが、Linqの要件はそれ自体で人為的です。私の場合、これは役に立ちました。
私の意見では、データはIQueryable
であるため、より複雑なものをExpression
に選択することを避けようと常に試みています。
したがって、私はこの問題に次のように取り組みます(これには、単純さのいい雰囲気があります)。
戻りデータ用に[〜#〜] dto [〜#〜]を作成します。
public class EventDto
{
// some properties here that you need
public Address Address {get;set;}
}
次に、ロジックをincludeAddress
の周りに分割します
public IEnumerable<EventDto> IncludeAddress(DbContext dbContext)
{
return dbContext.Event.Select(t => new
{
// select out your other properties here
Address = new
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
},
}).ToList().Select(x => new EventDto { Address = Address });
// put what ever mapping logic you have up there, whether you use AutoMapper or hand map it doesn't matter.
}
メソッドNoAddress
または呼び出すものは何でも似ていますが、Address
がないため、マップし直します。
次に、どれを単純に選択できます:
var eventDtos = new List<EventDto>();
if (includeAddress)
eventDtos.AddRange(this.IncludeAddress(dbContext));
else
eventDtos.AddRange(this.NoAddress(dbContext));
eventDtos.ForEach(e => { if (e.Address == null) e.Address = new Address(); });
Select
に多くのロジックがある場合は、SQLを読みやすくするsprocに移動することを検討します。
明らかにこれは単なるガイドであり、問題に取り組む方法についてのアイデアを提供します。
将来の読者のために、このSO複製(1年後に追加)が私の問題を解決する鍵となりました。
このタイプは、単一のLINQ to Entitiesクエリ内の2つの構造的に互換性のない初期化に表示されます
これを見ると、エラーメッセージは非常に明確です。同じLinq式でオブジェクトを複数回インスタンス化する場合、初期化順序を台無しにしないでください。私にとって、それはまさに私がやっていたことでした。 2つのインスタンス化呼び出しの間でプロパティの初期化を同期すると、コンパイラーは再び陽気になりました。
この場合:
new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
}
とは異なります
new AnonymousEventGetAddress()
OPクエリバージョン1では、elseブランチでの異常な初期化は、true
条件、発生してはならないバージョン2の場合、おそらく破棄されたために発生しないと言っても安全です。 'プロパティ1と2に対して2つの初期化順序があり、プロパティはまったくありません。これはそれを行う必要があります:
includeAddress
? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
}
: new AnonymousEventGetAddress
{
AddressLine1 = null,
CityName = null
}
私は同じ問題を抱えていて、select-functionの前に.ToList()を追加する解決策を見つけました:
var eventData = dbContext.Event.ToList().Select(t => new
{
Address = includeAddress ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
});
各プロパティ初期化子に条件ステートメントを配置できます。
var eventData = dbContext.Event.Select(t => new
{
Address = new AnonymousEventGetAddress
{
AddressLine1 = includeAddress ? t.Address.AddressLine1 : null,
CityName = includeAddress ? t.Address.AddressCityName : null
}
});