15

我一直在用XMLs 来表示实体框架。我试图创建一种可以在运行时注入属性的实体,首先我创建DynamicEntity了动态对象

public class DynamicEntity : DynamicObject
{
    Dictionary<string, object> dynamicMembers = new Dictionary<string, object>();

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dynamicMembers[binder.Name] = value;
        return true;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (dynamicMembers.TryGetValue(binder.Name, out result))
        {
            return dynamicMembers.TryGetValue(binder.Name, out result);
        }

        result = "";
        return true;
    }
}

然后实体继承自此

public partial class QUOTE_HOUSE : DynamicEntity

(当我从数据库获取数据后手动设置属性时,它似乎确实有效)。

因此,基于这种删除属性的机制,我尝试做另一个将属性插入 XML 的机制,并且整个事情似乎都还不错(至少它不会像 XML 不正确时通常那样做映射var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()});)。

执行查询时出现问题是 EF

实体类型 QUOTE_HOUSE 不是当前上下文模型的一部分。

说明:执行当前 Web 请求期间发生未处理的异常。请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。

异常详细信息:System.InvalidOperationException:实体类型 QUOTE_HOUSE 不是当前上下文模型的一部分。

[InvalidOperationException:实体类型 QUOTE_HOUSE 不是当前上下文模型的一部分。]
System.Data.Entity.Internal.InternalContext.UpdateEntitySetMappingsForType(Type entityType) +208
System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType ) +50

TryUpdateEntitySetMappingsForType我在System.Data.Entity.Internal.InternalContext加载 pdb 后追踪到EF

基本上我发生的事情QUOTE_HOUSE不在试图从this._workspace.GetItemCollection(DataSpace.OSpace)哪里映射它。UpdateEntitySetMappings

它检查它是否存在this._entitySetMappingsCache.ContainsKey(entityType)),因为它不是它然后尝试更新映射迭代this._workspace.GetItemCollection(DataSpace.OSpace)我的项目不存在的位置

但是我可以看到我的实体确实存在于this._workspace.GetItems<EntityContainer>(DataSpace.CSpace).

完整UpdateEntitySetMappings外观如下:

private void UpdateEntitySetMappings()
{
  ObjectItemCollection objectItemCollection = (ObjectItemCollection) this._workspace.GetItemCollection(DataSpace.OSpace);
  ReadOnlyCollection<EntityType> items = this._workspace.GetItems<EntityType>(DataSpace.OSpace);
  Stack<EntityType> entityTypeStack = new Stack<EntityType>();
  foreach (EntityType entityType1 in items)
  {
    entityTypeStack.Clear();
    EntityType cspaceType = (EntityType) this._workspace.GetEdmSpaceType((StructuralType) entityType1);
    do
    {
      entityTypeStack.Push(cspaceType);
      cspaceType = (EntityType) cspaceType.BaseType;
    }
    while (cspaceType != null);
    EntitySet entitySet = (EntitySet) null;
    while (entitySet == null && entityTypeStack.Count > 0)
    {
      cspaceType = entityTypeStack.Pop();
      foreach (EntityContainer entityContainer in this._workspace.GetItems<EntityContainer>(DataSpace.CSpace))
      {
        List<EntitySetBase> list = entityContainer.BaseEntitySets.Where<EntitySetBase>((Func<EntitySetBase, bool>) (s => s.ElementType == cspaceType)).ToList<EntitySetBase>();
        int count = list.Count;
        if (count > 1 || count == 1 && entitySet != null)
          throw Error.DbContext_MESTNotSupported();
        if (count == 1)
          entitySet = (EntitySet) list[0];
      }
    }
    if (entitySet != null)
    {
      EntityType entityType2 = (EntityType) this._workspace.GetObjectSpaceType((StructuralType) cspaceType);
      Type clrType1 = objectItemCollection.GetClrType((StructuralType) entityType1);
      Type clrType2 = objectItemCollection.GetClrType((StructuralType) entityType2);
      this._entitySetMappingsCache[clrType1] = new EntitySetTypePair(entitySet, clrType2);
    }
  }
}

实体如何进入 this._workspace.GetItemCollection(DataSpace.OSpace)?为什么实体会在CSpace而不是在OSpace

编辑: 对于那些可能想要破解赏金的人,以下是您可能需要设置环境以重现问题的组件。

public class SystemToDatabaseMapping
{
    public SystemToDatabaseMapping(string system, string databaseType, string database, string connectionString, Type enitityType)
    {
        System = system;
        Database = database;
        DatabaseType = databaseType;
        ConnectionString = connectionString;
        EntityType = enitityType;
    }

