简体   繁体   English

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

[英]Entity Framework Code First using context in Controller

I finally got my code to work as intended but I cant figure out why the previous way I had it setup didn't work. 我终于让我的代码按预期工作但我无法弄清楚为什么以前我设置它的方法不起作用。 I kept getting a null reference exception, specifically, "Object reference not set to an instance of an object.. What I want to do is to pass in a readonly BlogDb model and use the LINQ queries throughout the controller, but it seems like every controller action I have to pass in the BlogDb model. 我一直得到一个空引用异常,具体来说,“对象引用未设置为对象的实例。我想要做的是传入一个只读的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();
}

When I had the following snippit of code it kept throwing a object instance exception when I try to SaveChanges. 当我尝试使用SaveChanges时,我有下面的代码snippit,它一直抛出一个对象实例异常。

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

So I had to end up throwing a local instance of the model and use it that way. 所以我不得不最终抛出模型的本地实例并以这种方式使用它。

var _db  = new BlogDb();

And further down 再进一步下来

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

Am I just misunderstanding how to work with the database context? 我只是误解了如何使用数据库上下文? Also what is the purpose of making a constructor pass in the model? 在模型中传递构造函数的目的是什么?

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

You have 2 constructors - only one of them sets the model field. 你有2个构造函数 - 只有一个构造函数设置model字段。 The default controller factory will only ever call the default, parameterless constructor, leaving the parameterized one unused (and thus, the model field remains null ). 默认控制器工厂将只调用默认的无参数构造函数,使参数化的一个未使用(因此, model字段保持为null )。

That's why you get a NullReferenceException when you access model : the field's reference was never assigned! 这就是你访问model时得到NullReferenceException的原因:从未分配字段的引用!

This: 这个:

 var _db = new BlogDb(); 

Is a sloppy fix. 是一个草率的修复。 Instead, chain your constructors: 相反, 链接你的构造函数:

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

}

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

This way the model field will be assigned regardless of which constructor is used to create the controller. 这样,无论使用哪个构造函数来创建控制器,都将分配model字段。

Note that this is called bastard injection , a anti-pattern. 请注意,这称为混合注入 ,一种反模式。 Either you DI, or you don't - in other words, if you're going to be using the default controller factory, do this instead: 要么你DI,要么你没有 - 换句话说,如果你要使用默认的控制器工厂,请改为:

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

or even: 甚至:

private readonly BlogDb model = new BlogDb();

And then you can remove all constructors... and you have a tightly coupled piece of code. 然后你可以删除所有构造函数......并且你有一段紧密耦合的代码。

One good (excellent) read on the subject, is Dependency Injection in .NET , by Mark Seemann . 关于这个主题的一个好的(优秀的)读物是由Mark Seemann 在.NET中的依赖注入


IDisposable IDisposable的

Your BlogDb inheriting EF's DbContext , it implements the IDisposable interface, which means you need to think of how you're going to call Dispose() on that instance. 您的BlogDb继承了EF的DbContext ,它实现了IDisposable接口,这意味着您需要考虑如何在该实例上调用Dispose()

By making it an instance-level private field [and not doing proper dependency injection ], you've made your life very complicated. 通过使它成为一个实例级私有字段[而不是进行适当的依赖注入 ],你的生活变得非常复杂。

You want an instance per request - because you'll want that resource to be in-scope only as long as it needs to be, and that means a new instance for each ActionResult method; 您希望每个请求都有一个实例 - 因为只要需要,您就会希望该资源在范围内,这意味着每个ActionResult方法都有一个新实例; something like this: 这样的事情:

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

The using block ensures IDisposable.Dispose() will get called on context . using块确保IDisposable.Dispose()将在context调用。 The only other option , is to call context.Dispose() manually; 唯一的另一种选择是手动调用context.Dispose() ; if context is some private readonly BlockDb _context; 如果context是一些private readonly BlockDb _context; field instead, things will only go well until the disposed context is used again. 相反,只有再次使用已处置的上下文之后,事情才会顺利进行。

As a general rule of thumb, the object that is responsible for creating an IDisposable instance, is also responsible for disposing it. 作为一般经验法则,负责创建 IDisposable实例的对象也负责处理它。


Constructor Injection 构造函数注入

Now if you implement your own IControllerFactory use an IoC container , and wire it up in global.asax , it's a different story. 现在,如果您实现自己的IControllerFactory 使用IoC容器 ,并将其连接到global.asax ,这是一个不同的故事。

Now the object that's responsible for instantiating the disposable context is also responsible for disposing it, and can do so once per request , ie to inject a new instance, through the constructor, at each request - and then dispose it. 现在,负责实例化一次性上下文的对象也负责处理它,并且每次请求可以执行一次 ,即通过构造函数在每次请求时注入一个新实例 - 然后将其处理掉。

That's when you have an instance-level private readonly BlogDb _context; 那是你有一个实例级private readonly BlogDb _context; constructor-injected by the IoC container - because you know that whoever is giving you that instance, will be disposing it properly (because you've set it up that way). 由IoC容器注入构造函数 - 因为你知道无论谁给你那个实例,都会正确处理它(因为你已经这样设置)。

You won't need the default constructor anymore, so keep things simple with a single constructor that ends up statically documenting every dependency the class has, through the constructor signature: 您将不再需要默认构造函数,因此使用单个构造函数保持简单,该构造函数通过构造函数签名以静态方式记录类具有的每个依赖项

private readonly BlogDb _context;

public PostsController(BlogDb context)
{
    _context = context;
}

The foundation of dependency injection , is to depend on abstractions, not implementations . 依赖注入的基础是依赖于抽象,而不是实现 By doing that... 这样做......

private readonly IPostsService _service;

public PostsController(IPostsService service)
{
    _service = service;
}

You're now free to inject a BlogDb (given BlogDb : IBlogService )... or anything else that implements that interface, perhaps a mock implementation , that make it possible to write unit tests that can cover all controller actions, without hitting the database. 你现在可以自由地注入一个BlogDb (给定BlogDb : IBlogService )......或者实现该接口的任何其他东西,也许是一个模拟实现 ,它可以编写可以覆盖所有控制器操作的单元测试 ,而无需访问数据库。

By depending directly on BlogDb , the controller is tied to the specific implementation details of that class; 通过直接依赖于BlogDb ,控制器与该类的特定实现细节相关联; it's tightly coupled . 它是紧密耦合的 By depending on an abstraction , loose coupling is achievable. 通过依赖抽象 ,可以实现松散耦合

The model field will be null because you haven't told MVC how it should handle controllers with non-default constructors. model字段将为null因为您没有告诉MVC它应该如何处理具有非默认构造函数的控制器。 MVC uses something called a controller factory, that by default will pick a parameterless constructor, if it exists, when creating your controllers. MVC使用称为控制器工厂的东西,在创建控制器时,默认情况下会选择无参数构造函数(如果存在)。 If you remove the parameterless contructor, you will probably get an exception. 如果删除无参数构造函数,则可能会出现异常。

If you want to be able to "inject" parameters into your controllers, you need to do one of these: 如果您希望能够将“参数”注入控制器,则需要执行以下操作之一:

Implementing these interfaces is usually done using an IoC container. 通常使用IoC容器来实现这些接口。 There's existing implementations for the most popular ones out there, that you can just plug in and go. 现有最流行的实现,你可以插入即可。 I suggest your read more about MVC and Dependency Injection . 我建议你阅读更多关于MVC和依赖注入的内容

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM