【问题标题】:one2many relation, get parent with childone2many 关系,得到父母和孩子
【发布时间】:2018-09-25 15:49:05
【问题描述】:
public void getForm(string scode) {    
Form result = DBContext.Forms.Where(f => f.Code == fCode && f.SCode == sCode).FirstOrDefault();

    result.Products = result.Products.Where(p => p.Deleted== false).ToList(); // commenting this line fix the problem
return result;
}

我们如何将以上两行合并在一起以避免出现以下错误。

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

更新:

当在另一个函数中调用时,它会抛出上述错误

public void savetrans(string fcode)
{
                    Form form = GetForm(fCode);
                    var transDb = new DbContext.Data.Transaction()
                    {
                        FId = form.FId,
                        field1= "test",
                        field2= "test",
                        field3= "test",
                    };

                    DbContext.Transactions.Add(transactionDb);

                    DbContext.SaveChanges();
}

【问题讨论】:

  • 您是否要删除已删除的产品?
  • Forms 中附加的产品很少,产品有状态(deleted, true, false),喜欢包含产品,但状态为deleted 的产品为false。效果很好...但是当我尝试为表单添加事务时,它会通过注释 result.products 行引发上述错误。那行得通,所以我相信它一定是它的加载方式,如果我以某种方式使用“包含”将两者结合在一起,它可能会解决关系问题。
  • 如果您想获得没有删除产品的表格,您可以加入表格。如果要从数据库中删除已删除的产品,请选择它们并一一删除,然后保存数据上下文。

标签: c# entity-framework linq


【解决方案1】:

如果您要删除标记为已删除的产品:

var deletedProducts = result.Products.Where(x => x.Deleted).ToList();
foreach(var deletedProduct in deletedProducts)
  result.Products.Remove(deletedProduct);

如果 Form.Products 是 List<Product>,那么您可以使用 .RemoveAll(x => x.Deleted)

如果您只是想在处理实体时排除已删除的产品,那么我建议您为此目的使用未映射的属性:

public class Form
{
   public ICollection<Product> Products { get; set; } = new List<Product>();

   [NotMapped]
   public IReadOnlyCollection<Product> ActiveProducts => Products.Where(x => !x.Deleted).ToList().AsReadOnly();
   // or
   public IReadOnlyCollection<Product> ActiveProducts
   {
     get { return Products.Where(x => !x.Deleted).ToList().AsReadOnly();
   }
}

然后,如果您只想使用有效产品,请使用.ActiveProducts。这种方法的警告是,您不能在 EF Linq 表达式中使用此属性,并且只能以只读容量使用它。例如,不要尝试这样的事情:

var products = context.Forms.Where(x => x.FormId == formId).SelectMany(x => x.ActiveProducts);

这将出错,因为 EF 不会映射 ActiveProducts。您必须使用 .Products 和适当的 Deleted 过滤器。

我通常会在填充视图模型时而不是在实体级别放置诸如处理活动/非活动之类的逻辑。实体应该反映数据模型,而适用于您如何查看/与该逻辑交互的业务逻辑由您的视图模型表示。使用视图模型的好处是,在填充视图模型时,对 Active/Deleted 状态的检查会进行一次,并且它们不会使用在某些用途中无效的属性来毒害实体。

【讨论】:

    【解决方案2】:

    显然您的数据库有FormsProductsFormsProducts 之间存在一对多关系:每个 Form 有零个或多个 Products,每个 Product 使用外键(可能是 Product.FormId)恰好属于一个 Form )

    您的第一条语句获取满足特定要求的Forms 之一,如果没有这样的Form,则为null。在不检查 null 返回值的情况下,您尝试更改此 FormProducts

    问题是,可能有多个Products 具有指向此Form 的不可为空的外键。它们是表单的Products 的ICollection 中的项目。如果给Form.Products分配一个新的ICollection,实体框架想把Form.Products中的Products的外键设置为零,表示这个产品不再属于任何形式。但是在您的模型描述中,您定义了每个Product 应该恰好属于一个Form。因此出现错误。

    所以你应该做什么,取决于你想要的程序。您是否只想获取带有未删除产品的表单,然后您应该执行查询。如果您想从数据库中删除此表单中所有已删除的产品,那么您应该执行更新

    查询某个Form及其未删除的产品

    public ICollection<Form> GetFormWithNonDeletedProducts(string scode)
    {
        using (var dbContext = new MyDbContext(...))
        {
            return dBContext.Forms                      // from the collection of all Forms
                .Where(form => form.Code == fCode      
                        && form.SCode == sCode)         // keep only the ones that I want
                .Select(form => new Form()              // and create a new Form object
                {                                       // with the properties I plan to use
                     Id = form.Id,
                     Name = form.Name,
                     ...
                     Products = form.Products           // Fetch only the non-deleted products
                         .Where(product => !product.Deleted)
                         .ToList(),
                }
                .FirstOrDefault();
            }
        }
    }
    

    我需要创建一个新的 Product 对象的唯一原因是我想把它放在一个返回值中。如果在返回值中不需要它,可以将获取的属性放入匿名对象中。这通常更有效,因为您不会从数据库中获取您不会使用的数据。

    例如上面的例子将分配Product.FormId。你不需要它,因为你知道这个Form的所有千种产品都会有相同的值FormId:即Form.Id的值。

    相同的查询没有获取你不使用的属性(匿名类型)

    using (var dbContext = new MyDbContext(...))
    {
        return dBContext.Forms                  // from the collection of all Forms
            .Where(form => form.Code == fCode      
                && form.SCode == sCode)         // keep only the ones that I want
            .Select(form => new                 // and create a new Form object
            {                                   // with the properties I plan to use
                     Id = form.Id,
                     Name = form.Name,
                     ...
                     Products = form.Products   // Fetch only the non-deleted products
                         .Where(product => !product.Deleted)
                         .Select(product => new
                          {                     // Select only properties you plan to use
                              Name = product.Name,
                              Price = product.Price,
    
                              // not meaningful: you already know the value:
                              // FormId = product.FormId,
                          })
                         .ToList(),
            }
            .FirstOrDefault();
    }
    

    更新数据库:删除表单的已删除产品

    虽然您的函数名为GetForms,但您似乎想使用它来删除已删除的产品。

    最简单的方法是使用DbSet&lt;Products&gt;.RemoveRange

    using (var dbContext = new MyDbContext(...))
    {
        // remove all Deleted products of the form with Code equal to fCode
        // and SCode equal to sCode
        var productsToDelete = dbContext.Products
            .Where(product => product.Deleted
                && product.Form.Code == fCode
                && product.Form.Scode == sCode);
        // note: query is not executed yet!
    
        dbContext.Products.RemoveRange(productsToDelete);
        dbContext.SaveChanges();
    }
    

    可能你必须做RemoveRange(productsToDelete.ToList()),你必须检查一下。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-05
      • 1970-01-01
      • 2016-05-13
      • 1970-01-01
      • 2017-04-30
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      相关资源
      最近更新 更多