简体   繁体   中英

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.

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.

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. The default controller factory will only ever call the default, parameterless constructor, leaving the parameterized one unused (and thus, the model field remains null ).

That's why you get a NullReferenceException when you access model : the field's reference was never assigned!

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.

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:

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 .


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.

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; 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 . The only other option , is to call context.Dispose() manually; if context is some 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.


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.

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; 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).

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.

By depending directly on BlogDb , the controller is tied to the specific implementation details of that class; 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. MVC uses something called a controller factory, that by default will pick a parameterless constructor, if it exists, when creating your controllers. 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. 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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