实体框架未正确修改或删除子项

2024-06-02

我正在尝试保存Firm包含地址和网站的对象。我开发了在 Angular 7 中使用反应式表单在 UI 中添加和删除地址控件的功能。Firm对象,它正在为地址和网站创建附加条目,而不是将其视为现有记录。

因此,如果我从 UI 中删除网站和地址,我可以看到我正在将正确数量的数组元素传递给后端 api。所以我确信问题出在实体框架上。

所以我想要实现的是,如果用户从客户端删除地址或网站,则在调用实体框架中的更新方法时应该更新相同的地址或网站。我正在使用实体框架 6

UI - 我可以在其中添加多个地址

这是我的模型类

新公司视图模型

 public class NewFirmViewModel
    {
        public int FirmId { get; set; }

        public string FirmName { get; set;}

        public Nullable<DateTime> DateFounded { get; set; }

        public ICollection<AddressViewModel> Addresses { get; set; }

        public ICollection<WebsiteViewModel> Websites { get; set; }

        public bool hasIntralinks { get; set; }
    }

地址视图模型

public class AddressViewModel
{
    public int AddressId { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string Line3 { get; set; }
    public string Phone { get; set; }
    public bool IsHeadOffice { get; set; }
    public int FirmId { get; set; }
}

网站视图模型

public class WebsiteViewModel
{
    private int FirmWebsiteId { get; set; }
    private string WebsiteUrl { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public int FirmId { get; set; }
}

Entities

public class FIRM: Entity,IHasAUMs<FIRM_AUM> 
    {
        public FIRM()
        {
            //this.FIRM_PERSON = new HashSet<FIRM_PERSON>();
            this.MANAGERSTRATEGies = new HashSet<MANAGERSTRATEGY>();
            this.FIRM_ACTIVITY = new HashSet<FIRM_ACTIVITY>();
            this.FIRM_AUMs = new HashSet<FIRM_AUM>();
            this.FIRM_REGISTRATION = new HashSet<FIRM_REGISTRATION>();
            //this.ACTIVITies = new HashSet<ACTIVITY>();
            Addresses = new HashSet<ADDRESS>();
            //People = new HashSet<PERSON>();   
           // Websites = new HashSet<FIRM_WEBSITE>();
        }

        //public decimal ID { get; set; }
        //
        //
        //
        //
        public string NAME { get; set; }
        public string SHORT_NAME { get; set; }
        public string ALTERNATE_NAME { get; set; }
        public string WEBSITE { get; set; }
        public string WEBSITE_USERNAME { get; set; }
        public string WEBSITE_PASSWORD { get; set; }
        public bool? INTRALINKS_FIRM { get; set; }        
        public string NOTES_TEXT { get; set; }
        public string NOTES_HTML { get; set; }
        public string HISTORY_TEXT { get; set; }
        public string HISTORY_HTML { get; set; }

        public string HISTORY_SUM_TEXT { get; set; }
        public string HISTORY_SUM_HTML { get; set; }

        public Nullable<decimal> OLD_ORG_REF { get; set; }
        public Nullable<decimal> SOURCE_ID { get; set; }

        [DisplayFormat(DataFormatString = PermalConstants.DateFormat)]
        public Nullable<DateTime> DATE_FOUNDED { get; set; }

        public virtual  ICollection<ADDRESS> Addresses { get; set; }

      //  public ICollection<FIRM_WEBSITE> Websites { get; set; }
        // public ICollection<PERSON> People { get; set; }

        //public SOURCE SOURCE { get; set; }
        // public ICollection<FIRM_PERSON> FIRM_PERSON { get; set; }
        public ICollection<MANAGERSTRATEGY> MANAGERSTRATEGies { get; set; }
        public ICollection<FIRM_ACTIVITY> FIRM_ACTIVITY { get; set; }
        public ICollection<FIRM_REGISTRATION> FIRM_REGISTRATION { get; set; }
        //public ICollection<ACTIVITY> ACTIVITies { get; set; }
        public ICollection<FIRM_WEBSITE> Websites { get; set; }

