如何使用计算字段查询数据并映射到具有嵌套 ViewModel 集合的 ViewModel?

How to query data with calculated fields and map to a ViewModel with a nested ViewModel Collection?

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

场景如下:

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

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

选项 1

我 return 是自主数据集吗?像下面这样...然后以某种方式使用 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

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

选项 3

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

相关数据

这里是 return 在 SQL 服务器中查询我想要的数据(或多或少)。

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 日更新

我找到了一个答案 ,它很好地解释了我正在查看的内容。特别是在 A Better solution ? 部分下。将查询直接映射到我的 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[=76= 相同的事情](上图),但我可以在 ViewModel 中执行计算。这是我将追求的解决方案,并会及时通知大家。

您必须遍历 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()

希望这会有所帮助。

我成功了! ...我认为。 ...可能是。如果有的话,我正在查询数据,将其映射到我的 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 来实际执行计算。

此外,我看到很多帖子都指出不应在视图模型中进行计算。除了一些人说如果它只是 View 的要求也没关系。

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

我对这方面的很多东西还是很陌生,我一直在思考 MVC 的一些怪癖,Entity Framework 几个小时后我就脑残了,但我会继续优化并采用更好的方法随手编程。