为什么实体框架没有将我的集合加载到基类中

本文关键字:加载 集合 基类 我的 实体 框架 为什么 | 更新日期: 2023-09-27 18:27:31

我有一个从DynamicObject继承的基对象,我将其用作实体POCO的基类,这允许POCO像ExpandoObject一样工作(在引擎盖下有一个类似Dictionary<string,object>的对象)。我将该字典映射到与edm兼容的Key/Value类,如下所示:

public class EntityPair<TKey, TValue>
{
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }
   public TKey Key { get; set; }
   public TValue Value { get; set; }
   public string TableName { get; set; }
   public int FK { get; set; }
}

并且为我们需要存储的每个系统类型都有强类型继承类,即

public class EntityPairString: EntityPair<string,string> { }

而基expando有一个公共属性,它将获得一个类型(如字符串)的所有动态属性作为其对应的键值类型。例如,对于字符串属性,它将返回一个EntityPairString对象的集合。

现在我遇到的问题是,它没有加载数据库的类型(如字符串集合)的集合-我可以很好地存储它。但当我检索它时,我无法获得字符串KVPair集合。奇怪的是,EF说它知道关系,并加载了集合,但它没有设置集合——即使我渴望或明确地加载它。

实体:

public class Order : Expando
{
    public Order()
    {
        TableName = "Order";
    }
    public short OrderType { get; set; }
    public int InitiatorId { get; set; }
    public string InitiatorName { get; set; }
    [...]
    public class OrderConfiguration : EntityTypeConfiguration<Order>
    {
        public OrderConfiguration()
        {
            Map(c => c.MapInheritedProperties());
            HasMany(c => c.StringExpando)
                     .WithOptional()
                     .HasForeignKey(c => c.FK)
                     .WillCascadeOnDelete(true);
        }
    }
}

它继承的Expando:

[Serializable]
public class Expando : DynamicObject, IDynamicMetaObjectProvider
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    protected string TableName { get; set; }
    [...]
    public virtual ICollection<EntityPairString> StringExpando
    {
        get
        {
            var values = ((IEnumerable<EntityPair<string, object>>)Properties)
                .Where(c => c.Value is string)
                .Select(d => new EntityPairString()
                {
                    Key = d.Key,
                    Value = d.Value as string,
                    Id = d.Id,
                    FK = Id,
                    TableName = TableName
                }).ToList();
            return values;
        }
        set
        {
            if (Properties == null) Properties = new PropertyBag();
            foreach (var i in value)
            {
                var p = (EntityPair<string, string>)i;
                Properties.Add(new EntityPair<string, object>
                {
                    Id = i.Id,
                    Key = i.Key,
                    Value = i.Value,
                    FK = Id,
                    TableName = TableName
                });
            }
        }
    }
}

其他一切都在工作,就像我说的,我可以很好地向数据库添加值,但没有发生的是从数据库中加载回属性。Order对象返回良好,但不会重新加载字符串expando属性。

注意:虽然我在Order.OrderConfiguration中的实体映射表明它是外键,但我从显式数据库迁移中删除了它。在运行时,该关系被编入索引,并且仍然列在EF的内部。我打电话给验证了这一点

var relationshipManager = ((IObjectContextAdapter) ctx).ObjectContext
           .ObjectStateManager.GetRelationshipManager(order);
        var relations = relationshipManager.GetAllRelatedEnds();

简单地说,我的问题是,当我加载任何或所有Order实体时,为什么我的EntityPairString集合没有被EF附加到Order对象?

彻底性:

显式迁移的相关部分:

CreateTable(
    "dbo.expando_string",
     c => new
     {
         Id = c.Int(nullable: false, identity: true),
         TableName = c.String(nullable: false, maxLength: 128),
         FK = c.Int(nullable: false),
         Key = c.String(),
         Value = c.String(),
     })
     .PrimaryKey(t => new { t.Id, t.TableName, t.FK })
     .Index(t => t.FK);

更新

就在调试过程中,我决定确认它生成了正确的查询,所以我做了一个SQL配置文件,它运行的查询是:

exec sp_executesql N'SELECT 
[Project2].[Id] AS [Id], 
[Project2].[OrderType] AS [OrderType], 
[Project2].[InitiatorId] AS [InitiatorId], 
[Project2].[InitiatorName] AS [InitiatorName], 
...
[Project2].[C1] AS [C1], 
[Project2].[Id1] AS [Id1], 
[Project2].[TableName] AS [TableName], 
[Project2].[FK] AS [FK], 
[Project2].[Key] AS [Key], 
[Project2].[Value] AS [Value]
FROM ( SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[OrderType] AS [OrderType], 
    [Limit1].[InitiatorId] AS [InitiatorId], 
    [Limit1].[InitiatorName] AS [InitiatorName], 
    ... 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[TableName] AS [TableName], 
    [Extent2].[FK] AS [FK], 
    [Extent2].[Key] AS [Key], 
    [Extent2].[Value] AS [Value], 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   (SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[OrderType] AS [OrderType], 
        [Extent1].[InitiatorId] AS [InitiatorId], 
        [Extent1].[InitiatorName] AS [InitiatorName], 
        ...
        FROM [dbo].[Orders] AS [Extent1]
        WHERE [Extent1].[Id] = @p__linq__0 ) AS [Limit1]
    LEFT OUTER JOIN [dbo].[expando_string] AS [Extent2] ON [Limit1].[Id] = [Extent2].[FK]
)  AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC',N'@p__linq__0 int',@p__linq__0=1

它在执行时确实返回了正确的结果集,所以在EF C#代码的某个地方,它只是没有附加实体。

为什么实体框架没有将我的集合加载到基类中

我发现了问题所在-EF不设置集合,它只添加或删除集合,如果getter返回null,它将初始化集合,但它仍然会添加到刚刚初始化的集合中(这就是为什么你可以在它惰性加载集合的其余部分之前添加一个项目)

所以问题在于:

public virtual ICollection<EntityPairString> StringExpando
{
    get
    {
        var values = ((IEnumerable<EntityPair<string, object>>)Properties)
            .Where(c => c.Value is string)
            .Select(d => new EntityPairString()
            {
                Key = d.Key,
                Value = d.Value as string,
                Id = d.Id,
                FK = Id,
                TableName = TableName
            }).ToList();
        return values;
    }
    set
    {
        if (Properties == null) Properties = new PropertyBag();
        foreach (var i in value)
        {
            var p = (EntityPair<string, string>)i;
            Properties.Add(new EntityPair<string, object>
            {
                Id = i.Id,
                Key = i.Key,
                Value = i.Value,
                FK = Id,
                TableName = TableName
            });
        }
    }
}

我不得不进行一些重组,但我最终通过实际归还藏品使其发挥作用,所以当它添加到藏品中时,我会意识到这一点。

简单地说,这对我来说是一个愚蠢的错误,EF实际上已经为我设置了值,但它将它们设置在了一个我无法处理的列表中。

EF用于保存和获取集合的最后一种方法是什么:

    public ICollection<ExpandoString> StringExpando
    {
        get
        {
            // ReSharper disable ForCanBeConvertedToForeach
            for (var i = 0; i < _strings.Count; i++)
            // ReSharper restore ForCanBeConvertedToForeach
            {
                _strings[i].FK = Id;
                _strings[i].TableName = TableName;
            }
            //using for as we don't want to enumerate the bag
            return _strings.List;
        }
    }

使用可扩展对象作为实体的唯一警告是,您必须明确地将所有关系加载到可扩展实体上,但是,它们将很轻,因此不应该破坏交易。