        public Nullable<int> KEY_CONTACT_ID { get; set; }
        [NotMapped]
        public ICollection<FIRM_AUM> AUMs
        {
            get
            {
                return this.FIRM_AUMs;
            }
        }
        public ICollection<FIRM_AUM> FIRM_AUMs { get; set; }
    }


    ADDRESS

      public class ADDRESS : Entity
    {
        public ADDRESS()
        {
            // DATE_CREATED = DateTime.Now;
        }


        public string LINE1 { get; set; }
        public string LINE2 { get; set; }
        public string LINE3 { get; set; }
        public int CITY_ID { get; set; }
        public string POSTAL_CODE { get; set; }
        public string SWITCHBOARD_INT { get; set; }
        public string NOTES { get; set; }
        public int? OLD_ADDRESS_REF { get; set; }
        public int? SOURCE_ID { get; set; }

        public int FIRM_ID { get; set; }
        [ForeignKey("FIRM_ID")]
        public FIRM FIRM { get; set; }

        [ForeignKey("CITY_ID")]
        public CITY City { get; set; }

        public ICollection<PERSON> People { get; set; }

        // public SOURCE SOURCE { get; set; }

        public bool IS_HEAD_OFFICE { get; set; }
        [NotMapped]
        public string AddressBlurb
        {
            get
            {
                return string.Join(",", new[] { LINE1, LINE2, City != null ? City.NAME : "", City != null && City.Country != null ? City.Country.NAME : "" }.Where(x => !string.IsNullOrEmpty(x)));
            }
        }
    }


    FIRM_WEBSITE

      public class FIRM_WEBSITE : Entity
    {
        public FIRM_WEBSITE()
        {

        }
        private string _WEBSITE_URL;

        public string WEBSITE_URL
        {
            get
            {
                if (string.IsNullOrEmpty(_WEBSITE_URL))
                    return _WEBSITE_URL;
                try
                {

                    var ubuilder = new System.UriBuilder(_WEBSITE_URL ?? "");

                    return ubuilder.Uri.AbsoluteUri;
                }
                catch (UriFormatException ex)
                {
                    return _WEBSITE_URL;
                }

            }
            set { _WEBSITE_URL = value; }
        }

        public string USERNAME { get; set; }
        public string PASSWORD { get; set; }


        public int FIRM_ID { get; set; }
        [ForeignKey("FIRM_ID")]
        public FIRM FIRM { get; set; }
    }

API控制器

  [HttpPut]
    [SkipTokenAuthorization]
    [Route("api/firm/update")]
    public IHttpActionResult Update(NewFirmViewModel model)
    {


          var firmService = GetService<FIRM>();

        if (model == null) return StatusCode(HttpStatusCode.NotFound);

        var firm = firmService.GetWithIncludes(model.FirmId);

        if (firm != null)
        {
            firm.NAME = model.FirmName;
            firm.DATE_FOUNDED = model.DateFounded;
            firm.Addresses = model.Addresses.Select(x => new ADDRESS() {ID = x.AddressId, LINE1 = x.Line1, LINE2 = x.Line2, LINE3 = x.Line3, FIRM_ID = x.FirmId}).ToList();
            firm.Websites = model.Websites.Select(x => new FIRM_WEBSITE() {ID = x.FirmWebsiteId, WEBSITE_URL = x.WebsiteUrl, USERNAME = x.Username, PASSWORD = x.Password, FIRM_ID = x.FirmId}).ToList();


            var addressIds = model.Addresses.Select(x => x.AddressId).ToList();
            var addresses = firm.Addresses.Where(x => addressIds.Contains(x.ID)).ToList(); // All of the addresses we want to associate to this firm.
            // Identify addresses to remove from this firm.
            var addressesToRemove = firm.Addresses.Where(x => !addressIds.Contains(x.ID)).ToList();
            foreach (var address in addressesToRemove)
                firm.Addresses.Remove(address);

            // Identify addresses to associate to this firm.
            var existingAddressIds = firm.Addresses.Select(x => x.ID).ToList();
            var addressesToAdd = addresses.Where(x => !existingAddressIds.Contains(x.ID)).ToList();
            foreach (var address in addressesToAdd)
                firm.Addresses.Add(address);

            firmService.Update(firm);
        }
        else
        {

        }

        return Ok(firm);
}

数据库上下文

