【问题标题】:New records inserted in foreign key table when inserting in parent table插入父表时在外键表中插入新记录
【发布时间】:2014-01-10 02:55:03
【问题描述】:

我是 Asp.net MVC 的新手,正在开发一个简单的博客应用程序(Asp.Net MVC5、EF6)来学习。

我将存储库模式用于具有 EF 代码优先迁移的解决方案架构,Ninject 用于 DI。在客户端,我使用 jQuery Grid for Admin 来管理帖子、类别和标签。

- Blog.Model:Post.cs、Category.cs、Tags.cs

public class Post
{
    [Required(ErrorMessage = "Id is required")]
    public int Id { get; set; }

    [Required(ErrorMessage = "Title is required")]
    [StringLength(500, ErrorMessage = "Title cannot be more than 500 characters long")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Short description is required")]
    public string ShortDescription { get; set; }

    [Required(ErrorMessage = "Description is required")]
    public string Description { get; set; }

    public bool Published { get; set; }

    [Required(ErrorMessage = "PostedOn date is required")]
    public DateTime PostedOn { get; set; }

    public DateTime? ModifiedOn { get; set; }

    [ForeignKey("Category")]
    public virtual int CategoryId { get; set; }

    public virtual Category Category { get; set; }

    public virtual IList<Tag> Tags { get; set; }
}

public class Category
{
    [Key]
    public int CategoryId { get; set; }

    [Required(ErrorMessage = "Category Name is required")]
    [StringLength(500,ErrorMessage = "Category name length cannot exceed 500")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Category Name is required")]
    [StringLength(500, ErrorMessage = "Category name length cannot exceed 500")]     
    public string Description { get; set; }

    [JsonIgnore] 
    public virtual IList<Post> Posts { get; set; }
}

public class Tag
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Name is required")]
    [StringLength(500, ErrorMessage = "Name length should not exceed 500 characters")]
    public string Name { get; set; }

    public string Description { get; set; }

    [JsonIgnore]
    public IList<Post> Posts { get; set; }

}

- Blog.Repository:BlogRepository、IBlogRepository、BlogContext

public interface IBlogRepository
{
    int SavePost(Post post);

    //Other methods...
}

public class BlogRepository : BlogContext, IBlogRepository
{
    public BlogContext _db;

    public BlogRepository(BlogContext db)
    {
        _db = db;
    }

    public int SavePost(Post post)
    {
        _db.Posts.Add(post);
        _db.SaveChanges();
        return post.Id;
    }

    //Other implementations...
}

public class BlogContext : DbContext, IDisposedTracker
{
    public BlogContext() : base("BlogDbConnection") { }

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

    public bool IsDisposed { get; set; }

    protected override void Dispose(bool disposing)
    {
        IsDisposed = true;
        base.Dispose(disposing);
    }

- Blog.Web:AdminController.cs、NinjectWebCommon.cs

AdminController 以 Json 格式发送/使用数据。

public class AdminController : Controller
{    
    private readonly IBlogRepository _blogRepository;

    public AdminController(IBlogRepository blogRepository)
    {
        _blogRepository = blogRepository;
    }