    public Type EntityType { get; set; }
    public string System { get; set; }
    public string Database { get; set; }
    public string DatabaseType { get; set; }
    public string ConnectionString { get; set; }
    public List<ColumnToModify> ColumnsToModify  { get; set; }
}

public abstract class ColumnToModify
{
    protected ColumnToModify(string table, string column)
    {
        Table = table;
        Column = column;
    }

    public string Table { get; set; }
    public string Column { get; set; }

    public abstract bool IsRemove{ get; }
}

public class ColumnToRemove : ColumnToModify
{
    public ColumnToRemove(string table, string column) : base(table, column)
    {
    }

    public override bool IsRemove
    {
        get { return true; }
    }
}

public class ColumnToAdd : ColumnToModify
{
    public ColumnToAdd(string table, string column, Type type) : base(table, column)
    {
        this.Type = type;
    }

    public override bool IsRemove
    {
        get { return false; }
    }

    public Type Type { get; set; }
}

首先从 db 生成的实体,(DynamicEntity代码在上面)

public partial class QUOTE_HOUSE : DynamicEntity
{
    public long UNIQUE_ID { get; set; }
}

数据库的 DbContext 需要构造函数重载

 public partial class EcomEntities : DbContext
 {

    public EcomEntities(DbConnection connectionString)
        : base(connectionString, false)
    {
    }

    public virtual DbSet<QUOTE_HOUSE > QUOTE_HOUSE { get; set; }
....
}

进行列注入的机制(它是一个粗略的原型,所以请原谅它看起来有多糟糕),当注入尝试字符串列时,我知道它映射正常。

public static class EntityConnectionExtensions
{
    public static IEnumerable<XElement> ElementsAnyNS<T>(this IEnumerable<T> source, string localName)
        where T : XContainer
    {
        return source.Elements().Where(e => e.Name.LocalName == localName);
    }

    public static IEnumerable<XElement> ElementsAnyNS(this XContainer source, string localName)
    {
        return source.Elements().Where(e => e.Name.LocalName == localName);
    }

