c# - 为什么 Entity Framework 没有将我的集合加载到基类中?

标签 c# entity-framework dynamic entity-framework-6

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

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 有一个公共(public)属性,它将获取一个类型(如字符串)的所有动态属性,因为它是对应的键值类型。例如,对于字符串属性,它将返回 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();

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

为了彻底:

显式迁移的相关部分:

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 用来保存和获取集合的 final方法是什么样的:

    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;
        }
    }

将可扩展对象用作实体的唯一注意事项是您必须显式加载可扩展实体上的所有关系,但是它们很轻,所以它不应该成为交易破坏者。

关于c# - 为什么 Entity Framework 没有将我的集合加载到基类中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26513913/

相关文章:

asp.net-mvc - Linq查询,如何orderby

c# - StoreGeneratedPattern 不适用于 DevArt Entity Framework ?

java - java 重载的奇怪行为

css - float div 不会扩展以适应动态内容

c# - 使用 C# 或第 3 方库发出传真?

c# - Net Core 错误名称 'Ok' 在当前上下文中不存在

c# - 发送带有送达报告的短信

c# - Bouncy CaSTLe 是否保护内存/页面文件中的 "leaking"和其他攻击途径的 secret ?

c# - 关于更新断开实体图的思考

ruby-on-rails - 在 Rails 中设置静态路由