    //POST: /Admin/CreatePost
    [HttpPost, ValidateInput(false)]
    public ContentResult CreatePost([ModelBinder(typeof(PostModelBinder))] Post model)
    {
        string json;

        ModelState.Clear();

        if (TryValidateModel(model))
        {
            var id = _blogRepository.SavePost(model);
            json = JsonConvert.SerializeObject(
                new
                {
                    id = id,
                    success = true,
                    message = "Post saved successfully."
                });
        }
        else
        {
            json = JsonConvert.SerializeObject(
                new
                {
                    id = 0,
                    success = false,
                    message = "Post not saved."
                });
        }
        return Content(json, "application/json");
    }
}

public static class NinjectWebCommon 
{
  private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<BlogContext>().ToSelf(); //This isn't helping either
        kernel.Bind<IBlogRepository>().To<BlogRepository>();
    }   
}

我正在使用自定义模型绑定,因为我在保存帖子时遇到验证异常,因为从网格接收的类别和标签列表未映射到应用程序模型中的实际对象。因此,在自定义模型绑定中,我使用从网格接收到的实际对象填充 Post 对象。这个 Post 对象被发送到控制器,控制器使用 DbContext 和 Repository 保存到数据库。

public class PostModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext,  ModelBindingContext bindingContext)
    {
        var post = (Post)base.BindModel(controllerContext, bindingContext);

        **var blogRepository = new BlogRepository(new BlogContext());**//I think here I need to inject the dependency for BlogContext, but don't know how to do that.

        if (post.Category != null)
        {
            post.Category = blogRepository._db.Categories.AsNoTracking().Single(c => c.CategoryId == post.Category.CategoryId);
        }

        var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');

        if (tags.Length > 0)
        {
            post.Tags = new List<Tag>();

            foreach (var tag in tags)
            {
                var id = int.Parse(tag.Trim());
                post.Tags.Add(blogRepository._db.Tags.AsNoTracking().Single(t => t.Id == id));
            }
        }

        if (bindingContext.ValueProvider.GetValue("oper").AttemptedValue.Equals("edit"))
            post.ModifiedOn = DateTime.UtcNow;
        else
            post.PostedOn = DateTime.UtcNow;

        return post;
    }
}

问题:保存帖子后,数据上下文会在各自的表中插入类别和标签的新行。新创建的帖子是指外键列下的新类别(Id:22)。

帖子:

类别:

标签:

我认为这样做的原因是,当实体被保存时,它被附加到不同的 ObjectContext 并且我需要将它附加到当前上下文但不知道如何?我发现了类似的question asked before,但对此没有公认的答案。任何帮助将不胜感激。

【问题讨论】:

    标签: asp.net-mvc-4 entity-framework-6


    【解决方案1】:

    我能够通过手动将类别和标签值附加到 objectcontext 来解决上述问题,这表明 EF 需要进行更改。这样它就不会在 Category 和 Tag 的父表中创建新条目。

      public int SavePost(Post post)
        {
            //attach tags to db context for Tags to tell EF 
            //that these tags already exist in database
            foreach (var t in post.Tags)
            {
                _db.Tags.Attach(t);
            }
    
            //tell EF that Category already exists in Category table
            _db.Entry(post.Category).State = EntityState.Modified;
    
            _db.Posts.Add(post);
    
            _db.SaveChanges();
    
            return post.Id;
        }
    
     public void EditPost(Post post)
        {
            if (post == null) return;
    
            //get current post from database
            var dbPost = _db.Posts.Include(p => p.Tags).SingleOrDefault(p => p.Id == post.Id);
    
            //get new list of tags
            var newTags = post.Tags.Select(tag => new Tag() { Id = tag.Id, Name = tag.Name, Description = tag.Description }).ToList();
    
            if (dbPost != null)
            {
                //get category from its parent table and assign to db post
                dbPost.Category = _db.Categories.Find(post.Category.CategoryId); ;
    
                //set scalar properties
                _db.Entry(dbPost).CurrentValues.SetValues(post);
    
                //remove tags from post in database
                foreach (var t in dbPost.Tags.ToList())
                {
                    if (!newTags.Contains(t))
                    {
                        dbPost.Tags.Remove(t);
                    }
                }
    
                //add tags to post in database
                foreach (var t in newTags)
                {
                    if (dbPost.Tags.All(p => p.Id != t.Id))
                    {
                        var tagInDb = _db.Tags.Find(t.Id);
                        if (tagInDb != null)
                        {
                            dbPost.Tags.Add(tagInDb);
                        }
                    }
                }
            }
    
            //save changes
            _db.SaveChanges();
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-21
      • 1970-01-01
      • 1970-01-01
      • 2017-08-01
      相关资源
      最近更新 更多