我有一个标准的EF核心数据模型,有几个一对多和多对多的关系。
我想创建一种方法来生成各种数据计算或通常运行的过程。这些应该返回要在整个应用程序中使用的值,而不是要存储在数据库中的值。
我在这里举一个虚构的例子:
实体1-YearGroup
实体2-学生
关系-一个YearGroup
到许多学生
现在我明白你可以简单地在控制器中写:
int student = _context.Students.Where(s => s.YearGroupId == ygId).Count()
但是假设我想通过在某处创建一个返回此类数据的方法来简化此操作,因此我不必在每次获取YearGroup
中的学生
数量时都指定查询。我很欣赏这是一个简单的示例,但其他示例可能更复杂。
我在考虑在年组. cs
模型中添加一个字段,例如:
public int TotalStudents { //code here to get students from students table }
然后我可以像这样使用:
@model.YearGroup.TotalStudents
但是我不知道如何让它包括关系数据,即来自学生表的数据。
我不希望在单独的、不相关的类中创建随机方法,例如GetWorkentsInYearGroup(ygId)
。如果可能,将其包含在对象模型中会很好。
实现这一目标的最佳实践方式是什么?
注意:我没有代码编辑器也没有项目设置,因此我要编写的内容可能无法编译。
对于任何像你虚构的例子一样简单的情况,如果你只想得到每一年组的学生总数,并且我假设他们的关系已经正确设置,你可以简单地使用导航属性:
// 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>
{
...
}
这是我的偏好,因为我是一个DDD的人,我想先从域构建任何东西(你试图解决什么业务问题),而不考虑它的后端持久性。
这里的基本思想是有2组模型。它们可以是相似的,也可以是完全不同的。一组模型称为领域模型,它反映了您的业务领域。另一组模型称为持久性模型。这可能是您的常规实体框架实体。
更多关于这个: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;
虽然这会增加一些小的性能开销,但您可以从LamdaExtion中提取基本实体和导航属性,并编写一些扩展方法;
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;