在内存中加载相关对象(没有 ORM)

Posted

技术标签:

【中文标题】在内存中加载相关对象(没有 ORM)【英文标题】:Loading related objects in memory (without an ORM) 【发布时间】:2019-01-13 01:32:30 【问题描述】:

我正在使用 ADO.NET 将一堆数据从数据库读取到内存对象中。

这是我的领域模型:

// Question.cs
public class Question

    public int ID  get; set; 
    public string Title  get; set; 
    public string Description  get; set; 
    public IEnumerable<Tag> Tags  get; set; 


// Tag.cs
public class Tag 

    public int ID  get; set; 
    public string Name  get; set; 

在检索问题列表时,我想获取每个问题的相关标签。我可以这样做:

// QuestionRepository.cs

public IList<Question> FindAll()

    var questions = new List<Question>();

    using (SqlConnection conn = DB.GetSqlConnection())
    
        using (SqlCommand cmd = conn.CreateCommand())
        
            cmd.CommandText = "select * from questions";

            SqlDataReader reader = cmd.ExecuteReader();

            while (reader.Read())
            
                Question question = new Question();
                // Populate the question object using reader
                question.Load(reader);

                questions.Add(question);
            
            reader.Close();
        
     
    return questions;



// Question.cs
public void Load(SqlDataReader reader)

    ID = int.Parse(reader["ID"].ToString());
    Title = reader["Title"].ToString();
    Description = reader["Description"].ToString();

    // Use Tag Repository to find all the tags for a particular question
    Tags = tagRepository.GetAllTagsForQuestionById(ID); 


    return questions;


// TagRepository.cs
public List<Tag> GetAllTagsForQuestionById(int id)

    List<Tag> tags = new List<Tag> ();
    // Build sql query to retrive the tags
    // Build the in-memory list of tags 
    return tags;

我的问题是,是否有从数据库中获取相关对象的最佳实践/模式?

我在加载相关数据时遇到的大多数 SO 问题都提供了实体框架的解决方案。这个duplicate question.没有答案

即使我的代码有效,我也想知道其他方法可以做到这一点。我遇到的针对我的特定问题的最接近的解释是 Martin Fowler 的 Lazy Load 模式,我相信这将导致以下实现:

public class Question

    private TagRepository tagRepo = new TagRepository();
    private IList<Tag> tags;

    public int ID  get; set; 
    public string Title  get; set; 
    public string Description  get; set; 
    public IEnumerable<Tag> Tags 
        get
        
            if (tags == null)
            
                tags = tagRepo.GetAllTagsForQuestionById(ID);
            
            return tags;
        
      

还有其他选择吗?

【问题讨论】:

没有理由手动执行此操作。我从不推荐使用 Dapper(因为它可以优雅地处理像 DBNull 这样的事情)。 您可以使用任何 ORM(EF、Dapper、NHibernate 等)...它们为您简化了 DB 访问...延迟加载可以简单地在 EF 中打开/关闭,这不应该让您担心...如果您不想使用任何这些 ORM(我不确定您为什么要这样做?)您可以使用 ADO.NET Entity Framework 使用原始 sql。另请注意,您在这里寻求推荐......这是一个离题主题 【参考方案1】:

如果您坚持在 ADO.Net 中执行此操作,那么我建议对匿名类型、LINQ 和 Enumerable.Range(0,0) 使用一个小技巧。

首先,您需要创建一个匿名类型的列表(或者只创建一个映射回您的 SQL 语句的实际类)

var data = Enumerable.Range(0, 0).Select(x => new

    QestionId = 0,
    Title = "Question.Title",
    Description = "Question.Description",
    TagId = 0,
    Name = "Tag.Name"
).ToList();

接下来是您在 ADO.Net 中查询数据库并获取结果的地方。

这里的关键是编写一个查询,在一个查询中返回您要查找的所有数据。

using (var conn = GetConnection())

    using (var cmd = conn.CreateCommand())
    
        //Construct a valid SQL statement that joins questions to tags
        cmd.CommandText = "SELECT q.*, t.* FROM questions q JOIN tags t ON 1 = 1";

        using (var reader = cmd.ExecuteReader())
        
            while (reader.Read())
            
                data.Add(new
                
                    QestionId = reader.IsDBNull(0) ? 0 : int.TryParse(reader.GetValue(0).ToString(), out var qId) ? qId : 0,
                    Title = reader.IsDBNull(1) ? string.Empty : reader.GetValue(1).ToString(),
                    Description = reader.IsDBNull(2) ? string.Empty : reader.GetValue(2).ToString(),
                    TagId = reader.IsDBNull(3) ? 0 : int.TryParse(reader.GetValue(3).ToString(), out var tId) ? tId : 0,
                    Name = reader.IsDBNull(4) ? string.Empty : reader.GetValue(4).ToString()
                );
            
        
    

现在您的列表已完全填充了所有行,您只需将它们转换回您要查找的对象。

var questions = data.GroupBy(x => new x.QestionId, x.Title, x.Description).Select(y => new Question

    Id = y.Key.QestionId,
    Title = y.Key.Title,
    Description = y.Key.Description,
    Tags = y.Select(z => new Tag
    
        Id = z.TagId,
        Name = z.Name
    )
).ToList();

【讨论】:

以上是关于在内存中加载相关对象(没有 ORM)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用外键保存实体而不在 JPA 中加载相关实体?

在 Kivy 中从内存中加载图像

如何在 UITabBarController 中加载所有视图?

在 EF Core 中加载相关数据

在一个视图 asp.net 中从多到多相关表中加载数据

ORM(Linq-SQL EF)是不是从表中加载整个数据集?