2017-05-01 44 views
0

我有一个真正的代码优先部署我的数据模型,我需要执行通常会关联到视图的功能,但我不知道如何在时刻。在实体EF核心包含计算属性

为了简化问题,我的模型包含注册用户创建的内容以及其他用户可以与之交互的内容。我正在设计一个算法,说明这些东西是如何受欢迎的,这些算法归结为与交互类型相关的加权因子乘以某个日期时间的倒数。

举一个例子,假设我们有“喜欢”,“评论”和“购买”,权重分别为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当然是snark,但是当我说,我不确定是否有文件记录的方式做到这一点,我只是想念它或什么。我已经看到选择按照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;} 
} 

我进行添加,迁移步骤,然后改变所产生的向上/向下的如下:

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()和这样的作品,但我不知道这是否是正确的,因为它感觉像这样一个黑客...

+0

_an实体框架兼容way_将涉及C#类上的一些C#代码。发布(简化)类开始。 –

+0

@HenkHolterman,我添加了一些更多的上下文和一些广义的C#代码,让你知道我到目前为止的内容。谢谢参观。 – randcd

回答

2

的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解决方案可能并不全是坏的。

+0

我将不得不测试它看它是如何执行的。我宁愿做这样的事情,因为所有的元素都驻留在代码中,而不是我的解决方案,它只是利用EF中的弱引用。这里的主要问题是,这会驱动人们浏览我的网站时看到的第一页,因此它需要非常快速地加载。感谢您的回复,我会为DateDiff功能寻找NuGet包。 – randcd

+0

让我们听听如何解决问题,加速明智。如果你不得不调整很多,也许会发布一个自我回答。 –

+0

只是想补充一点,上面的查询可以简化为ctx.Things.Select(x => x.ThingId).Take(10)因为您没有过滤,只是预测。包括也是无用的。 – SuperJMN