【问题标题】:MVC Modifying a EF data through a navigation propertyMVC 通过导航属性修改 EF 数据
【发布时间】:2019-08-12 23:47:24
【问题描述】:

我最近完成了关于 udemy 的 MVC 课程,该课程构建了一个视频租赁应用程序。他介绍了如何签出电影,但我们自己去弄清楚如何重新签入。

我有一个客户模型:

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

        [Required(ErrorMessage = "Please enter customer's name.")]
        [StringLength(255)]
        public string Name { get; set; }

        public bool IsSubscribedToNewsletter { get; set; }

        [Display(Name = "Date of Birth")]
        [Min18YearsIfAMember]  
        public DateTime? Birthdate { get; set; }

        public MembershipType MembershipType { get; set; }  

        [Display(Name = "Membership Type")]
        public byte MembershipTypeId { get; set; } 
    }

电影模型:

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

        [Required]
        [StringLength(255)]
        public string Name { get; set; }

        public Genre Genre { get; set; }

        [Display(Name = "Genre")]
        [Required]
        public byte GenreId { get; set; }

        [Display(Name = "Release Date")]
        public DateTime ReleaseDate { get; set; }

        public DateTime DateAdded { get; set; }

        [Display(Name = "Number in Stock")]
        [Range(1, 20)]
        public byte NumberInStock { get; set; }

        public byte NumberAvailable { get; set; }
    }

还有一个包含客户和电影的租赁模型:

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

        [Required]
        public Customer Customer { get; set; }

        [Display(Name = "Customer Name")]
        public int CustomerId { get; set; }

        [Required]
        public Movie Movie { get; set; }

        public DateTime DateRented { get; set; }

        public DateTime? DateReturned { get; set; }
    }

我创建了一个显示有效租借的视图,并执行了删除操作,就像电影已上交一样将其删除,但我不知道如何通过增加可用电影的数量(Movies.NumberAvailable)。我尝试在与删除操作相同的操作中执行此操作,但我没有运气。这是删除操作:

public ActionResult Delete(int id)  
        {
            Rental rental = _context.Rentals.Find(id);
            //var movie = _context.Movies.Where(m => rental.Movie.Id.Contains(m.Id));
            rental.Movie.NumberAvailable++;

            _context.Rentals.Remove(rental);
            _context.SaveChanges();

            return RedirectToAction("List");
        }

我试图将那部电影拉出到它自己的变量中,然后将其加 1,但 rental.Movie.Id 部分弹出一个错误,说它没有包含的定义。如果我按照上面的方式运行它,我会在rental.Movie.NumberAvailable++; 收到一个异常,说“对象引用未设置为对象的实例。”

有没有人知道我可以做些什么来解决这个问题?是的,我是菜鸟。

【问题讨论】:

    标签: c# asp.net asp.net-mvc entity-framework model-view-controller


    【解决方案1】:

    试试:

    Rental rental = _context.Rentals.Where(r => r.Id == id).FirstOrDefault();
    rental.Movie.NumberAvailable++;
    

    【讨论】:

    • 这给出了相同的结果。 Rental 分配了一个 MovieId,但 Movie 为空。
    【解决方案2】:

    我可以建议几种方法来解决此类问题。

    1. 跟踪电影实例及其状态并按需计算总数。

    这有点像 Movie 类,但每部电影都会有一个 MovieDisc 集合,例如:

    public class MovieDisc
    {
       public int MovieDiscId{ get; set; }
       public bool InStock { get; set; }
       public string Barcode { get; set; }
    }
    

    基本上,当电影被签出或扫描时,它们的条形码会识别“光盘”或实例并设置 InStock。从电影方面:

    public class Movie 
    {
       // ...
    
       public virtual ICollection<MovieDisc> Discs {get; internal set;} = new List<MovieDisc>();
       public byte NumberInStock 
       { 
          get { return Discs.Count(x => x.InStock); }
       }
    
       public byte NumberAvailable 
       { 
          get { return Discs.Count(x => !x.InStock); }
       }
    }
    

    这种方法的注意事项是,要使用这些属性,Discs 集合需要预先加载,否则会导致延迟加载。

    1. 对实体采用 DDD 方法。

    领域驱动设计本质上是对领域中状态如何变化的控制。您可以使用域实体上的方法或操作来验证和控制对域允许的有效和完整更改,而不是使用单独的设置器来获取值。

    public class Movie
    {
        public int Id { get; set; }
    
        [Required]
        [StringLength(255)]
        public string Name { get; private set; }
    
        public Genre Genre { get; private set; }
    
        [Display(Name = "Genre")]
        [Required]
        public byte GenreId { get; private set; }
    
        [Display(Name = "Release Date")]
        public DateTime ReleaseDate { get; private set; }
    
        public DateTime DateAdded { get; private set; }
    
        [Display(Name = "Number in Stock")]
        [Range(1, 20)]
        public byte NumberInStock { get; private set; }
    
        public byte NumberAvailable { get; private set; }
    
        public void RentOneOut()
        {
           if (NumberInStock <= 0)
              throw new InvalidOperation("Cannot rent out a movie that has no stock.");
           if (NumberAvailable <= 0)
              throw new InvalidOperation("All movie copies are out.");
    
           NumberAvailable -= 1;
        }
    
        public void ReturnOneIn()
        {
           if (NumberInStock <= 0)
              throw new InvalidOperation("Cannot return a movie that has no stock.");
           if (NumberAvailable >= NumberInStock)
              throw new InvalidOperation("All movie copies are already in. Stocktake needed.");
    
           NumberAvailable += 1;
        }
    }
    

    注意所有的 setter 都是私有的。 (或内部,如果你想启用单元测试)目标是将针对域实体的有效操作表达为一种方法。这确保了多个检查和更新作为一个整体执行,以便多个属性可以一起更新,而不是冒着使实体处于不完整状态的风险。

    这对于您的租赁场景可能更实用:

    public function ReturnRental(Rental rental)
    {
       if (rental == null)
          throw new ArgumentNullException("rental");
    
       rental.Return();
    }
    
    // In Rental:
    public class Rental
    {
       // ...  private setters, like in Movie.
    
       public void Return()
       {
          Movie.ReturnOneIn();
          DateReturned = DateTime.Today;
       }
    }
    

    您希望处理因任何原因返回失败的情况。 (数据状态不同步)

    希望这能给你一些想法。

    【讨论】:

    • 这似乎比我想的要复杂一些。有没有办法从 Delete 操作中增加 NumberAvailable?
    • 您可以在实体之间使用引用,但这会使代码处于开放状态,最终处于无效状态。租借引用电影,因此当您找到要删除的租借时,您可以使用:rental.Movie.NumberAvailable += 1; 然后从 context.Rentals 中删除租借并致电 SaveChanges。但是,这会使您的代码容易出现错误,因为在任何地方添加/删除/修改了租赁,您需要平衡对它们过去和现在的电影关联的更改。 (即,将租借的电影从一部电影更改为另一部电影的功能。)您可以为它编写代码,但随着它的成熟,出现问题的风险也会增加。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多