首先,對不起問題的標題,很難想出一種表達方式,讓我解釋一下情況。
我正在使用規范模式通過物體框架執行資料庫過濾,并避免在記憶體上執行此操作(我大致遵循了這篇文章)。我的基本規范類是這樣的:
public abstract class Specification<T> : ISpecification<T>{
public abstract Expression<Func<T, bool>> FilterExpr();
public bool IsSatisfied(T entity)
{
Func<T, bool> func = this.FilterExpr().Compile();
return func(entity);
}
public Specification<T> And(Specification<T> otherSpec)
{
return new CombinedSpecification<T>(this, otherSpec);
}
}
從這個基本規范類派生出多個強型別規范,它們各自運行良好。但是,在嘗試組合繼承型別的規范時會出現問題。例如,假設我有以下模型:
public abstract class Person
{
public int Age {get; set;}
public string Name {get; set;}
}
public class Baby:Person
{
public bool CanTalk {get; set;}
}
現在,我創建了相應的規范,以便能夠過濾資料庫中的人員:
public class NameSpec : Specification<Person>
{
private string name;
public Namespec(string aName)
{
this.name = aName;
}
public override Expression<Func<Person, bool>> FilterExpr()
{
return p => p.Name == this.name;
}
}
public class IsAbleToTalkSpec : Specification<Baby>
{
public override Expression<Func<Baby, bool>> FilterExpr()
{
return p => p.CanTalk == true;
}
}
所以最后,假設我想過濾每個名為 John 的會說話的嬰兒:
var johnSpec = new NameSpec("John");
var combinedSpecification = johnSpec.And(new IsAbleToTalkSpec());
List<Baby> result = myRepository.Find(combinedSpecification);
Despite my models being properly binded to the DB via the EF configuration, doing this results in a compilation error, because there's no way a Specification<Baby> can be converted to a Specification<Person> when combining them, despite the mentioned inheritance. I understand why this happens, but I have no idea how to solve this without creating a NameSpec<Baby> instead of reusing the NameSpec<Person>, which scales horribly as my models grow. Additionaly, here is my CombinedSpecification<T> class for reference:
internal class CombinedSpecification<T> : Specification<T>
{
private Specification<T> leftSpec;
private Specification<T> rightSpec;
public CombinedSpecification(Specification<T> aSpec, Specification<T> otherSpec)
{
this.leftSpec = aSpec;
this.rightSpec = otherSpec;
}
public override Expression<Func<T, bool>> FilterExpr()
{
var parameter = this.leftSpec.Parameters[0];
var combined = Expression.AndAlso(
leftSpec.Body,
rightSpec.Body.ReplaceParam(rightSpec.Parameters[0], parameter)
);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
}
Thanks in advance for taking the time to read this lengthy rambling, I hope I was clear enough at describing my problem.
uj5u.com熱心網友回復:
你的班級設計與你想要實作的目標相矛盾。
您使用的泛型型別決定了您傳遞給它的物件的型別。這是您選擇使用的型別。但是隨后您想要傳遞不同的(子)型別并自動讓它們將基型別向上轉換為派生型別。即使將泛型放在一邊(除非隱式轉換,這里不相關),這也不是語言所允許的。
從一般的 OOP 角度來看,當您使用基本型別傳遞資料時:
public void DoStuff(Person p)
{
// logic
}
該內部邏輯只能在假設p為Person. 雖然可以向上轉換,但這通常表明 OOP 設計不好,并且在大多數情況下應該避免。
你不會這樣做:
public void DoStuff(object o)
{
var p = o as Person;
}
因此,您也不應該這樣做:
public void DoStuff(Person p)
{
var b = p as Baby;
}
原理完全一樣。
即使您使用的是泛型,您在這里也確實在做同樣的事情。就像我在上面的代碼片段中決定方法引數的型別一樣,您決定了泛型型別。在任何一種情況下,一旦我們選擇了一個基型別,我們就必須使用給定的基型別,而不應該試圖偷偷地向上轉換為派生型別。
有很多方法可以解決手頭的問題。我懷疑很多人會在這里解決對繼承的過度依賴。我同意這是一個可能的問題,但我認為你的例子過于簡單,我無法準確判斷繼承在這里是否合理。為了回答手頭的問題,我將假設它是,但帶有星號,您可能想要重新考慮使用繼承的決定。
使代碼更有效的一種方法是指定泛型型別約束。這允許您在需要時使用子型別。
public class NameSpec<T> : Specification<T> where T : Person
{
private string name;
public Namespec(string aName)
{
this.name = aName;
}
public override Expression<Func<T, bool>> FilterExpr()
{
return p => p.Name == this.name;
}
}
// If you want to avoid peppering your codebase with <Person> generic
// types, you can still create a default implementation.
// This allows you to use the non-generic class when dealing with
// Person objects, and use the more specific generic class when you
// are interested in using a more specific subtype.
public class Namespec : Namespec<Person> { }
注意where T : Person約束。我們已經取得了這個類通用,主叫方被允許選擇一起作業的泛型型別,但是我們已經實施,他們只能選擇通用型別的要么是的,從派生 Person。
基本用法是:
var person = new Person() { Name = "Fred" };
var personNameSpec = new Namespec<Person>("Fred");
Assert.IsTrue(personNameSpec.IsSatisfied(person));
var baby = new Baby() { Name = "Pebbles" };
var babyNameSpec = new Namespec<Baby>("Bamm-Bamm");
Assert.IsFalse(babyNameSpec.IsSatisfied(baby));
上面的邏輯在沒有泛型型別的情況下也能作業Namespec,因為你可以做personNameSpec.IsSatisfied(baby);. 這還不是很酷的部分。
這是很酷的部分:因為babyNameSpec是 a Namespec<Baby>,因此它是 的子型別Specification<Baby>,而不是Specification<Person>like personNameSpecis!
This solves the problem of merging two specifications, as the generic types are now both Baby and therefore there is no longer a Person/Baby type collision.
Specification<Baby> ableToTalkSpec = new IsAbleToTalkSpec();
Specification<Baby> babyNameSpec = new Namespec<Baby>("Bamm-Bamm");
CombinedSpecification<Baby> combinedSpec = ableToTalkSpec.And(babyNameSpec);
var baby = new Baby() { Name = "Pebbles" };
Assert.IsFalse(combinedSpec.IsSatisfied(baby));
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/313309.html
標籤:c# entity-framework generics entity-framework-core specification-pattern
上一篇:GetCustomAttributes和[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
