首页 > 解决方案 > EFCore - 键值从 C.Link 更改为 B.Link 在作为 A.Link 的公共抽象基类中实现

问题描述

为了使用 RSS 和 Atom 联合格式,对于在 Xamarin.Forms 应用程序中使用 .NET 依赖注入的应用程序来说是理想的,我设法实现了一个用于处理 RSS 和 Atom XML 的服务框架。我目前正在研究数据存储功能。

在代码库的摘录中,我实现了以下内容,用于将 RssChannel 和 RssItem 对象直接添加到数据库中。通过对 Rss 内容的数据库适配器类的静态方法的一些运行时反射,此代码最终在OnModelCreating(ModelBuilder builder)

builder.Entity<RssChannel>().HasKey(channel => channel.RssLink);
builder.Entity<RssChannel>().HasMany(channel => channel.Items).WithOne(item => item.Channel);
builder.Entity<RssItem>().HasKey(item => new { item.RssLink, item.ObjectDateString });

RssChannel并且RssItem在这里被实现为派生自抽象基类RssSyndicationObject

出于 XML 反序列化的目的,此类RssSyndicationObject为属性提供了实现和 XML 注释支持RssLink。然后,该属性及其 XML 注释将被继承到每个实现类,RssChannel并且RssItem.

RssItem然后用属性 定义Channel,这样就提供了指向RssChannel发布. 的反向链接RssItem

在模型构建器中支持这么多的工作时,我在代码库的实现下遇到了以下消息。{RssItem.RssLink, RssItem.ObjectDateString}它似乎表明正在修改基于集合的键值,以便RssItem.RssLink随后将 替换RssChannel.RssLinkRssChannel每个人的 的值RssItem

      Context 'SqliteStorageContext' started tracking 'RssChannel' entity with key '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'SqliteStorageContext' started tracking 'RssChannelImage' entity with key '{Uri: https://media.npr.org/images/podcasts/primary/npr_generic_image_300.jpg?s=200}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10803]
      The foreign key property 'RssItem.RssLink' was detected as changed from 'https://www.npr.org/2021/01/25/960354929/in-tiny-kansas-town-pandemic-skeptics-abound-amid-false-information-and-politics' to 'https://www.npr.org/templates/story/story.php?storyId=2' for entity with key '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2, ObjectDateString: Mon, 25 Jan 2021 13:49:00 GMT}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'SqliteStorageContext' started tracking 'RssItem' entity with key '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2, ObjectDateString: Mon, 25 Jan 2021 13:49:00 GMT}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10803]
      The foreign key property 'RssItem.RssLink' was detected as changed from 'https://www.npr.org/2021/01/25/960465917/israel-secures-covid-19-vaccine-doses-by-agreeing-to-share-medical-data-on-israe' to 'https://www.npr.org/templates/story/story.php?storyId=2' for entity with key '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2, ObjectDateString: Mon, 25 Jan 2021 13:16:18 GMT}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'SqliteStorageContext' started tracking 'RssItem' entity with key '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2, ObjectDateString: Mon, 25 Jan 2021 13:16:18 GMT}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10803]

那么,这就变成了一个错误。

fail: TheNamespace.SyndicationDataManagerService[0]
      Received exception for https://feeds.npr.org/2/rss.xml : OtherNamespace.ProviderFailureException: General error
       ---> System.InvalidOperationException: The instance of entity type 'RssItem' cannot be tracked because another instance with the key value '{RssLink: https://www.npr.org/templates/story/story.php?storyId=2, ObjectDateString: Mon, 25 Jan 2021 13:16:00 GMT}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)

该密钥中使用的 URL 来自RssChannel.RssLink,而不是原始RssItem.RssLink。这一点,以及在运行时更改密钥的问题 - 有没有办法在不修改 和 之一或两者的实现的情况下解决这个RssChannel问题RssItem

无论是什么可能导致“检测为已更改”消息中指示的更改,我都不相信它来自我自己的代码。