    private static void ModifyNodes(XElement element, List<ColumnToModify> tableAndColumn)
    {
        if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) ||
            element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("StoreEntitySet").Value))
        {
            var matchingRemoveSelectParts = tableAndColumn.Where(oo => oo.IsRemove && element.Value.Contains(string.Format("\"{0}\".\"{1}\" AS \"{1}\"", oo.Table, oo.Column))).ToList();

            if (matchingRemoveSelectParts.Any())
            {
                foreach (var matchingRemoveSelectPart in matchingRemoveSelectParts)
                {
                    var definingQuery = element.ElementsAnyNS("DefiningQuery").Single();
                    definingQuery.Value = definingQuery.Value.Replace(string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"", matchingRemoveSelectPart.Table, matchingRemoveSelectPart.Column), "");
                }
            }
            else
            {
                var nodesToRemove = element.Nodes()
                    .Where(o =>
                        o is XElement
                        && ((XElement) o).Attribute("Name") != null
                        && tableAndColumn.Any(oo => oo.IsRemove && ((XElement) o).Attribute("Name").Value == oo.Column));

                foreach (var node in nodesToRemove.ToList())
                {
                    node.Remove();
                }

                if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value))
                {
                    var elementsToAdd = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value);
                    if (new[] {"Type=\"number\"", "Type=\"varchar2\"", "Type=\"date\""}.Any(o => element.ToString().Contains(o)))
                    {
                        foreach (var columnToModify in elementsToAdd)
                        {
                            var columnToAdd = (ColumnToAdd) columnToModify;

                            var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type)
                                ? "number"
                                : columnToAdd.Type == typeof (DateTime) ? "date" : "varchar2";

                            var precision = "";
                            var scale = "";
                            var maxLength = "";
                            if (type == "number")
                            {
                                precision = "38";
                                scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0";
                            }

                            if (type == "varchar2")
                            {
                                maxLength = "500";
                            }

                            var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type));
                            if (!string.IsNullOrWhiteSpace(precision))
                            {
                                newProperty.Add(new XAttribute("Precision", precision));
                            }

                            if (!string.IsNullOrWhiteSpace(scale))
                            {
                                newProperty.Add(new XAttribute("Scale", scale));
                            }

                            if (!string.IsNullOrWhiteSpace(maxLength))
                            {
                                newProperty.Add(new XAttribute("MaxLength", maxLength));
                            }

                            element.Add(newProperty);
                        }
                    }
                    else if (
                        new[] {"Type=\"Decimal\"", "Type=\"String\"", "Type=\"DateTime\"", "Type=\"Boolean\"", "Type=\"Byte\"", "Type=\"Int16\"", "Type=\"Int32\"", "Type=\"Int64\""}.Any(
                            o => element.ToString().Contains(o)))
                    {
                        foreach (var columnToModify in elementsToAdd)
                        {
                            var columnToAdd = (ColumnToAdd) columnToModify;

                            var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type)
                                ? "Decimal"
                                : columnToAdd.Type == typeof (DateTime) ? "DateTime" : "String";

                            var precision = "";
                            var scale = "";
                            var maxLength = "";
                            if (type == "Decimal")
                            {
                                precision = "38";
                                scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0";
                            }

                            if (type == "String")
                            {
                                maxLength = "500";
                            }

                            var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type));
                            if (!string.IsNullOrWhiteSpace(precision))
                            {
                                newProperty.Add(new XAttribute("Precision", precision));
                            }

                            if (!string.IsNullOrWhiteSpace(scale))
                            {
                                newProperty.Add(new XAttribute("Scale", scale));
                            }

                            if (!string.IsNullOrWhiteSpace(maxLength))
                            {
                                newProperty.Add(new XAttribute("MaxLength", maxLength));
                                newProperty.Add(new XAttribute("FixedLength", "false"));
                                newProperty.Add(new XAttribute("Unicode", "false"));
                            }

                            element.Add(newProperty);
                        }
                    }
                }
            }

            if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) && element.GetNamespaceOfPrefix("store") != null &&
                element.Attribute(element.GetNamespaceOfPrefix("store") + "Type") != null &&
                element.Attribute(element.GetNamespaceOfPrefix("store") + "Type").Value == "Tables")
            {
                var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value);
                foreach (var matchingAddSelectPart in matchingAddSelectParts)
                {
                    var definingQuery = element.ElementsAnyNS("DefiningQuery").Single();
                    var schemaRegex = new Regex(string.Format("\\nFROM \\\"([a-zA-Z0-9]*)\\\".\\\"{0}\\\"", matchingAddSelectPart.Table));
                    var schema = schemaRegex.Matches(definingQuery.Value)[0].Groups[1].Value;
                    definingQuery.Value = definingQuery.Value.Replace(
                        string.Format("\nFROM \"{0}\".\"{1}\" \"{1}\"", schema, matchingAddSelectPart.Table),
                        string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"\nFROM \"{2}\".\"{0}\" \"{0}\"", matchingAddSelectPart.Table, matchingAddSelectPart.Column, schema));
                }
            }

            if (element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => !oo.IsRemove && oo.Table == element.Attribute("StoreEntitySet").Value))
            {
                var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("StoreEntitySet").Value);
                foreach (var matchingAddSelectPart in matchingAddSelectParts)
                {
                    element.Add(new XElement(element.GetDefaultNamespace() + "ScalarProperty", new XAttribute("Name", matchingAddSelectPart.Column),
                        new XAttribute("ColumnName", matchingAddSelectPart.Column)));
                }
            }
        }
    }

    public static EntityConnection Create(List<ColumnToModify> tablesAndColumns, string connString)
    {
        var modelNameRegex = new Regex(@".*metadata=res:\/\/\*\/([a-zA-Z.]*).csdl|.*");
        var model = modelNameRegex.Matches(connString).Cast<Match>().SelectMany(o => o.Groups.Cast<Group>().Skip(1).Where(oo => oo.Value != "")).Select(o => o.Value).First();

        var conceptualReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".csdl"));
        var mappingReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".msl"));
        var storageReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".ssdl"));

        var conceptualXml = XElement.Load(conceptualReader);
        var mappingXml = XElement.Load(mappingReader);
        var storageXml = XElement.Load(storageReader);

        foreach (var entitySet in new[] {storageXml, conceptualXml}.SelectMany(xml => xml.Elements()))
        {
            if (entitySet.Attribute("Name").Value == "ModelStoreContainer")
            {
                foreach (var entityContainerEntitySet in entitySet.Elements())
                {
                    ModifyNodes(entityContainerEntitySet, tablesAndColumns);
                }
            }

            ModifyNodes(entitySet, tablesAndColumns);
        }

        foreach (var entitySet in mappingXml.Elements().ElementAt(0).Elements())
        {
            if (entitySet.Name.LocalName == "EntitySetMapping")
            {
                foreach (var entityContainerEntitySet in entitySet.Elements().First().Elements())
                {
                    ModifyNodes(entityContainerEntitySet, tablesAndColumns);
                }
            }

            ModifyNodes(entitySet, tablesAndColumns);
        }

        var storageCollection = new StoreItemCollection(new [] {storageXml.CreateReader()});
        var conceptualCollection = new EdmItemCollection(new[] { conceptualXml.CreateReader() });
        var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()});

        var workspace = new MetadataWorkspace();

        workspace.RegisterItemCollection(conceptualCollection);
        workspace.RegisterItemCollection(storageCollection);
        workspace.RegisterItemCollection(mappingCollection);
        var connectionData = new EntityConnectionStringBuilder(connString);
        var connection = DbProviderFactories
            .GetFactory(connectionData.Provider)
            .CreateConnection();
        connection.ConnectionString = connectionData.ProviderConnectionString;

        return new EntityConnection(workspace, connection);
    }
}

