【问题标题】:How to query data with calculated fields and map to a ViewModel with a nested ViewModel Collection?如何使用计算字段查询数据并映射到具有嵌套 ViewModel 集合的 ViewModel?
【发布时间】:2018-02-14 12:38:00
【问题描述】:

我在此站点上引用了许多与计算字段和 ViewModel 相关的问题,但我似乎无法从给出的示例中推断出来。我希望布置一个特定的场景可以让某人指出我看不到的东西。一般来说,我是 WebApp 设计的新手。请考虑到这一点。另外,如果我遗漏了任何相关信息,请告诉我,我会更新问题。

这里是场景:

我有一个复杂的查询,它跨越多个表以返回计算中使用的数据。具体来说,我存储转换为基本单位的配方的单位,然后将数量转换为用户指定的单位。

我正在使用 AutoMapper 从实体映射到 ViewModel,反之亦然,但我不确定如何处理计算值。尤其是嵌套的 ViewModel 集合。

选项 1

我是否返回一组自主的数据?像下面这样......然后以某种方式使用 AutoMapper 进行映射?也许我需要手动进行映射,我还没有找到一个包含嵌套 ViewModel 的可靠示例。在这一点上,我什至不确定以下代码是否正确处理了自治数据的嵌套集合。

        var userId = User.Identity.GetUserId();

        var recipes = from u in db.Users.Where(u => u.Id == userId)
                      from c in db.Categories
                      from r in db.Recipes
                      join ur in db.UserRecipes.Where(u => u.UserId == userId) on r.Id equals ur.RecipeId
                      join mus in db.MeasUnitSystems on ur.RecipeYieldUnitSysId equals mus.Id
                      join muc in db.MeasUnitConvs on mus.Id equals muc.UnitSysId
                      join mu in db.MeasUnits on mus.UnitId equals mu.Id
                      join msy in db.MeasUnitSymbols on mu.Id equals msy.UnitId
                      select new 
                      {
                          Id = c.Id,
                          ParentId = c.ParentId,
                          Name = c.Name,
                          Descr = c.Descr,
                          Category1 = c.Category1,
                          Category2 = c.Category2,
                          Recipes = new 
                          {
                              Id = r.Id,
                              Title = r.Title,
                              Descr = r.Descr,
                              Yield = String.Format("{0} {1}", ((r.Yield * muc.UnitBaseConvDiv / muc.UnitBaseConvMult) - muc.UnitBaseConvOffset), msy.Symbol)
                          }
                      };

选项 2

我想到的另一个选项是返回实体并像往常一样使用 AutoMapper。然后遍历集合并在那里执行计算。我觉得我可以完成这项工作,但对我来说似乎效率低下,因为它会导致许多查询返回数据库。

选项 3

????我想不出任何其他方法来做到这一点。但是,如果您有任何建议,我非常愿意倾听。

相关数据

这是在 SQL Server 中返回我想要的数据的查询(或多或少)。

declare @uid as nvarchar(128) = 'da5435ae-5198-4690-b502-ea3723a9b217'

SELECT  c.[Name] as [Category]
        ,r.Title
        ,r.Descr
        ,(r.Yield*rmuc.UnitBaseConvDiv/rmuc.UnitBaseConvMult)-rmuc.UnitBaseConvOffset as [Yield]
        ,rmsy.Symbol
FROM    Category as c
        inner join RecipeCat as rc on c.Id = rc.CategoryId
        inner join Recipe as r on rc.RecipeId = r.Id
        inner join UserRecipe as ur on r.Id = ur.RecipeId and ur.UserId = @uid
        inner join MeasUnitSystem as rmus on ur.RecipeYieldUnitSysId = rmus.Id
        inner join MeasUnitConv as rmuc on rmus.Id = rmuc.UnitSysId
        inner join MeasUnit as rmu on rmus.UnitId = rmu.Id
        inner join MeasUnitSymbol as rmsy on rmu.Id = rmsy.UnitId
        inner join UserUnitSymbol as ruus on rmsy.UnitId = ruus.UnitId and rmsy.SymIndex = ruus.UnitSymIndex and ruus.UserId = @uid

视图模型

public class CategoryRecipeIndexViewModel
{
    public int Id { get; set; }

    public int ParentId { get; set; }

    [Display(Name = "Category")]
    public string Name { get; set; }

    [Display(Name = "Description")]
    public string Descr { get; set; }

    public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }
    public CategoryRecipeIndexViewModel Category2 { get; set; }

    public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}

public class RecipeIndexViewModel
{
    public int Id { get; set; }

