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

时间:2018-02-08 00:07:12

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

我在此网站上引用了许多与计算字段和ViewModel相关的问题,但我似乎无法根据给出的示例进行推断。我希望制定一个具体的方案可以让别人指出我无法看到的东西。我是WebApp设计的新手。请考虑到这一点。另外,如果我没有提供任何相关信息,请告知我们,我会更新问题。

以下是该场景:

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

我使用AutoMapper从实体映射到ViewModels,反之亦然,但我不知道如何处理计算值。特别是将嵌套的ViewModel Collection抛入混合中。

选项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

的ViewModels

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; }
}

更新2/10/2018

我找到了一个答案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是一个集合,如何完成映射?

更新2/13/2018

我觉得我正在取得进步,但目前我正朝着错误的方向前进。我发现了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查询执行然后执行内存数据中的所有映射,那么我基本上会做与选项2 相同的事情(上面),但我可以在ViewModel中执行计算。这是我将追求的解决方案,并将每个人都发布。

2 个答案:

答案 0 :(得分:0)

您必须迭代UserType Collection并将值映射到UserType dto的集合。

使用此代码。

public function test_user_login_when_phone_is_empty()
{
    $response = $this->json(
        'POST',
        route('login'),
        [
            'phone' => '',
            'password' => 'sdfksfhas4u'
        ]
    );

    $response
        ->assertJsonValidationErrors(['phone'])
        ;
}

希望这会有所帮助。

答案 1 :(得分:0)

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

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

控制器方法

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 Models中进行计算。除了一些人说如果它只是视图的要求就没关系。

最后我会说,注意ViewModel中的变量名称会让我有些头疼。我以为我知道如何利用Linq查询,但是返回的数据存在问题。依靠实体框架提供的急切加载来更容易地恢复所需的分层数据结构,而不是我曾经使用的平台结构。

我对很多这方面还很陌生,围绕MVC和实体框架的一些怪癖缠绕我的脑袋几个小时后让我的大脑死了,但我将继续优化并采用更好的编程方法我走了。