初始化:

public ActionResult QUOTE_HOUSE()
    {
        var onlineDocs = Enumerable.Empty<QUOTE_HOUSE>();
        var mappings = new List<SagaSystemToDatabaseMapping>{new SagaSystemToDatabaseMapping("x", "Oracle", "Db1",
                   "metadata=res://*/Ecom.Ecom.csdl|res://*/Ecom.Ecom.ssdl|res://*/Ecom.Ecom.msl;provider=Oracle.ManagedDataAccess.Client;provider connection string='...'", typeof(EcomEntities))
                {
                    ColumnsToModify = new List<ColumnToModify> { new ColumnToAdd("QUOTE_HOUSE","TESTCOL", typeof(string))    }
                }};
        var entityConnection = EntityConnectionExtensions.Create(mappings[0].ColumnsToModify,mappings[0].ConnectionString);
        using (var db = new EcomEntities(entityConnection))
        {
            onlineDocs = db.QUOTE_HOUSE.Take(10);
        }

        return View("QUOTE_HOUSE", onlineDocs.ToList());
    }

您应该能够从实体生成 oracle 数据库QUOTE_HOUSE并输入一些虚拟值,不要认为您需要视图,因为它会爆炸.ToList()。生成数据库后,将附加列添加到数据库而不是模型(alter table QUOTE_HOUSE add TESTCOL Varchar2(20)) - 使数据库中的列在运行时注入模型中。您可能还需要在此处调试 EF 程序集,这是如何进行的。如果您需要更多信息或我遗漏了什么,请告诉我。

4

1 回答 1

9

我知道这可能不是您所期望的,但我想至少它会帮助您不要在那个方向上浪费更多时间。

好消息是问题不是由您的“hackish”代码引起的。事实上,我能够在不使用该代码的情况下重现该问题。刚刚创建了一个QUOTE_HOUSE包含 a 的表TestCol,将其导入到新的 edmx 上下文中,然后TestCol从实体类中删除了生成的属性。然后我让类继承了DynamicEntity,使用默认构造函数创建了一个上下文,调用context.QUOTE_HOUSE.ToList()它并以完全相同的异常发生。

坏消息是你想要达到的目标是不可能的。EF 仅使用反射来映射“对象空间”成员。它不提供任何类型扩展机制TypeDescriptor,例如动态运行时(它甚至不允许您投影到动态对象)。后者是可以理解的,因为每个动态对象可能有不同的属性,并且不存在动态类型之类的东西。请注意,在运行时“删除”列的技巧有效,因为它的作用与首先在代码中使用基本相同NotMapped,即该属性确实存在,但被 EF 忽略。

如果您对实体为何在 inCSpace但不在 in感兴趣,答案包含在名为(inside namespace) - sourceOSpace的内部类中。有一个名为 的方法,它调用并且如果它返回,则不添加类型。依次调用,传递使用反射提取的列表,如果无法将任何成员映射到对象属性,则后者返回,从而有效地防止类型被添加到类型集合中。 OSpaceTypeFactorySystem.Data.Entity.Core.Metadata.EdmTryCreateStructuralTypeTryCreateMembersfalseTryCreateMembersTryFindAndCreatePrimitivePropertiesPropertyInfofalse CSpaceOSpaceOSpace

希望至少能满足你的好奇心 :) 但是,不幸的是,在运行时向 EF 实体“添加”属性是一个死主意。

于 2016-07-02T18:15:15.897 回答