    [Display(Name = "Recipe")]
    public string Title { get; set; }

    [Display(Name = "Description")]
    public string Descr { get; set; }

    [Display(Name = "YieldUnit")]
    public string Yield { get; set; }
}

2018 年 2 月 10 日更新

我找到了一个答案here,它很好地解释了我在看什么。特别是在 更好的解决方案? 部分下。将查询直接映射到我的 ViewModel 看起来也可以让我得到我的计算值。问题是,给出的例子又过于简单了。

他给出了以下 DTO 的

public class UserDto
{
  public int Id {get;set;}
  public string Name {get;set;} 
  public UserTypeDto UserType { set; get; }    
}
public class UserTypeDto
{
    public int Id { set; get; }
    public string Name { set; get; }
}

并为映射执行以下操作:

var users = dbContext.Users.Select(s => new UserDto
{
    Id = s.Id,
    Name = s.Name,
    UserType = new UserTypeDto
    {
        Id = s.UserType.Id,
        Name = s.UserType.Name
    }
});

如果 UserDTO 看起来像这样会怎样:

    public class UserDto
    {
      public int Id {get;set;}
      public string Name {get;set;} 
      public ICollection<UserTypeDto> UserTypes { set; get; }    
    }

如果 UserTypes 是一个集合,映射将如何完成?


2018 年 2 月 13 日更新

我觉得我正在取得进步,但目前正朝着错误的方向前进。我找到了this 并提出了以下内容(由于 linq 查询中的方法调用,目前出现错误):

*注意:我从 ViewModel 中删除了 Category2,因为我发现它不是必需的,只会进一步复杂化。

在索引控制器方法中查询

    IEnumerable<CategoryRecipeIndexViewModel> recipesVM = db.Categories
        .Where(x => x.ParentId == null)
        .Select(x => new CategoryRecipeIndexViewModel()
    {
        Id = x.Id,
        ParentId = x.ParentId,
        Name = x.Name,
        Descr = x.Descr,
        Category1 = MapCategoryRecipeIndexViewModelChildren(x.Category1),
        Recipes = x.Recipes.Select(y => new RecipeIndexViewModel()
        {
            Id = y.Id,
            Title = y.Title,
            Descr = y.Descr
        })
    });

递归方法

private static IEnumerable<CategoryRecipeIndexViewModel> MapCategoryRecipeIndexViewModelChildren(ICollection<Category> categories)
{
    return categories
        .Select(c => new CategoryRecipeIndexViewModel
        {
            Id = c.Id,
            ParentId = c.ParentId,
            Name = c.Name,
            Descr = c.Descr,
            Category1 = MapCategoryRecipeIndexViewModelChildren(c.Category1),
            Recipes = c.Recipes.Select(r => new RecipeIndexViewModel()
            {
                Id = r.Id,
                Title = r.Title,
                Descr = r.Descr
            })
        });
}

在这一点上,我什至没有我需要的计算,但这并不重要,直到我得到这个工作(小步骤)。我很快发现你不能真正调用 Linq 查询中的方法。然后我想到,如果我需要强制执行 Linq 查询,然后对内存中的数据执行所有映射,那么我基本上会做与 Option 2 相同的事情(以上),但我可以在 ViewModel 中执行计算。这是我将追求的解决方案,并将让每个人都知道。

【问题讨论】:

  • 收藏有什么问题? AutoMapper 对集合属性没有任何问题,只要 1) 名称匹配或名称映射已明确配置,并且 2) 子类有映射配置。而且w.r.t。计算:这些也可以在 AM 中使用 ForMember 进行配置。
  • 我对集合的唯一问题是,如果我自己进行映射以获得我想要的数据和计算,但 aakash 回答了这个问题,我相信这是错误的方向我的类别表的递归性质。我已经成功地将 AutoMapper 与集合属性一起使用,但在涉及计算时不确定如何使用它。我目前的计划是将 EF 与 AM 一起使用,以带回所有数据,包括计算所需的数据,然后在 ViewModels 中进行计算。请参阅 2/13 更新。

标签: c# asp.net-mvc-5 entity-framework-6 ef-database-first


【解决方案1】:

您必须遍历 UserType 集合并将值映射到 UserType dto 的集合。

