how to map back to get the actual sql table column name apart from model in MVC?

前端 未结 2 865
面向向阳花
面向向阳花 2020-12-12 06:18

I am mapping my table column name to generic way in to use them in model. like:

        public UserEntityMapper()
        {
            ToTable(\"tbl_User\")         


        
相关标签:
2条回答
  • 2020-12-12 06:45

    Reading the mappings

    I've seen similar questions before and several less than satisfying solutions based on splitting the SQL string generated by a DbSet. Attempts to access the mapping metadata are blocked by unaccessible internal types. It kept (and keeps) bugging me that this information is so hidden. Atter all, it's your own mapping.

    So I did some fooling around to see if something can be improved here. Since everything can easily be read from an edmx file, that's where I started. And finally got there. It the following code, the edmx file is in an XDocument called doc. I'll explain the code below.

    var xn = XName.Get("Mappings", doc.Root.GetDefaultNamespace().ToString());
    var mappingsNode = doc.Descendants(xn).Descendants().First();
    var mappingDoc = XDocument.Parse(mappingsNode.ToString());
    xn = XName.Get("MappingFragment", mappingDoc.Root
                                                .GetDefaultNamespace().ToString());
    var mappings = mappingDoc
                .Descendants(xn)
                .Select(x => new
                    {
                        Entity = x.Attribute("StoreEntitySet").Value,
                        Mapping = x.Descendants()
                                    .Select(d => new 
                                                { 
                                                  Property = d.Attribute("Name")
                                                              .Value,
                                                  Column = d.Attribute("ColumnName")
                                                            .Value
                                                })
                    });
    

    Sample output from a very small edmx:

    Entity      Property        Column
    ---------------------------------------------
    Category    CategoryID      CategoryID 
                Name            CategoryName 
                Description     Description 
    
    Product     ProductID       ProductID 
                Name            ProductName 
                QuantityPerUnit QuantityPerUnit 
                UnitPrice       UnitPrice 
                StartDate       StartDate 
    

    Explanation:

    An edmx file has a fixed structure that I summarize here:

    <Edmx Version='3.0' xmlns='http://schemas.microsoft.com/ado/2009/11/edmx'>
      <Runtime>
        <ConceptualModels>
        ...
        </ConceptualModels>
        <Mappings>
          <Mapping Space='C-S' xmlns='http://schemas.microsoft.com/ado/2009/11/mapping/cs'>
            <EntityContainerMapping StorageEntityContainer='CodeFirstDatabase' CdmEntityContainer='UserQuery'>
              <EntitySetMapping Name='Categories'>
                <EntityTypeMapping TypeName='CodeFirstNamespace.Category'>
                  <MappingFragment StoreEntitySet='Category'>
                    <ScalarProperty Name='CategoryID' ColumnName='CategoryID' />
                    ...
                  </MappingFragment>
                </EntityTypeMapping>
              </EntitySetMapping>
              <AssociationSetMapping Name='Category_Products' TypeName='CodeFirstNamespace.Category_Products' StoreEntitySet='CategoryProduct'>
              </AssociationSetMapping>
            </EntityContainerMapping>
          </Mapping>
        </Mappings>
        <StorageModels>
        ...
        </StorageModels>
      </Runtime>
      <Designer>
      ...
      </Designer>
    </Edmx>
    

    The Mappings part is what we're after. So I first create a new XDocument of this part:

    var xn = XName.Get("Mappings", doc.Root.GetDefaultNamespace().ToString());
    // First descendant of the "Mappings" node, which is the "Mapping" node
    var mappingsNode = doc.Descendants(xn).Descendants().First();
    var mappingDoc = XDocument.Parse(mappingsNode.ToString());
    

    The "Mapping" node has its own namespace, so I parse it into an XDocument in order to get this namespace by which I can query its nodes. Thus, I can get to the mapping fragments:

    xn = XName.Get("MappingFragment", mappingDoc.Root
                                                .GetDefaultNamespace().ToString());
    var mappings = mappingDoc.Descendants(xn) ...
    

    Maybe there is a better way to get to these nodes, but I'm not very fluent in this XDocument API.

    Then it's a question of pulling out the right attributes and project them to a queryable structure.

    Thoughts

    It works, OK, but it's not elegant at all. It greatly depends on the edmx structure, so it can break any moment. I think there should come a decent, convenient way to read a context's metadata.

    By the way, if you work code-first you can generate an edmx by an EdmxWriter.

    0 讨论(0)
  • 2020-12-12 06:52

    Working on another problem I found a much better way to achieve this. I think this wasn't even possible when I wrote the first answer.

    To find a store name starting from a CLR type name, we have to access the store-CLR space of the EDM model. That's where mappings between classes and properties on the one hand, and tables and column on the other hand are found. Knowing this it's just a matter of carefully dissecting the content of a deep tree of objects to get the desired results:

    public IEnumerable<Tuple<string, string, string, string>> GetTableAndColumns<TEntity>()
    {
        var entityContainerMappings = (this as IObjectContextAdapter).ObjectContext
            .MetadataWorkspace.GetItems(DataSpace.CSSpace)
            .OfType<EntityContainerMapping>();
    
        var entityTypeMappings = entityContainerMappings
            .SelectMany(m => m.EntitySetMappings
                .Where(esm => esm.EntitySet.ElementType.Name == typeof(TEntity).Name))
            .SelectMany(esm => esm.EntityTypeMappings).ToArray();
    
        var propertyMappings = (from etm in entityTypeMappings
            from prop in etm.EntityType.Properties
            join pm in entityTypeMappings.SelectMany(etm => etm.Fragments)
                .SelectMany(mf => mf.PropertyMappings)
                .OfType<ScalarPropertyMapping>()
                on prop.Name equals pm.Property.Name
            select new {etm, prop, pm}
            );
    
        return propertyMappings.Select(x => Tuple.Create(x.etm.EntitySetMapping.EntitySet.Name, x.pm.Column.Name, x.prop.DeclaringType.Name, x.prop.Name));
    }
    

    This is a method inside a DbContext subtype. For the specified type, it returns tuples containing

    • Table Name
    • Column Name
    • CLR class name
    • Property name

    For istance:

    Item1     Item2            Item3    Item4
    ===================================================
    Products  ProductId        Product  Id
    Products  ProductName      Product  Name
    Products  QuantityPerUnit  Product  QuantityPerUnit
    Products  UnitPrice        Product  UnitPrice
    Products  StartDate        Product  StartDate
    Products  RowVersion       Product  RowVersion
    
    0 讨论(0)
提交回复
热议问题