【问题标题】:Include only one property, not entire database row只包括一个属性,而不是整个数据库行
【发布时间】:2017-03-04 16:33:24
【问题描述】:

型号:

public class Word
{
    public int ID { get; set; }
    public string Title { get; set; }
    public DateTime? WhenCreated { get; set; }
    public ApplicationUser Author { get; set; }

    [NotMapped]
    public string AuthorName
    {
        get
        {
            if (Author != null)
            {
                return Author.UserName;
            }
            else {
                return "";
            }
        }
    }

    public List<Definition> Definitions { get; set; }
}

控制器:

[HttpGet]
    public IEnumerable<Word> Get()
    {
        return _db.Words.Include(x=>x.Author).ToList();
    }

我的控制器现在返回整个 ApplicationUser 类,这是 Word 的属性之一。我只想发送ApplicationUser 的一个属性:UserName。我该怎么做?

我添加了AuthorName,它将只返回我想要从ApplicationUser 获得的数据。不幸的是,我仍然需要.Include(x=&gt;x.Author) 才能使该属性正常工作。我可以在数据序列化过程中以某种方式省略包含Author(在向用户发送数据时隐藏它)吗?

我知道我可以使用.Select() 方法,但它需要我输入我需要的所有属性。如果我以后修改我的模型,我将需要更新所有那些.Select(),这会很不方便,而且会浪费时间。

你会怎么解决这个问题?

【问题讨论】:

    标签: c# asp.net entity-framework asp.net-core entity-framework-core


    【解决方案1】:

    您需要创建一个 Dto 对象并为其分配值并返回 Dto。

    Dto

    public class WordDto 
    {
        public string Title { get; set; }
        public DateTime? WhenCreated { get; set; }
        public string AuthorName { get; set; }
    }
    

    然后在你的行动中

    [HttpGet]
    public async Task<IEnumerable<WordDto>> Get()
    {
        return _db.Words
                  .Include(x=>x.Author)
                  .Select(x =>
                      new WordDto 
                      {
                          Title = x.Title,
                          DateTime = x.WhenCreated,
                          AuthorName = x.Author?.UserName ?? string.Empty
                      }
                  )
                  .ToListAsync();
    }
    

    【讨论】:

    • 我会删除.ToList(),感觉更正确,让控制器在需要时实现查询
    • 对不起,我不完全理解。以这种方式使用 Select 将仅返回一些 UserName 变量(我不确定它是什么,它不存在于 Word 类中)。我想返回Word 类的所有属性和Author 的属性之一
    • 然后你需要创建一个Dto对象并对其进行赋值/展平
    • 这是我不想做的,因为主模型中的每一个小变化都需要在我与.Select() 一起使用的所有地方进行更改。它当然解决了这个问题,但我认为这是临时解决方案。
    • 你永远不应该从 REST 服务返回持久性模型,一个 db 更改的后果比必须更新模型要糟糕得多:所有使用此 RESTapi 的应用程序都需要随着持久性的每次更改而更新.这要糟糕得多。这就是您将数据访问层抽象为存储库或请求处理程序的原因
    【解决方案2】:

    你可以试试如下图。

    注意:这里不需要使用Include

        [HttpGet]
        public async Task<IEnumerable<Word>> Get()
        {
            return _db.Words.Select(x => new  
                          {
                              Word = x,
                              AuthorName = x.Author.UserName
                          }
                      ).ToList();
        }
    

    【讨论】:

    • 不幸的是,它显示错误:“无效的匿名类型成员声明器。必须使用成员分配、简单名称或成员访问来声明匿名类型成员”
    • Author 属性不是IEnumerable 时,你怎么能做到x.Author.Select(y =&gt; new { y.UserName })
    • 哦..更正了。谢谢@Brad
    • 您会在 EF Core 中发现您确实需要执行 Include(x =&gt; x.Author),否则 Author 属性将为空。
    • 这不会以任何方式编译,您不能将匿名类型隐式转换为类
    【解决方案3】:

    创建一个视图模型并使用 AutoMapper 进行填充。看看使用 AutoMapper 和 ProjectTo 扩展https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions

    这样,如果您将属性添加到 View 模型,如果它们存在于您的 EF 模型中,它们将被自动映射

    因此,创建一个具有适当命名的所需属性的 VM(请参阅 AutoMapper 文档关于命名约定):

    public class WordVM 
    {
        public string Title { get; set; }
        public DateTime? WhenCreated { get; set; }
        public string AuthorUserName { get; set; }
    }
    

    然后使用 AutoMapper 进行投影(它会执行任何所需的包含,因此如果您稍后更改 VM,它会处理该问题)

      _db.Words.ProjectTo<WordVM>().ToList();
    

    您不需要NotMapped 属性 AutoMapper 会将导航属性Author 和作者属性UserName 映射到AuthorUserName

    【讨论】:

      【解决方案4】:

      我的解决方法是使用 .include() 获取所有相关实体,然后遍历它们并省略我不想返回的属性值。如果您的模型发生更改,则需要进行一些维护,但令人惊讶的是,它并没有显着影响响应时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-18
        • 1970-01-01
        • 1970-01-01
        • 2021-01-20
        • 1970-01-01
        • 2019-06-06
        • 1970-01-01
        • 2015-01-17
        相关资源
        最近更新 更多