使用此代码。

    var users =  dbContext.Users.Select(s => new UserDto
    Id = s.Id,
    Name = s.FullName,
    UserType = s.UserType.Select(t => new UserTypeDto
    {
        Id = t.Id,
        Name = t.Name
    }).ToList()

希望这会有所帮助。

【讨论】:

  • 我希望早点回来查看。我刚刚找到this,他的回答指出了同样的事情。这会处理 CategoryRecipeIndexViewModel 视图模型中的 Recipes 属性,但我仍然需要映射 Category1 和 Category2。 Category1 是递归集合,Category 2 只是递归的,我还没有对此进行任何研究。我现在将搜索示例,但是如果您知道如何处理递归视图模型,那将有很大帮助!
  • 你问过 UserTypes 是不是一个集合?
  • 是的,但我仅使用该示例来简化我的问题。我尝试填充的实际视图模型(请参阅更新前的问题)更复杂。
  • 我刚刚意识到的其他内容,您给出的示例以及我链接到我们所有 Linq 方法的示例。我发现 Linq 查询更具可读性。我将如何使用 Linq 查询编写它?
【解决方案2】:

我搞定了! ...我认为。 ...也许。如果有的话,我正在查询数据,将其映射到我的 ViewModels 并且我也有计算。我确实有其他问题,但它们更具体。我将在下面布置我遵循的解决方案以及我认为需要工作的地方。

我基本上是从上面实现了我的选项 2,但我没有遍历集合,而是在 ViewModel 中执行了计算。

控制器方法

public ActionResult Index()
{
    var userId = User.Identity.GetUserId();

    var recipes = db.Categories.Where(u => u.Users.Any(x => x.Id == userId))
                        .Include(c => c.Category1)
                        .Include(r => r.Recipes
                            .Select(u => u.UserRecipes
                                .Select(s => s.MeasUnitSystem.MeasUnitConv)))
                        .Include(r => r.Recipes
                            .Select(u => u.UserRecipes
                                .Select(s => s.MeasUnitSystem.MeasUnit.MeasUnitSymbols)));

    IEnumerable<CategoryRecipeIndexViewModel> recipesVM = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryRecipeIndexViewModel>>(recipes.ToList());

    return View(recipesVM);
}

查看模型

public class CategoryRecipeIndexViewModel
{
    public int Id { get; set; }

    public int ParentId { get; set; }

    [Display(Name = "Category")]
    public string Name { get; set; }

    [Display(Name = "Description")]
    public string Descr { get; set; }

    public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }

    public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}

public class RecipeIndexViewModel
{
    public int Id { get; set; }

    [Display(Name = "Recipe")]
    public string Title { get; set; }

    [Display(Name = "Description")]
    public string Descr { get; set; }

    public double Yield { get; set; }

    public ICollection<UserRecipeIndexViewModel> UserRecipes { get; set; }

    [Display(Name = "Yield")]
    public string UserYieldUnit
    {
        get
        {
            return System.String.Format("{0} {1}", ((Yield * 
                        UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvDiv / 
                        UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvMult) - 
                        UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvOffset).ToString("n1"),
                        UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnit.MeasUnitSymbols.FirstOrDefault().Symbol);
        }
    }
}

public class UserRecipeIndexViewModel
{
    public MeasUnitSystemIndexViewModel MeasUnitSystem { get; set; }
}

public class MeasUnitSystemIndexViewModel
{
    public MeasUnitIndexViewModel MeasUnit { get; set; }

    public MeasUnitConvIndexViewModel MeasUnitConv { get; set; }
}

public class MeasUnitIndexViewModel
{
    public ICollection<MeasUnitSymbolIndexViewModel> MeasUnitSymbols { get; set; }
}

public class MeasUnitConvIndexViewModel
{
    public double UnitBaseConvMult { get; set; }

    public double UnitBaseConvDiv { get; set; }

    public double UnitBaseConvOffset { get; set; }
}

public class MeasUnitSymbolIndexViewModel
{
    public string Symbol { get; set; }
}

这似乎有效,但我知道它需要一些工作。

例如,Recipe 和 UserRecipe 之间的关系显示为一对多。实际上,如果 UserRecipe 由当前用户过滤,则关系将是一对一的。此外,MeasUnit 和 MeasUnitSymbol 实体也是如此。目前,我依靠这些集合的 FirstOrDefault 来实际执行计算。

另外,我看到许多帖子指出不应在视图模型中进行计算。除了一些人说如果它只是视图的要求就可以了。

最后我要说的是,注意 ViewModel 中的变量名称会为我省去一些麻烦。我以为我知道如何使用 Linq 查询,但返回的数据有问题。与我习惯使用的平面表结构相比,依靠 Entity Framework 提供的快速加载来恢复所需的分层数据结构更容易。

我对这方面的很多东西还很陌生,并且在几个小时后陷入 MVC 和 Entity Framework 的一些怪癖让我脑残,但我会继续优化并采用更好的编程方法.

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-10
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多