使用 Ninject 的 ASP.NET MVC 3 应用程序、实体框架 4 代码优先 CTP 5、模式
我尝试使用上述技术构建一些基础项目。我想要最大的灵活性和可测试性,因此我尝试一路使用模式,使其成为未来项目的基础。然而,似乎 出了什么问题或者其他什么,我真的需要帮助。所以我有两个问题:
我当前的代码有什么问题吗?我正确应用了模式吗?有什么建议或推荐可以引导我走向正确的方向吗?
为什么这段代码实际上连接了数据库,创建了数据库,但即使我执行了正确的操作,也不支持插入? (有关此错误的详细信息,请参阅帖子末尾)已修复
我相信这也可以帮助其他人,因为我还没有找到足够的信息来弥补正确。我很确定很多人都尝试以正确的方式去做,但像我一样不确定我所做的是否正确。
我有两个实体:评论和评论
COMMENT
public class Comment
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Author { get; set; }
public virtual string Body { get; set; }
}
REVIEW
public class Review
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Author { get; set; }
public virtual string Body { get; set; }
public virtual bool Visible { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
我以这种方式为每个实体建立了一个基础存储库:
GENERIC REPOSITORY
public abstract class EFRepositoryBase<T> : IRepository<T> where T : class
{
private Database _database;
private readonly IDbSet<T> _dbset;
protected IDatabaseFactory DatabaseFactory { get; private set; }
protected Database Database { get { return _database ?? (_database = DatabaseFactory.Get()); } }
public EFRepositoryBase(IDatabaseFactory databaseFactory)
{
DatabaseFactory = databaseFactory;
_dbset = Database.Set<T>();
}
public virtual void Add(T entity)
{
_dbset.Add(entity);
}
public virtual void Delete(T entity)
{
_dbset.Remove(entity);
}
public virtual T GetById(long id)
{
return _dbset.Find(id);
}
public virtual IEnumerable<T> All()
{
return _dbset.ToList();
}
}
For 特定操作,我使用一个接口:
public interface IReviewRepository : IRepository<Review> {
// Add specific review operations
IEnumerable<Review> FindByAuthor(string author);
}
所以我从抽象类中获取泛型操作加上特定操作:
public class EFReviewRepository : EFRepositoryBase<Review>, IReviewRepository
{
public EFReviewRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{ }
public IEnumerable<Review> FindByAuthor(string author)
{
return base.Database.Reviews.Where(r => r.Author.StartsWith(author))
.AsEnumerable<Review>();
}
}
正如你所想的,我还使用数据库工厂来生成数据库上下文:
DATABASE FACTORY
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private Database _database;
public Database Get()
{
return _database ?? (_database = new Database(@"AppDb"));
}
protected override void DisposeCore()
{
if (_database != null)
_database.Dispose();
}
}
一次性(一些扩展方法...)
public class Disposable : IDisposable
{
private bool isDisposed;
~Disposable()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!isDisposed && disposing)
{
DisposeCore();
}
isDisposed = true;
}
protected virtual void DisposeCore()
{
}
}
数据库
public class Database : DbContext
{
private IDbSet<Review> _reviews;
public IDbSet<Review> Reviews
{
get { return _reviews ?? (_reviews = DbSet<Review>()); }
}
public virtual IDbSet<T> DbSet<T>() where T : class
{
return Set<T>();
}
public Database(string connectionString)
: base(connectionString)
{
//_reviews = Reviews;
}
public virtual void Commit()
{
base.SaveChanges();
}
/*
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// TODO: Use Fluent API Here
}
*/
}
最后,我有我的工作单元...
工作单元 strong>
public class UnitOfWork : IUnitOfWork
{
private readonly IDatabaseFactory _databaseFactory;
private Database _database;
public UnitOfWork(IDatabaseFactory databaseFactory)
{
_databaseFactory = databaseFactory;
}
protected Database Database
{
get { return _database ?? (_database = _databaseFactory.Get()); }
}
public void Commit()
{
Database.Commit();
}
}
我还使用 Ninject 绑定了接口:
NINJECT CONTROLLER FACTORY
public class NinjectControllerFactory : DefaultControllerFactory
{
// A Ninject "Kernel" is the thing that can supply object instances
private IKernel kernel = new StandardKernel(new ReviewsDemoServices());
// ASP.NET MVC calls this to get the controller for each request
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
return (IController)kernel.Get(controllerType);
}
private class ReviewsDemoServices : NinjectModule
{
public override void Load()
{
// Bindings...
Bind<IReviewRepository>().To<EFReviewRepository>();
Bind<IUnitOfWork>().To<UnitOfWork>();
Bind<IDatabaseFactory>().To<DatabaseFactory>();
Bind<IDisposable>().To<Disposable>();
}
}
}
但是,当我调用构造函数时(默认操作)...
public class ReviewController : Controller
{
private readonly IReviewRepository _reviewRepository;
private readonly IUnitOfWork _unitOfWork;
public ReviewController(IReviewRepository postRepository, IUnitOfWork unitOfWork)
{
_reviewRepository = postRepository;
_unitOfWork = unitOfWork;
}
public ActionResult Index()
{
Review r = new Review { Id = 1, Name = "Test", Visible = true, Author = "a", Body = "b" };
_reviewRepository.Add(r);
_unitOfWork.Commit();
return View(_reviewRepository.All());
}
}
这似乎创建了数据库,但没有在EF4 中的数据库。看来我可能找到了问题..在查看数据库对象时..连接状态已关闭并且服务器版本抛出此类异常:
ServerVersion = '(((System.Data.Entity.DbContext (_database)).Database.Connection).ServerVersion' threw an exception of type 'System.InvalidOperationException'
我正在做正确的事情吗?我构建的内容有什么问题吗?
另外,如果您对我发布的代码有建议,我会很高兴。我只是想学习在 MVC 3 中构建任何类型的应用程序的正确方法。我想要一个好的开始。
我使用:
带有代码优先的实体框架 4
ASP.NET MVC 3
Ninject 作为 DI 容器
SQL Server Express(非 R2)
Visual Studio 2010 Web Express
I've tried to build some base project with above technologies. I wanted maximum flexibility and testability so I tried to use patterns along the way to make this as a base for future projects. However, it seem
something is wrong or whatever and I really need help here. So i have two questions :
Is there anything wrong with my current code? I've applied patterns correctly? Any suggestions or recommendation that would lead me in the right direction?
Why do this code actually connect to the database, create it, but doesn't support insert even if I perform the corrects operation? (Look at the end of the post for details about this error) FIXED
I believe this could also help others since I haven't found enough information in order to make something up correctly. I am pretty sure lots of people try to do it the right way and are not sure like me if what I am doing is right.
I have two entities: Comment and Review
COMMENT
public class Comment
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Author { get; set; }
public virtual string Body { get; set; }
}
REVIEW
public class Review
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Author { get; set; }
public virtual string Body { get; set; }
public virtual bool Visible { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
I built up a base repository for each of them this way :
GENERIC REPOSITORY
public abstract class EFRepositoryBase<T> : IRepository<T> where T : class
{
private Database _database;
private readonly IDbSet<T> _dbset;
protected IDatabaseFactory DatabaseFactory { get; private set; }
protected Database Database { get { return _database ?? (_database = DatabaseFactory.Get()); } }
public EFRepositoryBase(IDatabaseFactory databaseFactory)
{
DatabaseFactory = databaseFactory;
_dbset = Database.Set<T>();
}
public virtual void Add(T entity)
{
_dbset.Add(entity);
}
public virtual void Delete(T entity)
{
_dbset.Remove(entity);
}
public virtual T GetById(long id)
{
return _dbset.Find(id);
}
public virtual IEnumerable<T> All()
{
return _dbset.ToList();
}
}
For specific operations, I use an interface:
public interface IReviewRepository : IRepository<Review> {
// Add specific review operations
IEnumerable<Review> FindByAuthor(string author);
}
So I am getting the generics operations from the abstract class plus the specific operations:
public class EFReviewRepository : EFRepositoryBase<Review>, IReviewRepository
{
public EFReviewRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{ }
public IEnumerable<Review> FindByAuthor(string author)
{
return base.Database.Reviews.Where(r => r.Author.StartsWith(author))
.AsEnumerable<Review>();
}
}
As you figured out, I also use a database factory will produce the database context :
DATABASE FACTORY
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private Database _database;
public Database Get()
{
return _database ?? (_database = new Database(@"AppDb"));
}
protected override void DisposeCore()
{
if (_database != null)
_database.Dispose();
}
}
DISPOSABLE (Some extensions methods...)
public class Disposable : IDisposable
{
private bool isDisposed;
~Disposable()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!isDisposed && disposing)
{
DisposeCore();
}
isDisposed = true;
}
protected virtual void DisposeCore()
{
}
}
DATABASE
public class Database : DbContext
{
private IDbSet<Review> _reviews;
public IDbSet<Review> Reviews
{
get { return _reviews ?? (_reviews = DbSet<Review>()); }
}
public virtual IDbSet<T> DbSet<T>() where T : class
{
return Set<T>();
}
public Database(string connectionString)
: base(connectionString)
{
//_reviews = Reviews;
}
public virtual void Commit()
{
base.SaveChanges();
}
/*
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// TODO: Use Fluent API Here
}
*/
}
And to finish, I have my unit of work....
UNIT OF WORK
public class UnitOfWork : IUnitOfWork
{
private readonly IDatabaseFactory _databaseFactory;
private Database _database;
public UnitOfWork(IDatabaseFactory databaseFactory)
{
_databaseFactory = databaseFactory;
}
protected Database Database
{
get { return _database ?? (_database = _databaseFactory.Get()); }
}
public void Commit()
{
Database.Commit();
}
}
I also bound using Ninject the interfaces:
NINJECT CONTROLLER FACTORY
public class NinjectControllerFactory : DefaultControllerFactory
{
// A Ninject "Kernel" is the thing that can supply object instances
private IKernel kernel = new StandardKernel(new ReviewsDemoServices());
// ASP.NET MVC calls this to get the controller for each request
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
return (IController)kernel.Get(controllerType);
}
private class ReviewsDemoServices : NinjectModule
{
public override void Load()
{
// Bindings...
Bind<IReviewRepository>().To<EFReviewRepository>();
Bind<IUnitOfWork>().To<UnitOfWork>();
Bind<IDatabaseFactory>().To<DatabaseFactory>();
Bind<IDisposable>().To<Disposable>();
}
}
}
However, when I call in the constructor (the default action) ...
public class ReviewController : Controller
{
private readonly IReviewRepository _reviewRepository;
private readonly IUnitOfWork _unitOfWork;
public ReviewController(IReviewRepository postRepository, IUnitOfWork unitOfWork)
{
_reviewRepository = postRepository;
_unitOfWork = unitOfWork;
}
public ActionResult Index()
{
Review r = new Review { Id = 1, Name = "Test", Visible = true, Author = "a", Body = "b" };
_reviewRepository.Add(r);
_unitOfWork.Commit();
return View(_reviewRepository.All());
}
}
This seem to create the database but doesnt't insert anything in the database in EF4. It seem that I may figured out the problem.. while looking at the database object.. the connection state is closed and server version throw an exception of this kind :
ServerVersion = '(((System.Data.Entity.DbContext (_database)).Database.Connection).ServerVersion' threw an exception of type 'System.InvalidOperationException'
I am doing the right things? Is there anything wrong in what I've built ?
Also if you have recommandation about the code I posted, I would be glad. I am just trying to the learn the right way for building any kind of application in MVC 3. I want a good a start.
I use :
Entity Framework 4 with Code-First
ASP.NET MVC 3
Ninject as DI Container
SQL Server Express (not R2)
Visual Studio 2010 Web Express
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
呃。这一次很狡猾。实际上我不太了解ninject,所以我无法立即弄清楚。
我找到了与错误相关的第二个问题的解决方案,发现 ninject 实际上拍摄了 DatabaseFactory 的两个实例,一个用于存储库,一个用于工作单元。事实上,错误并不是问题所在。这是对象数据库中的内部错误,但我认为这是正常的,因为我使用实体框架。
真正的问题是 Ninject 绑定了 IDatabaseFactory 的两个不同实例,这导致 2 个连接打开。
该评论已添加到 _reviewRepostory 中使用数据库第一个实例的第一组中。
当对工作单元调用提交时..由于审查不在该数据库实例上,因此它没有保存任何内容。事实上,称为databasefactory的工作单元会导致创建一个新实例,因为ninject发送了它的新实例。
要修复它,只需使用 :
而不是
现在所有系统都可以正常工作了!
现在,我想知道第一个问题的答案,即我当前的代码是否有问题?我正确应用了模式吗?有什么建议或建议可以引导我走向正确的方向吗?
Eww. This one was sneaky. Actually i don't know ninject much so i couldnt figure it out right away.
I found the solution for the SECOND question which was related to the error by finding that ninject actually shoot two instance of the DatabaseFactory, one for the repository and one for the unit of work. Actually, the error was not the problem. It was an internal error in the object database but its normal i think since im using Entity Framework.
The real problem was that Ninject was binding two different instance of IDatabaseFactory which lead to 2 connection open.
The review was added to the first set in _reviewRepostory which was using the first instance of the Database.
When calling commit on the unit of work.. it saved nothing due to the fact that the review wasnt on this database instance. In fact, the unit of work called the databasefactory which lead to creating a new instance since ninject sent a new instance of it.
To fix it simply use :
instead of
And now all the system work correctly!
Now, would love some answers regarding the first question which was if there anything wrong with my current code ? Ive applied patterns correctly ? Any suggestions or recommendation that would lead me in the right direction ?
一个小观察:通过让
EFRepositoryBase
和IReviewRepository
具有返回IEnumerable
而不是IQueryable
的方法, ,您可以防止后续方法向查询添加过滤表达式/约束或投影等。相反,通过使用IEnumerable<>
,您将对完整结果集执行任何后续过滤(例如使用 LINQ 扩展方法),而不是允许这些操作影响和简化运行的 SQL 语句针对数据存储。换句话说,您正在网络服务器级别进行进一步过滤,而不是在数据库级别(如果可能的话)真正所属的数据库级别。
话又说回来,这可能是故意的 - 有时,如果您确实想阻止函数的调用者修改生成的 SQL 等,则使用 IEnumerable<> 是有效的。
One small observation: by having your
EFRepositoryBase
andIReviewRepository
have methods that return anIEnumerable<>
instead of anIQueryable<>
, you prevent subsequent methods from adding filter expressions/constraints or projections or so on to the query. Instead, by usingIEnumerable<>
, you will do any subsequent filtering (e.g. using LINQ extension methods) on the full result set, rather than allowing those operations to affect and simplify the SQL statement that gets run against the datastore.In other words, you are doing further filtering at the webserver level, not at the database level where it really belongs if possible.
Then again, this may be intentional - sometimes using
IEnumerable<>
is valid if you do want to prevent callers of your function from modifying the SQL that is generated, etc.