在实体EF Core

时间:2017-05-01 15:21:44

标签: c# entity-framework-core

我有一个真正的代码优先部署我的数据模型,我需要执行通常与视图关联的功能,但我不知道如何完成它。

为了简化问题,我的模型包含注册用户创建的内容以及其他用户可以与之交互的帖子。我正在设计一种算法来说明这些事物的流行程度,这可归结为与交互类型相关的加权因子乘以某个日期的倒数。

作为一个例子,假设我们分别使用权重为1,2和3的“喜欢”,“评论”和“购买”。我们还说我的时间是1周。在那种情况下,3周前的购买量与本周的类似量一样多。

因此模型的相关部分是Thing,它具有主键和一些额外的元数据; InteractionType具有主键,Weight值和其他一些元数据; Interaction具有复合主键(由Thing主键,User对象主键和InteractionType主键组成)和DateValue列和任何其他与上下文相关的元数据。

最终,我希望以实体框架兼容的方式执行以下查询:

;WITH InteractionScore
AS
(
  SELECT t.Id ThingId, SUM(it.Weight/CAST(DATEDIFF(wk, i.DateValue, GETDATE()) AS float)) IntScore
  FROM Things t
  INNER JOIN Interactions i ON t.Id = i.ThingId
  INNER JOIN InteractionTypes it ON i.InteractionTypeId = it.Id
  GROUP BY t.ID
)
SELECT TOP(10) t.*
FROM Things t
LEFT JOIN InteractionScore isc ON t.Id = isc.ThingId
ORDER BY ISNULL(isc.IntScore, 0.0) DESC

数据模型类定义如下:

namespace Project.Entities
{
  public class User
  {
    [Key]
    public int Id {get; set;}
    public string IdentityRef {get;set;}
    public string DisplayName {get;set;}
    [InverseProperty("Owner")]
    public ICollection<Thing> OwnedThings {get; set;}
  }

  public class InteractionType
  {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public double Weight { get; set; }
  }

  public class Thing
  {
    [Key]
    public int Id {get;set;}
    public int OwnerId {get; set;}
    [ForeignKey("OwnerId")]
    public User Owner {get; set;}
    public string Summary {get; set;}
    public string Description {get; set;}
    public ICollection<Interaction> Interactions {get;set;}
  }

  public class Interaction
  {
    public int ThingId { get; set; }
    public int UserId { get; set; }
    public int InteractionTypeId {get; set;}
    public Thing Thing {get; set;}
    public User User {get;set;}
    public InteractionType InteractionType {get;set;}
    public DateTime DateValue {get;set;}  
    public string Comment {get; set;}
    public string PurchaseReference {get;set;}      
  }

}

消费应用程序中的DbContext定义

namespace Project.Consumer
{
  public class ApplicationData : DbContext
  {
    public DbSet<User> Users {get;set;}
    public DbSet<Thing> Things {get;set;}        

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Interaction>()
               .HasKey(i => new {i.ThingId, i.UserId, i.InteractionTypeId});            
    }
  }

  public List<Thing> GetMostPopularThings(ApplicationData ctx)
  {
    return ctx.Things.OrderByDescending(lambdaMagic).Take(10).ToList();
  }
}

lambdaMagic当然是嗤之以鼻,但正如我所说的那样,我不确定是否有记录的方式这样做,我只是错过了它或者什么。我已经看到可以选择按ctx.Things.FromSql("select string or sp here").Take(10).ToList();的方式做一些事情,这可能没问题,但我真的想减少项目之外存在的事物数量(例如SP定义)

自写这个问题以来,我继续创建另一个名为ThingStatistics的模型类:

public class ThingStatistics
{
  [Key]      
  public int ThingId {get; set;}
  [ForeignKey("ThingId")]
  public Thing Thing {get; set;}
  public double InteractionScore {get;set;}
}

然后我按如下方式扩充了Thing类:

public class Thing
{
    [Key]
    public int Id {get;set;}
    public int OwnerId {get; set;}
    [ForeignKey("OwnerId")]
    public User Owner {get; set;}
    public string Summary {get; set;}
    public string Description {get; set;}
    public ICollection<Interaction> Interactions {get;set;}
    public ThingStatistics Stats {get;set;}
}

我执行了Add-Migration步骤,然后按如下方式更改了结果Up / Down:

public partial class AggregationHack : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
       migrationBuilder.Sql("GO; CREATE VIEW ThingStatistics AS SELECT t.ID ThingId, ISNULL(it.Weight/CAST(DATEDIFF(wk, i.DateValue, GETDATE()) + 1 AS float), 0) InteractionScore FROM Things t LEFT JOIN Interactions i INNER JOIN InteractionTypes it ON i.InteractionTypeId = it.Id ON t.ID = i.ThingId GROUP BY t.Id; GO;");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
       migrationBuilder.Sql("DROP VIEW ThingStatistics; GO;");
    }
}

所以,现在我可以做ctx.Things.Include(t => t.Stats).OrderByDescending(t => t.Stats.InteractionScore).Take(10).ToList()并且这有效,但我不知道这是否正确,因为它感觉就像这样的黑客......

1 个答案:

答案 0 :(得分:3)

ORM的想法是联接大多被导航属性取代。所以你的基本C#查询看起来像

ctx.Things
   .Include(t => t.Interactions).ThenInclude(i => i.InteractionType) 
   .Select(t => new { 
      t.ThingId, 
      IntScore = t.Interactions.Sum(i => i.InteractionType.Weight /  ...) 
    })
   .OrderBy(x => x.IntScore)
   .Select(x => x.ThingId)
   .Take(10);

在那些...上你需要替换DateDiff(NuGet上有第三方pkg)。
虽然这更简单,更易读,但这种方式更难以影响查询的性能,您必须进行测试。您的SQL解决方案可能并不全是坏事。