我希望能够将“RssLink”属性的共享实现保留在RssSyndicationObject. 虽然我相信我已经找到了一个解决方法,在这些实现类中分别实现每个“链接”属性,但是必须有一种方法可以在模型构建器下使用流利的 API 来解决这个问题?

任何意见,将不胜感激。我查看了 EFCore 和传统实体框架实现的文档。我发现解决它的唯一方法——当直接在数据库中存储来自这些 XML 记录类的值时——是更改这些 XML 记录类的实现。

虽然我计划实现一个适配器框架,但在以后对此应用程序的一些测试中,我不相信我已经看到了这个问题的最后一个。

更新

使用以下作为RssChannel- 而不是仅 的RssLink的键RssChannel,如上所述 - 现在似乎没有发生问题。至少它可能代表一种解决方法?

builder.Entity<RssChannel>().HasKey(channel => new { channel.RssLink, channel.RssTitle });

尽管如此,这可能无法解决问题发生的原因 - 例如一个问题,为什么RssItem在运行时修改了键值?这样在密钥已经初始化之后,它将被更改为使用RssLink项目的包含通道。查看代码,我无法想象RssItem当仅将通道RssLink用作 each 的单项键时,这将如何成为 any 键的结果RssChannel

类似基类的问题RssChannelRssItem派生只是一个猜测。

至少,这里可能有一种猜测,作为一种解决方法,而不修改 XML 数据类的实现?

更多详细信息

代码库的摘录,在其目前的重构状态下。

public abstract class SyndicationObject
{
        [XmlIgnore]
        public string? ObjectTitle { get; set; }

        [XmlIgnore]
        public string? ObjectDateString { get; set; } 
        // ObjectDateString definition truncated. 
        // this property accesses a naïve ObjectDate property of type DateTime?
}

public abstract class RssSyndicationObject : SyndicationObject 
{
        [XmlElement("link", Namespace = RssConstants.NsUriRss)]
        public string? RssLink { get; set; }

        [XmlElement("title", Namespace = RssConstants.NsUriRss)]
        public string? RssTitle { get => ObjectTitle; set => ObjectTitle = value; } 
}


public class RssItem : RssSyndicationObject
{
        protected RssChannel? channel;

        [XmlIgnore]
        public RssChannel? Channel { get => channel; set => channel = value; }
}


public class RssChannel : RssSyndicationObject
{
        [XmlElement("item", Namespace = RssConstants.NsUriRss)]
        public ItemsList<RssChannel, RssItem>? Items [...] 
        // Items property definition truncated for brevity.
        // This property sets the containing RssChannel on each RssItem
        // then stores the provided ItemsList<RssChannel,RssItem> under 
        // a protected field of the same type. 
        //
        // Later item-> channel registration is provided in the
        // ItemsList<T1,T2>.Add(...) implementation, such that may
        // appear to be used under XML deserialization for this property
}


public class ItemsList<TChannel, TItem> : List<TItem> 
        where TChannel : RssChannel
        where TItem : RssItem
{

        private TChannel rssChannel;

        public ItemsList(TChannel rssChannel) : base()
        {
            this.rssChannel = rssChannel;
        }

        new public void Add(TItem item)
        {
            base.Add(item);
            item.Channel = rssChannel;
        }
}

尽管与此问题无关,但我一直在使用 RssItem[]?通道项目集的存储类型。就 XML 反序列化而言,这成功了。但是,数据库适配器无法处理此属性。虽然ItemsList<..>实现(在此处介绍的修订版中)可能很幼稚,但它不提供排序或其他功能,但它至少在数据存储测试期间发挥了作用。

在对这个问题的一些早期重构中,在尝试重新设计RssItemRssChannel链接属性时,当我定义 egstring? RssItem.ItemLinkstring? RssChannel.ChannelLink属性每个都存储在类的本地字段中时,这个问题已经停止发生,而不是使用RssLink来自的公共属性RssSyndicationObject,在这两个实现类中。

标签: c#entity-framework-core

解决方案


推荐阅读