【问题标题】:Is there an OCP-friendly way to dynamically choose which collection in a DbContext to access?是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?
【发布时间】: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 是最简单的完成方式我的目标?

【问题讨论】:

  • 如果还要添加新语言,是否还需要添加数据库表?如果是这样,我不会推荐它。一般来说,在多语言环境中添加语言永远不会导致重新编码任何内容。
  • 你能修改它来做这样的事情吗? stackoverflow.com/questions/25302393/…
  • @Stefan 没错。例如,要添加法语支持,我会在我的上下文中添加一个FrenchPhrase 类、一个FrenchPhrases 属性,并且(当前)在我的方法中添加一个case "French" 块。
  • @JAF;好的,那么您的 close for change 关注比switch 声明要广泛得多。我会重新考虑您的方法,并使用 1 张桌子。如果要很大,请使用更动态的方法。 ...顺便说一句,如果它只是应用程序中的多语言支持,您还可以使用Resource (resx) 文件。
  • @Stefan:就像一个新的Phrase 类的集合,包含TextLanguage 属性。通过创建一个包含所有翻译的视图并将DbContext 映射到该视图,可以将数据库中的现有表分开。谢谢!此外,实际上还有用于 UI 翻译的 resx 文件。这种方法和东西在后端用于报告生成。

标签: c# .net entity-framework-core open-closed-principle


【解决方案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.{language}Phrase";
      
          //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&lt;EnglishPhrase&gt;(),但是由于你的参数决定了类型,你需要使用反射来调用正确的OfType方法。

      您可以将所有这些反射代码放入一个辅助类中,这样您的操作会更清晰。

      以后当您想添加更多语言时,您只需编辑 PhraseType 枚举并在 Fluent API 中添加额外的 HasValue。不需要对 Action 方法进行任何更改。

      我刚刚在我的系统上进行了测试,它确实有效。

      【讨论】:

        猜你喜欢
        • 2010-11-25
        • 2012-11-10
        • 1970-01-01
        • 1970-01-01
        • 2021-11-02
        • 1970-01-01
        • 1970-01-01
        • 2012-08-30
        • 1970-01-01
        相关资源
        最近更新 更多