我有一个包含多个UI项目和一堆常见(共享)DLL项目的解决方案。还有一个基于EF的“数据库访问项目”,它包含模型和DbContext,并由UI项目引用。
现在的问题是,对于使用“数据库访问项目”中的DbContext的每个UI项目,我必须安装Entity Framework NuGet包。当我必须更新EF时,问题变得更糟。然后我总是要注意使用EF的所有项目都安装了相同的EF版本。
有更好的解决方案吗?对我来说,正确的方法似乎只需要在一个地方安装EF,即“数据库访问项目”。
我还考虑过隐藏EF和代理对象背后的DbSets,以避免引用EF。
当我省略EF参考时,我会收到类似
的错误Error CS0012 The type 'DbContext' is defined in an assembly that is not referenced. You must add a reference to assembly 'EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
和
Error CS0012 The type 'DbSet<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
答案 0 :(得分:0)
为什么要从DAL层公开DbContext?理想情况下,DAL层应该公开一个使用Dbcontext或Dbset的类。您的UI层上没有任何EF后悔。有多种方法可以实现这一目标。一种常用的模式称为存储库模式。
你可以在这里看到E,g
答案 1 :(得分:0)
您的问题通常是一个示例,说明为什么将数据库的内部暴露给外部世界是不明智的。
您应该公开您的用户希望能够执行Linq语句的数据序列。通常,将DbContext的DbSets类公开为IQueryable集合就足够了。这通常被称为存储库模式。
如果您没有存储库,则用户将创建DbContext类的对象。然后他们将使用DbContext的DbSets以IQueryable的形式访问您的表。使用这些IQueryables来创建和执行查询,可以使用SaveChanges。最后他们将Dispose DbContext,。
使用类似的存储库。您的Repository的用户将创建一个Repository对象。访问代表您的表的IQueryables。执行linq,可能的SaveChanges和Dispose the Repository。
如果您只想允许查询,请公开IQueryable对象,如果您希望能够添加/更新/删除提取的项目,还可以添加Add / Remove / SaveChanges函数。
如果将这些函数公开为接口,则只需通过内存单元测试数据库替换DbContext,或者有时仅使用列表来单元测试代码。
例如,学校,教师,成绩的学校背景:教师有很多学生;学生有很多老师(多对多);学生的成绩为零或以上,每个成绩只属于一名学生(一对多)。
学校上下文中的所有项目都由Id标识:它们都实现了IId。这用于使通用类能够通过Id
获取项目带有接口的DbContext:
interface IId
{
public int Id {get;}
}
class Teacher : IId {...}
class Student: IId {...}
class Grade: IId {...}
interface ISchoolQuerier
{
IQueryable<Teacher> Teachers {get; }
IQueryable<Student> Students {get;}
IQueryable<Grade> Grades {get;}
}
class SchoolDbContext : DbContext
{
public DbSet<Teacher> Teachers {get; set;}
public DbSet<Student> Students {get; set;}
public DbSet<Grades> {get; set;}
// explicit interface implementation
IQueryable<Teacher> ISchoolQuerier.Teachers {get{return this.Teachers;}}
IQueryable<Student> ISchoolQuerier.Students {get{return this.Students;}}
IQueryable<Grade> ISchoolQuerier.Grades{get{return this.Grades;}}
}
使用此SchoolDbContext的SchoolRepository
class SchoolRepository : IDisposable, ISchoolQuerier
{
private SchoolRepository() : this (ISchoolQuerier)(new SchoolDbContext())
{ // default constructor used default SchoolDbContext
}
private SchoolRepository(ISchoolQuerier querier)
{ // use the provided querier
// this constructor is useful for unit tests
this.querier = querier;
}
private bool isDisposed = false;
private readonly ISchoolQuerier;
#region IDisposable
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
if (disposing && !this.isDisposed)
{
IDisposable disposableSchool = this.SchoolQuerier as IDisposable;
if (disposableSchool != null)
{
this.schoolDbContext.Dispose();
}
this.isDisposed = true;
}
}
#endregion IDispose
#region ISchoolQuerier
public IQueryable<Teacher> Teachers {get{return this.SchoolDbContext.Teachers;}}
public IQueryable<Student> Students {get{return this.SchoolDbContext.Students;}}
public IQueryable Grades {get {return this.SchoolDbContext.Grades;}} #endregion ISchoolQuerier }
用户将使用它与使用SchoolContext的方式类似:
using (var schoolRepository = new SchoolRepository())
{
var youngStudents = schoolRepository.Students
.Where(student => student.Birthday > ...)
.Select(student => new {...});
}
SchoolRepository有两个构造函数。默认构造函数使用默认的DbContext。另一个构造函数使用提供的ISchoolQuerier。这个其他构造函数可用于单元测试,您可以在其中提供具有三个集合的类来测试简单查询
如果您只将ISchoolQuerier接口公开给用户,那么他们不必安装实体框架。您也可以在内部更改访问数据库的方式。不再想要实体框架吗?来吧,从现在开始使用Dapper,用户不会注意到差异!
如果要添加/更新/删除项目,请考虑为其创建单独的界面。只有拥有该界面的人才能更新/删除项目。
这里我使用了之前定义的IId
interface IChangeableSet<T> : IQueryable<T> : where T: IId
{
public T Get(int id)
public T Add(T item);
public T Remove(int Id);
public T Remove(T item);
}
class DbSet<T> : IChangeableSet<T> : where T: IId
{
public DbSet(System.Data.Entity.IDbset<T> dbSet)
{
this.dbset = dbSet
}
private readonly System.Data.Entity.IDbSet<T> dbSet;
public T Get(int id)
{
return this.DbSet.Where(item => item.Id == id).SingleOrDefault();
// here is where I uses that all items implement IId
}
public T Add(T item)
{
return this.dbSet.Add(item);
}
... // TODO: fill the other functions
}
SchoolRepository实现了这个接口(最好是显式的实现)
class ChangeableSchoolRepository : IDisposable, ISchoolQuerier
{
public ChangeableSchoolRepository(SchooldDbContext dbContext)
{
this.schoolDbContext = dbContext;
}
private readonly SchoolDbContext;
public IChangeableSet<Teacher> Teachers
{
get {return new DbSet(dbContext.Teachers);}
}
... // etc for Students / Grades
public void SaveChanges()
{
return this.SchoolDbcontext.SaveChanges();
}
}
答案 2 :(得分:0)
我现在使用以下解决方案。通常,我在三个代理对象中封装了我需要的EF和IQueryable的功能。
我现在使用代理对象(DataStore类)来代替直接使用EF DbContext,它为我提供了DbSets(存储库)。
由DbContext公开的EF DbSets包装到一个通用的存储库中,它公开了处理数据所需的功能。 Repository本身派生自代理EF DbQuery的DbQueryProxy。
现在我可以用熟悉的方式轻松处理数据:
using (var db = new DataStore())
{
var students = db.Students.DoSomeLinq(...);
var teachers = db.Teachers.SomeLinq(...);
// do some work ...
// note: The entities in students and teachers are still bound to the DbContext internal to DataStore!
// optional
db.SaveChanges();
}
<强>数据存储强>
public class DataStore : IDisposable
{
private readonly DbContext _dbContext;
#region constructors
public DataStore()
{
_dbContext = new DbContext();
}
#endregion constructors
internal DbContext Context => _dbContext;
public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : EntityBase => _dbContext.RemoveRange(entities);
#region db sets
public Repository<Teacher> Teachers => _dbContext.Teachers; // implicit type conversion from DbSet<TEntity> to Repository<TEntity>!
public Repository<Student> Students => _dbContext.Students;
public Repository<Grade> Grades => _dbContext.Grade;
#endregion db sets
#region DbContext
public void Dispose() => _dbContext.Dispose();
public int SaveChanges() => _dbContext.SaveChanges();
public Repository<TEntity> Set<TEntity>() where TEntity : EntityBase => _dbContext.Set<TEntity>();
public override bool Equals(object obj) => _dbContext.Equals(obj);
public override int GetHashCode() => _dbContext.GetHashCode();
public override string ToString() => _dbContext.ToString();
#endregion DbContext
}
<强>库&LT; TEntity&GT; 强>
public class Repository<TEntity> : DbQueryProxy<TEntity> where TEntity : class
{
private readonly DbSet<TEntity> _DbSet;
internal Repository(DbSet<TEntity> dbSet) : base(dbSet)
{
_DbSet = dbSet;
}
private Repository()
{
}
public static implicit operator DbSet<TEntity>(Repository<TEntity> entry) => entry._DbSet;
public static implicit operator Repository<TEntity>(DbSet<TEntity> entry) => new Repository<TEntity>(entry);
#region DbSet<TEntity>
public TEntity Add(TEntity entity) => _DbSet.Add(entity);
public IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities) => _DbSet.AddRange(entities);
public TEntity Attach(TEntity entity) => _DbSet.Attach(entity);
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity => _DbSet.Create<TDerivedEntity>();
public override bool Equals(object obj) => _DbSet.Equals(obj);
public override int GetHashCode() => _DbSet.GetHashCode();
public TEntity Find(params object[] keyValues) => _DbSet.Find(keyValues);
public Task<TEntity> FindAsync(params object[] keyValues) => _DbSet.FindAsync(keyValues);
public Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues) => _DbSet.FindAsync(cancellationToken, keyValues);
public TEntity Remove(TEntity entity) => _DbSet.Remove(entity);
public IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities) => _DbSet.RemoveRange(entities);
#endregion DbSet<TEntity>
}
<强> DbQueryProxy&LT; TEntity&GT; 强>
public class DbQueryProxy<TResult> : IOrderedQueryable<TResult>, IListSource
{
private readonly DbQuery<TResult> _DbQuery;
internal DbQueryProxy(DbQuery<TResult> entry)
{
_DbQuery = entry;
}
protected DbQueryProxy()
{
}
public static implicit operator DbQuery<TResult>(DbQueryProxy<TResult> entry) => entry._DbQuery;
public static implicit operator DbQueryProxy<TResult>(DbQuery<TResult> entry) => new DbQueryProxy<TResult>(entry);
#region Interfaces
Type IQueryable.ElementType => ((IQueryable)_DbQuery).ElementType;
Expression IQueryable.Expression => ((IQueryable)_DbQuery).Expression;
IQueryProvider IQueryable.Provider => ((IQueryable)_DbQuery).Provider;
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_DbQuery).GetEnumerator();
IEnumerator<TResult> IEnumerable<TResult>.GetEnumerator() => ((IEnumerable<TResult>)_DbQuery).GetEnumerator();
bool IListSource.ContainsListCollection => ((IListSource)_DbQuery).ContainsListCollection;
IList IListSource.GetList() => ((IListSource)_DbQuery).GetList();
#endregion Interfaces
#region DbQuery<TResult>
public string Sql => _DbQuery.Sql;
public DbQueryProxy<TResult> AsNoTracking() => _DbQuery.AsNoTracking();
public override bool Equals(object obj) => _DbQuery.Equals(obj);
public override int GetHashCode() => _DbQuery.GetHashCode();
public DbQueryProxy<TResult> Include(string path) => _DbQuery.Include(path);
public override string ToString() => _DbQuery.ToString();
#endregion DbQuery<TResult>
}