是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?
Posted
技术标签:
【中文标题】是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?【英文标题】:Is there an OCP-friendly way to dynamically choose which collection in a DbContext to access? 【发布时间】:2018-08-24 16:48:22 【问题描述】:我正在研究一种从数据库中检索记录集合的方法。这些记录根据它们包含的数据的一个方面存储在单独的表中。假设它看起来像这样。
public class EnglishPhrase : IPhrase
public string Text get; set;
public class SpanishPhrase: IPhrase
public string Text get; set;
// This is actually a DbContext with DbSets.
// I have not implemented DbContext in this example to
// alleviate overhead when reproducing the situation.
public class MyContext
public EnglishPhrase[] EnglishPhrases get; set;
public SpanishPhrase[] SpanishPhrases get; set;
我的方法需要根据语言参数选择英语或西班牙语短语。现在我正在使用switch
声明来完成它。
public IEnumerable<IPhrase> GetPhrases(string language)
IEnumerable<IPhrase> result = null;
MyContext context = new MyContext();
switch(language)
case "English":
result = context.EnglishPhrases.ToList();
break;
case "Spanish":
result = context.SpanishPhrases.ToList();
break;
default:
throw new Exception();
return result;
我使用switch
是因为我稍后会添加更多语言,但这意味着每次我这样做时我都必须修改此方法。但是,我不禁觉得可能有更好的方法来做到这一点。
我可以做点别的吗,比如向IPhrase
接口添加一个Language
属性,这将允许我的方法以这种方式访问正确的DbSet
,或者switch
是最简单的完成方式我的目标?
【问题讨论】:
如果还要添加新语言,是否还需要添加数据库表?如果是这样,我不会推荐它。一般来说,在多语言环境中添加语言永远不会导致重新编码任何内容。 你能修改它来做这样的事情吗? ***.com/questions/25302393/… @Stefan 没错。例如,要添加法语支持,我会在我的上下文中添加一个FrenchPhrase
类、一个FrenchPhrases
属性,并且(当前)在我的方法中添加一个case "French"
块。
@JAF;好的,那么您的 close for change 关注比switch
声明要广泛得多。我会重新考虑您的方法,并使用 1 张桌子。如果要很大,请使用更动态的方法。 ...顺便说一句,如果它只是应用程序中的多语言支持,您还可以使用Resource
(resx) 文件。
@Stefan:就像一个新的Phrase
类的集合,包含Text
和Language
属性。通过创建一个包含所有翻译的视图并将DbContext
映射到该视图,可以将数据库中的现有表分开。谢谢!此外,实际上还有用于 UI 翻译的 resx
文件。这种方法和东西在后端用于报告生成。
【参考方案1】:
这就是我想到的。更像是伪代码。你需要把它适应你的场景。但包括开闭原则的主要概念。我使用通用类来构建语言对象。
如果您想添加更多语言,只需创建新课程。您可以创建某种语言类,然后英语类(和其他语言)可以从它继承。
您可以使用依赖注入并仅使用接口进行操作。
public interface IPhrase<T>
string Text get; set;
public class Phrase<T> : IPhrase<T> where T : Language
public string Text get; set;
public DbSet<T> DbSet get; set;
public Phrase(string text, DbContext context)
Text = text;
DbSet = context.Set<T>();
public class Language
//do smth
public class English : Language
//do smth
public class Spanish : Language
//do smth
用法:
DbContext _context = new DbContext();
Phrase<English> en = new Phrase<English>("Your english text goes here", _context);
public IEnumerable<IPhrase<T>> GetPhrases(Phrase<T> language)
return language.DbSet.ToList();
GetPhrase 方法甚至可以放在 Phrase 类中。
希望对你有帮助。祝你好运!
【讨论】:
【参考方案2】:您可以在 IPhrase 模型中包含“语言”属性。然后当你加载上下文时: 结果 = context.Where( p => p.Language == languageParam).ToList()
不确定这对你来说是否更容易
【讨论】:
【参考方案3】:您可以使用 TPH 来简化上下文和操作。
型号:
public enum PhraseType
English,
Spanish
public abstract class Phrase
public int Id get; set;
public string Text get; set;
public PhraseType PhraseType get; set;
public class EnglishPhrase : Phrase
public class SpanishPhrase : Phrase
数据库上下文:
public DbSet<Phrase> Phrases get; set;
protected override void OnModelCreating(ModelBuilder modelBuilder)
//...
modelBuilder.Entity<Phrase>()
.HasDiscriminator(p => p.PhraseType)
.HasValue<EnglishPhrase>(PhraseType.English)
.HasValue<SpanishPhrase>(PhraseType.Spanish);
动作方法:
public IEnumerable<Phrase> GetPhrases(string language)
// assuming the language parameter is "english" or "spanish"
var theType = $"YourNamespace.languagePhrase";
//assuming your models are in the current assembly
Type type = TypeInfo.GetType(theType, true, true);
MethodInfo method = typeof(Queryable).GetMethod("OfType").MakeGenericMethod(type);
var obj = appContext.Phrases.AsQueryable();
var result = method.Invoke(obj, new[] obj );
return result as IEnumerable<Phrase>;
一点解释:
您使用 TPH(每个层次结构的表),因此您只需要创建一个 DbSet
。
当你想返回所有EnglishPhrase
时你可以使用context.Phrases.OfType<EnglishPhrase>()
,但是由于你的参数决定了类型,你需要使用反射来调用正确的OfType
方法。
您可以将所有这些反射代码放入一个辅助类中,这样您的操作会更清晰。
以后当您想添加更多语言时,您只需编辑 PhraseType
枚举并在 Fluent API 中添加额外的 HasValue
。不需要对 Action 方法进行任何更改。
我刚刚在我的系统上进行了测试,它确实有效。
【讨论】:
以上是关于是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?的主要内容,如果未能解决你的问题,请参考以下文章
OCP开源项目:Spring Cloud Gateway模块中动态路由的实现