实体框架6最简单的方法来反规范化列,以避免频繁的连接

本文关键字:连接 规范化 框架 最简单 方法 实体 | 更新日期: 2023-09-27 18:05:09

让我们假设,我有两个实体

class Author{
    public int Id{get;set;}
    public string Name{get;set;}
    //.....
}
class Article{
    public int Id{get;set;}
    public int AuthorId{get;set;}
    public string Text{get;set;}
}

现在我想添加到文章AuthorName属性加倍现有的作者。名称以简化结果linq查询和执行时间。我确信我的数据库只会被一个Asp使用。Net MVC项目。使用EF(不使用数据库触发器)实现这种列的常见方法是什么?

还有一个更困难的情况。假设我想在Author实体中有TotalWordCountInAllArticles列,该实体由Article的Text属性计算。

实体框架6最简单的方法来反规范化列,以避免频繁的连接

您可以将AuthorName属性添加到Article,并通过确保创建Articles或更新Author.Name的任何代码也更新所有Articles来手动保持完整性。TotalWordCount也是如此,Article.Text改变的时候,重新计算其他Articles的所有计数。

有一些模式可以使这更自动化,比如域事件模式(https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/),但它绝对不是即插即用的。这取决于这是几件事还是经常发生。

如果您经常为了性能而对数据进行非规范化处理,您可能需要考虑更多的架构,其中有一个规范化的数据库,然后有一个单独的进程,该进程生成数据的非规范化视图并将其放入文档存储中。

注意:这可能不会回答您问题的EF部分,但它确实为您的问题提供了另一种解决方案。

不知道你的项目开发到什么程度了,但你可能想考虑一下Drapper,它可以使这个简单,快速,并提供许多其他好处。

让我们假设对Article模型做了一个小的修改,以包含Author模型。

public class Article 
{
    public int ArticleId { get; set; }
    public string Text { get; set; }
    // using Author model
    public Author Author { get; set; }
}

假设您期望执行的SQL在概念上类似于:

select article.[Id]
    ,article.[Text]
    ,article.[AuthorId]
    ,author.Name
from [Article] article
    join [Author] author on author.AuthorId = article.AuthorId;

用Drapper实现一个存储库来检索它们真的很简单。它可能看起来像:

public class ArticleRepository : IArticleRepository
{
    // IDbCommander is a Drapper construct
    private readonly IDbCommander _commander;
    /// <summary>
    /// Initializes a new instance of the <see cref="ArticleRepository"/> class, 
    /// injecting an instance of the IDbCommander using your IoC framework of 
    /// choice. 
    /// </summary>
    public ArticleRepository(IDbCommander commander)
    {
        _commander = commander;
    }
    /// <summary>
    /// Retrieves all article instances. 
    /// </summary>
    public IEnumerable<Article> RetrieveAll()
    {
        // pass the query method a reference to a 
        // mapping function (Func<T1, T2, TResult>) 
        // although you *could* pass the predicate 
        // in right here, the code is more readable
        // when it's separated out. 
        return _commander.Query(Map.AuthorToArticle);            
    }
    private static class Map
    {
        // simple mapping function which allows you
        // to map out exactly what you want, exactly
        // how you want it. no hoop jumping!
        internal static Func<Article, Author, Article> 
            AuthorToArticle = (article, author) =>
        {
            article.Author = author;
            return article;
        };
    }
}

您将使用Drapper可用的配置将SQL连接到存储库。它支持json和xml配置文件,如果你愿意,你也可以在代码中配置它。

我在Github上为您提供了一个快速示例。

为什么要考虑这个?这样做有很多好处:

  • 您指出了性能问题(执行时间)。Drapper是建立在高性能微orm之王Dapper之上的抽象层。
  • 你可以显式地控制对象的映射——没有奇怪的语义或框架怪癖(就像你面临的那样)。
  • 没有自动生成SQL。你决定将执行什么SQL。
  • 你的SQL和你的c#是分开的——如果你的模式改变了(也许是为了提高性能),没有需要重新编译你的项目,改变你的实体映射或改变任何你的域代码或存储库逻辑。您只需更新配置中的SQL代码。
  • 同样,您可以将服务/存储库层设计得更加域友好,而不必担心数据访问问题会污染您的服务层(反之亦然)。
  • 完全可测试-您可以轻松地模拟来自IDbCommander的结果。
  • 更少的编码-不需要实体和dto(除非你想要它们),不重写OnModelCreating方法或从DbContext派生,POCO上没有特殊属性。

这只是冰山一角。