簡體   English   中英

創建可重用的 function 以根據 EF Core 數據 model 中的相關數據返回值的最佳方法是什么?

[英]What is the best way to create a reusable function to return a value based on related data in an EF Core data model?

我有一個標准的 EF Core 數據 model,其中包含幾個一對多和多對多的關系。

我想創建一種方法來生成各種數據計算或通常運行的程序。 這些應該返回要在整個應用程序中使用的值,而不是要存儲在數據庫中的值。

我將在這里給出一個虛構的場景示例:

實體 1 - 年YearGroup

實體 2 - Student

關系- YearGroup到許多Students

現在我明白你可以簡單地寫在 controller 中:

int student = _context.Students.Where(s => s.YearGroupId == ygId).Count()

但是假設我想通過在某處創建一個返回此類數據的方法來簡化此操作,這樣我就不必每次獲取YearGroup中的Students人數時都指定查詢。 我很欣賞這是一個簡單的例子,但其他例子可能更復雜。

我正在考慮向yeargroup.cs model 添加一個字段,例如:

public int TotalStudents { //code here to get students from students table }

然后我可以使用它:

@model.YearGroup.TotalStudents

但我不知道如何讓它包含關系數據,即從學生表中獲取。

我不想在單獨的、不相關的類中創建隨機方法,例如GetStudentsInYearGroup(ygId) 如果可能的話,最好將其包含在 object model 中。

實現這一目標的最佳實踐方法是什么?

注意:我沒有代碼編輯器,也沒有項目設置,所以我要寫的東西可能無法編譯。

就像你虛構的例子一樣簡單

對於任何像你虛構的例子一樣簡單的情況,如果你只想獲得每年組的學生總數,並且我假設他們的關系設置正確,你可以簡單地使用導航屬性:

// Find the target year group
var yearGroup = _context.YearGroups
    .SingleOrDefault(x => x.Id == ygId);

int totalStudents = 0;
if (yearGroup != null)
{
    totalStudents = yearGroup.Students.Count();
}

擴展方法

我能想到的另一種方法是將您需要的任何東西定義為實體的擴展方法:

public static class YearGroupExtensions
{
    public static int GetTotalStudents(this YearGroup yearGroup)
    {
        if (yearGroup == null)
        {
            return 0;
        }

        return yearGroup.Students.Count();
    }

    public static int GetTotalStudents(this YearGroup yearGroup, Gender gender)
    {
        if (yearGroup == null)
        {
            return 0;
        }

        if (gender == null)
        {
            return yearGroup.GetTotalStudents();
        }

        return yearGroup
            .Students
            .Count(x => x.Gender == gender);
    }
}

// usage
// YearGroup yearGroup = GetYearGroup(ygId);
// int totalStudents = yearGroup.GetTotalStudents();

存儲庫模式

如果您發現自己在為大多數實體重復您需要的類似方法,那么為它們定義一個通用存儲庫可能會很好。

我不是在這里爭論這是否只是DbContext的包裝器,因為它本身已經在使用存儲庫模式。

public interface IEntity { }

public interface IRepository<T> where T : IEntity
{
    IEnumerable<T> GetAll();
    T GetById(int id);
    void Insert(T entity);
    ...
}

public abstract class RepositoryBase<T> : IRepository<T> where T : IEntity
{
    protected readonly AppDbContext _dbContext;

    protected DbSet<T> _entities;

    private RepositoryBase(AppDbContext dbContext)
    {
        _dbContext = dbContext;
        _entities = dbContext.Set<T>();
    }

    public virtual IEnumerable<T> GetAll()
    {
        return _entities.AsEnumerable();
    }

    ...
}

public class YearGroupRepository : RepositoryBase<YearGroup>
{
    ...
}

分離持久性和域 Model

這是我的偏好,因為我是一個DDD人,我想首先從域構建任何東西(你試圖解決什么業務問題),而不考慮它的后端持久性。

這里的基本思想是有 2 套模型。 它們可以相似,也可以完全不同。 1 套模型稱為域 model,它反映了您的業務域。 另一組模型稱為持久性 model。這可能是您的常規實體框架實體。

更多相關信息: https://stackoverflow.com/a/14042539/2410655

給定一個 DbContext、一個實體和一個導航屬性,您可以按如下方式構造一個 IQueryable;

public static IQueryable AsQueryable(DbContext context, object entity, string navigation){
    var entry = context.Entry(entity);
    if (entry.State == EntityState.Detatched)
        return null;
    var nav = entry.Navigation(navigation);
    return nav.Query();
}

我覺得應該有一個現有的方法,但我現在似乎找不到。

然后,您應該能夠以相當通用的方式對任何導航屬性使用此方法,而無需在各處復制外鍵條件。

public int TotalStudents(DbContext context) =>
    AsQueryable(context, this, nameof(Students))?.Count() ?? 0;

雖然這會增加一些小的性能開銷,但您可以從 LamdaExpression 中提取基本實體和導航屬性,並編寫一些擴展方法;

public class QueryableVisitor : ExpressionVisitor
{
    private object obj;
    public object BaseObject { get; private set; }
    public string Navigation { get; private set; }
            
    protected override Expression VisitConstant(ConstantExpression node)
    {
        BaseObject = obj = node.Value;
        return base.VisitConstant(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        Visit(node.Expression);
        BaseObject = obj;
        if (node.Member is PropertyInfo prop)
            obj = prop.GetValue(obj);
        else if (node.Member is FieldInfo field)
            obj = field.GetValue(obj);
        Navigation = node.Member.Name;
        return node;
    }
}

public static IQueryable<T> AsQueryable<T>(this DbContext context, Expression<Func<IEnumerable<T>>> expression)
{
    var visitor = new QueryableVisitor();
    visitor.Visit(expression);
    var query = AsQueryable(context, visitor.BaseObject, visitor.Navigation);
    return (IQueryable<T>)query;
}

public static int Count<T>(this DbContext context, Expression<Func<IEnumerable<T>>> expression) =>
    AsQueryable(context, expression)?.Count() ?? 0;

啟用如下所示的強類型用法;

public int TotalStudents(DbContext context) =>
    context.Count(() => this.Students);

public int ActiveStudents(DbContext context) =>
    context.AsQueryable(() => this.Students)?.Where(s => s.Active).Count() ?? 0;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM