繁体   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