简体   繁体   中英

How to remove DbSet from my interface?

I would prefer that my interface didn't call out DbSet exclusively. I was trying to make them into ICollection and IQueryable...but with IQueryable I couldn't call the "Add" method such as _db.Posts.Add(post). With ICollection whenever I called the Add method it would jump to the get statement and return a list instead of adding it as part of the ORM instructions to do an insert Query.

Any ideas? I'm lost

My controller

private readonly IBlogDb _db;

    public PostsController(IBlogDb db)
    {
        _db = db;
    }

public ActionResult Update(int? id, string title, string body, DateTime date, string tags)
    {



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

            }
            _db.Save();

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

    }

Then we have the interface. I don't get why I need to call out the DbSet here. I'd like to make it a collection? Seems like I'm cluttering things up like was mentioned in this response:
ASP.NET MVC4: IQueryable does not contain a definition for 'Add'

public interface IBlogDb
{


    DbSet<Post> Posts { get; }
    DbSet<Tag> Tags { get; }


    void Save();
}

Finally the DbContext class

public class BlogDb : DbContext, IBlogDb
{
    public BlogDb()
        : base("DefaultConnection")
    {

    }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    void IBlogDb.Save()
    {
        SaveChanges();
    }


    public DbSet<Comment> Comments { get; set; }
    public DbSet<Administrator> Administrators { get; set; }


    //Implementing IBlogDb


}

You can easily remove DbSet from your interface if you embrace explicit implementation and don't mind a few extra lines of code in the implementation:

public interface IMyDbContext
{
    ICollection<Customer> Customers { get; }
}

public class MyDbContext : IMyContext
{
    ICollection<Customer> IMyContext.Customers { get { return (ICollection<Customer>)Customers; } }

    public DbSet<Customer> Customers { get; set; }
}

EDIT: I posted this answer, then reread the question, then deleted it, and now it's back. This will get you what you need as far as Add/Remove from the collection. If you want an additional IQueryable instead, you can layer it with another interface and explicit implementation. However, this gets ugly.

What you really want is to use an IDbSet and create a class, such as this replacement for DbSet

public class MemoryDbSet<TEntity> : DbSet<TEntity>, IQueryable,
    IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity>
    where TEntity : class
{
    private readonly Func<TEntity, object[], bool> _findSelector;
    private readonly ObservableCollection<TEntity> _data;
    private readonly IQueryable _query;

    public MemoryDbSet(Func<TEntity, object[], bool> findSelector)
    {
        _findSelector = findSelector;
        _data = new ObservableCollection<TEntity>();
        _query = _data.AsQueryable();
    }

    public override TEntity Find(params object[] keyValues)
    {
        return _data.SingleOrDefault(item => _findSelector(item, keyValues));
    }

    public override TEntity Add(TEntity item)
    {
        _data.Add(item);
        return item;
    }

    public override TEntity Remove(TEntity item)
    {
        _data.Remove(item);
        return item;
    }

    public override TEntity Attach(TEntity item)
    {
        _data.Add(item);
        return item;
    }

    public override TEntity Create()
    {
        return Activator.CreateInstance<TEntity>();
    }

    public override TDerivedEntity Create<TDerivedEntity>()
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public override ObservableCollection<TEntity> Local
    {
        get { return _data; }
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get
        {
            return new MemoryDbAsyncQueryProvider<TEntity>(_query.Provider);
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.
        GetAsyncEnumerator()
    {
        return new MemoryDbAsyncEnumerator<TEntity>(_data.GetEnumerator());
    }
}

You shouldn't be doing what your are doing!

public class DbContextFactory : IDbContextFactory<BlogDb>
{
    public BlogDb Create() 
    {
        return new BlogDb();
    }
}

Then inject this into your controller:

public class MyController : Controller
{
    private readonly IDbContextFactory<BlogDb> blogContextFactory;
    public MyController(IDbContextFactory<BlogDb> blogContextFactory)
    {
        this.blogContextFactory = blogContextFactory;
    }
}

Then in your method you should be disposing of the context properly:

public ActionResult SaveRecord(FormCollection formStuff)
{
    if (ModelState.IsValid)
    {
         try
         {
             using ( var context = blogContextFactory.Create())
             {
                 // do you stuff here!
             }
         }

    }

}

The reason being is that you don't want to be leaving a database connection open!

Now that you are using the context correctly, you can create yourself a service that exposes the method, in that service you can hide the context implementation.

public class MyService : IMyService
{
    private readonly IDbContextFactory<BlogDb> blogContextFactory;
    public MyService(IDbContextFactory<BlogDb> blogContextFactory)
    {
        this.blogContextFactory = blogContextFactory;
    }

    public void CreateBlog(FormCollection formStuff) {};

    public void UpdateBlog() {};

    public IQueryable<Blog> Retrieve(Func<Blog, bool> predicate)
    {
        return this.context.Blogs.Where(predicate);
    }
}

So IMyService is what your controller will interact with, hence its name as an interface or contract. So this is where you want to expose your methods for manipulating the blog context:

public interface IMyService
{
     void CreateBlog(Blog blog); // you can have what ever passed through to the create, a viewModel, a DTO model or the Domain model. Personally I choose the DTO model.

     void UpdateBlog(Blog blog);

     void DeleteBlog(Guid id);

     IQueryable<Blog> RetreiveBlogs(Func<Blog, bool> predicate); // the predicate allows you to do a quick query if you want to
}

so in your controller do this instead:

public class MyController : Controller
{
    private readonly IMyService blogService;
    public MyController(IMyService blogService)
    {
        this.blogService= blogService;
    }

    public ActionResult SaveRecord(FormCollection formStuff)
    {
        if (ModelState.IsValid)
        {
             try
             {
                 this.blogService.CreateBlog(formStuff);
             }

        }

     }
}

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