     public class Repo<T> : IRepo<T> where T : Entity, new()
        {
            public readonly Db dbContext;

            private ILogger _logger;
            private IQueryable<T> lastQuery { get; set; }
            private bool? _enablelazyloading;
            private IEntityWatcher<T> _watcherNotification;
            private bool _EnableChangeNotification;
            public string ID { get; set; }
            private string _clientId;

            #region Constructors
            public Repo(IDbContextFactory f)
            {
                if (typeof(T).GetCustomAttribute<SeparateDbContext>() != null)
                    dbContext = f.GetContext<T>();
                else
                    dbContext = f.GetContext();
                _logger = IoC.Resolve<ILogger>();
                try
                {
                    _watcherNotification = IoC.Resolve<IEntityWatcher<T>>();
                }
                catch (Exception ex)
                {
                    _logger.Error("Change Notification failed to resolve in Repo.  The Repo will continue to function without notification.", ex);

                }
            }
            public Repo() : this(new DbContextFactory()) { }
            #endregion

            public bool? EnableLazyLoading
            {
                get { return dbContext.EnableLazyLoading; }
                set { dbContext.EnableLazyLoading = value; }
            }

            public void SetClientId(string clientId)
            {
                var oc = dbContext.Database.Connection as OracleConnection;

                if (oc != null)
                {
                    oc.Open();
                    oc.ClientId = clientId;
                    oc.Close();
                }
            }


            public T Update(T obj)
            {
                _logger.Info("Repo.Update {0}", obj);
                var entity = Get(obj.ID);
                var oldEntity = new T();
                var entry = dbContext.Entry(entity);
                oldEntity.InjectFrom(entry.OriginalValues.ToObject());
                if (dbContext.Entry(obj).State == System.Data.Entity.EntityState.Detached)
                {
                    entry.CurrentValues.SetValues(obj);
                }
                    LogAllModifiedEntities(dbContext);
                dbContext.SaveChanges();
                if (_watcherNotification != null)
                    _watcherNotification.EntityChanged(ChangeNotificationType.Modified, entity, oldEntity);
                return Get(obj.ID);
            }


 public void EntityChanged(ChangeNotificationType changeNotificationType, T newEntity, T oldEntity) {
            if(_entityAuditEnabled) {
                var filter = IoC.Resolve<IEntityWatchFilter<T>>();
                filter.Filter(changeNotificationType, newEntity, oldEntity);
            }
        }
    }

