实体框架代码首先在Controller中使用上下文

Spe*_*ets 3 c# linq asp.net-mvc entity-framework

我终于让我的代码按预期工作但我无法弄清楚为什么以前我设置它的方法不起作用.我一直得到一个空引用异常,具体来说,"对象引用未设置为对象的实例.我想要做的是传入一个只读的BlogDb模型并在整个控制器中使用LINQ查询,但似乎每个控制器动作我必须传入BlogDb模型.

private readonly BlogDb model;

public PostsController(BlogDb model)
{
    this.model = model;
}

public ActionResult Index()
{
    return View();
}

[ValidateInput(false)]
public ActionResult Update(int? id, string title, string body, DateTime date, string tags)
{
    var _db  = new BlogDb();

    if (!IsAdmin)
    {
        RedirectToAction("Index");
    }
    if (ModelState.IsValid)
    {
        Post post = GetPost(id);
        post.Title = title;
        post.Body = body;
        post.Date = date;
        post.Tags.Clear();

        tags = tags ?? string.Empty;
        string[] tagNames = tags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

        foreach (string tagName in tagNames)
        {
            post.Tags.Add(GetTag(tagName));
        }

        if (!id.HasValue || id == 0)
        {
            _db.Posts.Add(post);
        }
        _db.SaveChanges();

        return RedirectToAction("Details", new { id = post.Id });
    }
    return View();

}

public ActionResult Edit(int? id)
{

    Post post = GetPost(id);
    var tagList = new StringBuilder();

    foreach (Tag tag in post.Tags)
    {
        tagList.AppendFormat("{0}", tag.Name);
    }

    ViewBag.Tags = tagList.ToString();

    return View(post);

}



public Tag GetTag(string tagName)
{

    //return model.Tags.FirstOrDefault(x => x.Name == tagName) ?? new Tag() { Name = tagName };
    return new Tag() { Name = tagName };

}

private Post GetPost(int? id)
{
    if (id.HasValue && id != 0)
    {
        return model.Posts.FirstOrDefault(x => x.Id == id);
    }
    return new Post();
}
Run Code Online (Sandbox Code Playgroud)

当我尝试使用SaveChanges时,我有下面的代码snippit,它一直抛出一个对象实例异常.

if (!id.HasValue || id == 0)
{
    model.Posts.Add(post);
}
model.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

所以我不得不最终抛出模型的本地实例并以这种方式使用它.

var _db  = new BlogDb();
Run Code Online (Sandbox Code Playgroud)

再进一步下来

if (!id.HasValue || id == 0)
{
    _db.Posts.Add(post);
}
_db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

我只是误解了如何使用数据库上下文?在模型中传递构造函数的目的是什么?

Mat*_*don 7

private readonly BlogDb model;

public PostsController(BlogDb model)
{
    this.model = model;
}

public PostsController()
{

}
Run Code Online (Sandbox Code Playgroud)

你有2个构造函数 - 只有一个构造model字段.该默认控制器工厂将只调用默认参数的构造函数,离开参数一个未使用的(因此,该model领域仍然是null).

这就是您NullReferenceException访问时获得的原因model:该字段的引用从未分配过!

这个:

var _db  = new BlogDb();
Run Code Online (Sandbox Code Playgroud)

是一个草率的修复.相反,链接你的构造函数:

public PostsController()
    : this(new BlogDb())
{

}

public PostsController(BlogDb model)
{
    this.model = model;
}
Run Code Online (Sandbox Code Playgroud)

这样,model无论使用哪个构造函数来创建控制器,都将分配该字段.

请注意,这称为混合注入,一种反模式.要么你DI,要么你没有 - 换句话说,如果你要使用默认的控制器工厂,请改为:

public PostsController()
{
    this.model = new BlogDb();
}
Run Code Online (Sandbox Code Playgroud)

甚至:

private readonly BlogDb model = new BlogDb();
Run Code Online (Sandbox Code Playgroud)

然后你可以删除所有构造函数......并且你有一段紧密耦合的代码.

关于这个主题的一个好的(优秀的)读物是由Mark Seemann 在.NET中的依赖注入.


IDisposable的

BlogDb继承了EF DbContext,它实现了IDisposable接口,这意味着你需要考虑你将如何调用Dispose()该实例.

通过使它成为一个实例级私有字段[而不是进行适当的依赖注入 ],你的生活变得非常复杂.

您需要每个请求一个实例 - 因为只要需要,您就会希望该资源在范围内,这意味着每个ActionResult方法都有一个新实例; 这样的事情:

IEnumerable<Post> posts;
using (var context = new BlogDb())
{
    posts = context.Posts.ToList();
    return View(posts);
}
Run Code Online (Sandbox Code Playgroud)

using块确保IDisposable.Dispose()将调用上context.该唯一的选择,是调用context.Dispose()手动; 如果context是某个private readonly BlockDb _context;字段,那么只有再次使用已处理的上下文之后,事情才会顺利进行.

根据经验,一般情况下,这是负责的对象创建一个IDisposable实例,还负责处置它.


构造函数注入

现在,如果您实现自己的IControllerFactory 使用IoC容器,并将其连接起来global.asax,那就是另一回事了.

现在,负责实例化一次性上下文的对象也负责处理它,并且每次请求可以执行一次,即通过构造函数在每次请求时注入一个新实例 - 然后将其处理掉.

那时你有一个由IoC容器private readonly BlogDb _context; 注入的实例级构造函数 - 因为你知道无论谁给你那个实例,都会正确处理它(因为你已经这样设置).

您将不再需要默认构造函数,因此使用单个构造函数保持简单,该构造函数通过构造函数签名以静态方式记录类具有的每个依赖项:

private readonly BlogDb _context;

public PostsController(BlogDb context)
{
    _context = context;
}
Run Code Online (Sandbox Code Playgroud)

依赖注入的基础是依赖于抽象,而不是实现.这样做......

private readonly IPostsService _service;

public PostsController(IPostsService service)
{
    _service = service;
}
Run Code Online (Sandbox Code Playgroud)

您现在可以自由地注入BlogDb(给定BlogDb : IBlogService)...或实现该接口的任何其他东西,可能是模拟实现,这使得编写可以覆盖所有控制器操作的单元测试成为可能,而无需访问数据库.

通过直接依赖BlogDb,控制器与该类的具体实现细节相关联; 它是紧密耦合的.通过依赖抽象,可以实现松散耦合.

  • @Spets谢谢!你喜欢[Code Review](http://codereview.stackexchange.com/)然后 - 随时随地有*工作代码*你希望同行评审:)(代码按预期工作很重要,否则最好在Stack Overflow上询问 (2认同)
  • retailcoder非常感谢您的帖子.我会在它工作后发布它,我已经阅读了更多关于注射的内容.我想要理解我正在做的事情,而不仅仅是回流和复制粘贴代码.再次感谢! (2认同)