   public bool Filter(ChangeNotificationType changeNotificationType, T newEntity, T oldEntity) {
            try {
                ///only 
                if(_WatchList.Contains(typeof(T).Name) || !_WatchList.Any()) {
                    var newLegacyStratImpl = newEntity as ILegacyStrategy;
                    var oldLegacyStratImpl = oldEntity as ILegacyStrategy;
                    var blankStrategies = IoC.Resolve<ICrudService<LEGACY_STRATEGY>>().Where(x => x.NAME.Trim() == "").Select(x => x.ID).AsEnumerable();
                    if(changeNotificationType == ChangeNotificationType.Added && newLegacyStratImpl != null && newLegacyStratImpl.LEGACY_STRATEGY_ID.HasValue && !blankStrategies.Contains(newLegacyStratImpl.LEGACY_STRATEGY_ID.Value)) {

                        _action.Added(newEntity);
                        return true;
                    } else if(changeNotificationType == ChangeNotificationType.Deleted && newLegacyStratImpl != null) {
                        _action.Deleted(newEntity);
                        return true;
                    } else if(changeNotificationType == ChangeNotificationType.Modified && newLegacyStratImpl != null && oldLegacyStratImpl != null) {
                        ///need to go the extra distance and make sure the legacy strategy was changed and not some other property.
                        var hasChanged = newLegacyStratImpl.LEGACY_STRATEGY_ID != oldLegacyStratImpl.LEGACY_STRATEGY_ID;
                        if(hasChanged) {
                            _action.Modified(newEntity, oldEntity);
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
                return false;///all else fails...
            } catch(Exception ex) {
                _logger.Error(ex);
                return false;
            }
        }

        firm.Addresses = model.Firm.Addresses;
        firm.Websites=  model.Firm.Websites;

这...您实际上是在告诉上下文的此实例将您的“模型”提供的地址和网站视为实体。上下文不了解这些实体,因此它对待它们的方式与您执行以下操作时没有什么不同:

foreach(var address in model.Firm.Addresses)
{
   firm.Addresses.Add(new Address { AddressId = address.AddressId, City = address.City, /* ... */ });
}

就上下文而言,这些对象是“新的”。

作为一般规则,避免将实体传递给客户端,并且永远不要信任/接受从客户端返回的实体。如果公司关联现有地址,则 AddressID 列表对于公司更新模型来说绰绰有余。 (假设如果用户创建或更新了地址内容,则该内容将单独保存。)如果用户可以通过固件更新传递新地址,那么您需要一个合适的地址视图模型并检测新的或更新的条目。

上述问题的一个简单明显的解决方案是使用以下方法将实体与上下文关联起来Attach()但我从不推荐这样做,因为它相信该实体没有以意外的方式被修改。 (另外还会引发其他突然出现的边缘情况,例如上下文可能已经具有与该 ID 关联的实体)

当更新子引用(例如地址)时,我们不会将地址内容作为公司更新的一部分进行更新:

var addressIds = model.Firm.Addresses.Select(x => x.AddressId).ToList();
var addresses = dbContext.Addresses.Where(x => addressIds.Contains(x => x.AddressId)).ToList(); // All of the addresses we want to associate to this firm.

// Identify addresses to remove from this firm.
var addressesToRemove = firm.Addresses.Where(x => !addressIds.Contains(x.AddressId)).ToList();
// Identify addresses to associate to this firm.
var addressesToAdd = addresses
        .Except(firm.Addresses, new LamdaComparer((a1,a2) => a1.AddressId == a2.AddressId));

foreach(var address in addressesToRemove)
    firm.Addresses.Remove(address);

if(addressesToAdd.Any())
    firm.Addresses.AddRange(addressesToAdd);

如果您可能要更新地址详细信息,则需要做更多的工作,但问题的关键在于您不能信任传递给客户端并通过模型接收回来的实体。视图模型应该是 POCO 类,而不是实体。为了避免此类问题,应验证从视图传回的任何内容,并应从处理请求的上下文中加载适用的实体。

可以找到 LamdaComparerhere https://stackoverflow.com/questions/6277760/can-i-use-linqs-except-with-a-lambda-expression-comparer.

编辑:如果实现比较器存在问题.. 如果没有 LamdaComparer,您可以执行以下操作:

// Identify addresses to associate to this firm.
var existingAddressIds = firm.Addresses.Select(x => x.AddressId).ToList();
var addressesToAdd = addresses.Where(x => !existingAddressIds.Contains(x.AddressId)).ToList();

编辑 2:存储库类有助于启用单元测试。通用存储库类是邪恶的。如果您不使用单元测试,那么我将避免增加尝试将 EF 功能抽象到存储库(尤其是通用存储库)中的复杂性。在您的情况下,为了避免可能破坏代码的其他区域,我将向您的服务添加一个名为 SaveChanges 的方法,该方法仅调用上下文的 SaveChanges,然后不调用您的 service.Update(enty) 方法,而是调用 service.SaveChanges ()。

尝试在存储库中抽象出 EF 的功能会适得其反。例如,尝试对添加和删除的相关实体进行检查需要了解相关实体,而这不是通用实现所知道的知识。接受 EF 是应用程序的核心部分,与 .Net Framework 是应用程序的核心部分没有什么不同。这使您可以充分利用 EF 的功能,而无需编写代码尝试隐藏诸如排序表达式、分页、归约和映射操作等内容,或者干脆不利用这些功能,因为它们可能会“泄漏”EF 主义。

这并不是说您的项目的 Repo/Context Wrapping 实现不好或错误,但它很复杂,并且导致了难以解释的行为。从您提供的代码中我可以看到,它旨在将实体视为两个独立的角色:模型和模型的独立表示。 IMO 这违反了单一职责,实体应该代表模型,仅此而已。 ViewModel 或 DTO 是将相关信息传输到视图或外部使用者,而不是实体。是的,EF 提供了分离/重新附加以及在实体之间复制值的功能,但我反对将其与已重新用作视图模型的实体一起使用的一个关键点是,从客户端返回的视图模型/DTO 不能被值得信赖。实体公开的信息远多于客户端操作可能希望更新的信息,但如果被调试器拦截,返回的实体可能包含对任何这些值的更改。

也许这是您从其他开发人员那里继承的东西,或者是您根据野外发现的示例构建的东西。复杂性必须服务于非常具体的目的才能证明其存在的合理性。不幸的是,在大多数情况下,它是盲目相信它会解决未来的一些问题或仅仅因为它是一个挑战而添加的。设计模式是作为传达相关概念的一种手段而开发的,但也被视为一种福音all代码应该看起来像。重构、精炼和整合代码对于减少 bug 来说是一件好事,但应该在代码的目标被证明和理解之后再做。否则,这就是过早的优化,并会导致诸如此类的令人头疼的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实体框架未正确修改或删除子项 的相关文章

  • 有没有办法找到 .NET 中嵌入资源的最后修改日期?

    有人知道这样做的方法 在运行时 吗 我不确定我能在描述中详细阐述比标题中已经给出的更多信息 但如果您觉得我错过了某些内容 请说出来 resx 文件本身应该有一个与之关联的修改日期 但您将无法获取该文件中各个资源的修改日期
  • 绑定到布尔值的可见性的 TargetNullValue

    我有一个Grid whose Visibility使用以下方法将属性绑定到某个模型的布尔属性Converter
  • 在解析完成之前遇到流结束?

    我正在尝试反序列化流 但总是收到此错误 解析完成之前遇到流结束 这是代码 Some code here BinaryFormatter b new BinaryFormatter return myObject b Deserialize
  • 如何在不拉动所有对象的情况下编辑或添加到特定字段

    我怎样才能做到这一点 a myFavorits Add 而不将所有对象拉到 vara 因为a有很多数据 我不想全部拉出来a对象 但我找不到方法来做到这一点 我想做 lambda 和 linq 而不返回一些东西 但 linq 总是返回一些东西
  • Response.Redirect() 重定向到子文件夹中的页面

    我正在使用一个Response Redirect login aspx 自从我搬家以来login aspx to my Account子文件夹中 我尝试了以下代码 但它不起作用 Response Redirect Account login
  • C# 是“??”吗?运算符线程安全吗?

    大家都知道这不是线程安全的 public StringBuilder Builder get if builder null builder new StringBuilder return builder 那这个呢 public Stri
  • JIT编译后的代码是如何注入内存并执行的?

    考虑一个典型的 Windows x86 或 AMD64架构 内存为 分为可执行部分 无法写入 和 数据部分 可以写入但不能写入 被处决 想想 DEP JIT 在内存中编译方法 通常 不存储任何东西 磁盘 而是将其移动到哪里 下一条指令指针可
  • 如何防止在 .NET 中的表单初始化时触发值更改事件?

    考虑一个带有几个单选按钮和一个复选框的简单 NET 表单 每个单选按钮都有一个 CheckedChanged 处理程序设置 该处理程序根据复选框的状态执行某些操作 我的问题是 当我初始化要检查的默认单选按钮 从设计器属性窗口 时 会为该单选
  • 查询限制与返回类不同的类

    对于我当前的项目 我正在学习 NHibernate 但在翻译以下查询时遇到问题 select person Firstname person Lastname from Person inner join Contract on contr
  • 为什么 BLToolkit 没有更流行? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 从 SynchronizationContext 派生

    简而言之 我实现了一个从 SynchronizationContext 派生的类 以便 GUI 应用程序可以轻松地使用 GUI 线程以外的线程上引发的事件 我非常感谢对我的实施提出评论 具体来说 有什么是您建议反对的或可能会导致我未预见到的
  • 使用私有构造函数的 C# 单元测试类?

    好吧 我刚刚收到一个作业 我必须对具有私有构造函数的类执行单元测试 现在 当所有方法也都是非静态时 我该如何在不初始化类的情况下进行单元测试 有什么方法可以对具有私有构造函数的类进行单元测试 无需反射 如果您无法将类公开 您仍然可以通过以下
  • 测试从 ComboBox 派生的自定义控件

    我创建了一个从 ComboBox 派生的控件 并希望对其行为进行单元测试 但是 它在我的单元测试中的行为似乎与实际应用程序中的行为不同 在实际应用程序中 Combobox DataSource 属性和 Items 同步 换句话说 当我更改
  • 持续运行的 C# 代码 - 服务还是单独的线程?

    我有一个 NET 4 Web 应用程序 它有 3 个关联的独立项目 DAL BAL 和 UI 我正在使用实体框架进行数据库交互 我有代码循环遍历一堆数据库数据 根据找到的内容调用方法 然后更新数据库 我希望这段代码一直运行 同时 我希望用户
  • 如何在 WCF 中反序列化自定义 SOAP 标头?

    我正在尝试向通过 WCF 的所有 SOAP 请求添加自定义标头 我发现这篇精彩的文章 http blogs msdn com b mohamedg archive 2012 10 21 adding custom soap headers
  • 是否有理由为什么用 XmlInclude 修饰的基类在序列化时仍然会抛出类型未知的异常?

    我将简化代码以节省空间 但所提供的内容确实说明了核心问题 我有一个类 它的属性是基类型 有 3 个派生类可以分配给该属性 如果我将任何派生类分配给容器并尝试序列化容器 XmlSerializer 会抛出可怕的错误 类型 x 不是预期的 使用
  • 实体框架中的导航属性是什么

    我是实体框架的新手 当Visual Studio创建模型图时我们主要可以看到Entities Propertie和Navigation Properties这两个东西 那么这些Navigation Properties是什么 如何使用它们
  • 如何从另一个线程阻止 UI 线程或强制表单在 UI 线程内运行

    我的应用程序的一个要求是 如果它失去数据库连接 那么它必须弹出一个大模式 无连接 稍后重试 对话框 阻止所有用户交互 直到重新获得连接为止 我通过在应用程序启动时启动 DeviceMonitor 类的实例来实现此目的 该类创建一个 Syst
  • 合并大文件的最佳方法是什么?

    我必须合并数千个大文件 每个大约 200MB 我想知道合并这些文件的最佳方法是什么 行将有条件地复制到合并文件中 可以使用 File AppendAllLines 或使用 Stream CopyTo 吗 使用 File AppendAllL
  • 使用实体框架实现 if-not-exists-insert,无需竞争条件

    使用 LINQ to Entities 4 0 是否有正确的模式或构造来安全地实现 如果不存在则插入 例如 我目前有一个跟踪 用户收藏夹 的表 用户可以在其收藏夹列表中添加或删除文章 基础表不是真正的多对多关系 而是跟踪一些附加信息 例如添

随机推荐