Resolve entity name for Foreign key properties from a FieldMapping

Posts   
 
    
basik
User
Posts: 123
Joined: 20-Aug-2012
# Posted on: 11-Jul-2013 11:47:31   

I've created a custom Lpt template to produce map class for each entity in an Entity v5 Framework project. Using LLBLGenPro 3.5 November 6th 2012 Build.

This is the LPT snippet that produces the #region Properties part of the class


#region Properties
<%
            var fieldMappingsToTraverse = GeneratorUtils.GetAllFieldMappingsToTraverseForEntity(currentProject, mapping);
            string propertyField = string.Empty;
            foreach(FieldMapping fm in fieldMappingsToTraverse)
            { 
                if(fm.MappedFieldInstance.IsForeignKeyField) 
                    propertyField = string.Format("t.{0}.{1}",fm.MappedFieldInstance.FullName,fm.MappedFieldName);
                else
                    propertyField = string.Format("t.{0}",fm.MappedFieldName);
                    
                if(fm.MappedTarget.NETTypeAsString=="System.String") 
                {
%>this.Property(t => <%=propertyField%>).HasMaxLength(<%=fm.MappedTarget.Length%>);
<%              }
                else
                {
%>this.Property(t => <%=propertyField%>);
<%              }
            }
%>#endregion    

Resulting class


public partial class SecurityMap : EntityTypeConfiguration<Security>
{
    public SecurityMap()
    {
        #region Primary Key
        this.HasKey(t => t.SecurityId);
        #endregion
        
        #region Properties
        this.Property(t => t.CountryCode).HasMaxLength(10);
        this.Propertyt => t.CurrencyCode (FK).CurrencyCode).HasMaxLength(5);
        #endregion
    }
}

Required


public partial class SecurityMap : EntityTypeConfiguration<Security>
{
    public SecurityMap()
    {
        #region Primary Key
        this.HasKey(t => t.SecurityId);
        #endregion
        
        #region Properties
        this.Property(t => t.CountryCode).HasMaxLength(10);
        this.Property(t => t.Currency.CurrencyCode).HasMaxLength(5);
        #endregion
    }
}

The issue I have is that from the FieldMapping there doesn't seem to be the name of the entity that represents the FK field. In this case CurrencyCode is a FK field on the Security entity and the property needed is t.Currency.CurrencyCode. The related entity is therefor Currency. I've looked at the assembly in ILSpy but can't see how to get back to the entity for a related FK field. The flag fm.MappedFieldInstance.IsForeignKeyField is useful as it indicates that the field is a foreign key field.

Please could you advise or perhaps this is done in some of the shipped templates. Thank you.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 12-Jul-2013 07:59:11   

IMHO, you are doing an incorrect assumption in your code, or at least that seems to me:

You have a fieldMapping in your loop, suppose that it's a FK field, that FK field is pointing to another entity, so you don't have exactly the field that you want to map on that related entity, the only you could do is emit the navigator as a property. So, it's not clear what your required class is supposed to be.

The other incorrect assumption is that the relation is simple (i.e. the FK is formed only by one field). For instance, you have A.b and A.c, and in your DB you have A.b (FK) pointing to X.b, an another composite FK formed by (A.b, A.c) pointing to (Y.b, Y.c). It's incorrect to assume that given a simple FieldMapping you can get the other side of a relation.

The best you can do is to inspect the involved relationships and analyze them in order to know more about the two sides (PK,FK). Then you can emit code for the corresponding navigators, but I don't know because your required code seems to require a field, maybe you know in advance that the field's name would be the same on both sides and that the relationship is not composite.

See this template code, for instance:

<%
    Project currentProject = _executingGenerator.ProjectDefinition; 
    EntityDefinition entity = (EntityDefinition)_activeObject;
    
    var allRelationshipInfosToTraverse = GeneratorUtils.GetAllRelationshipInfosForEntity(_executingGenerator, entity)
                                                    .Where(ri=>((ri.RelationshipType==EntityRelationshipType.ManyToMany) && 
                                                                ((IndirectRelationshipEdge)ri.Relationship).IsPureManyToMany) ||
                                                                ((ri.RelationshipType!=EntityRelationshipType.ManyToMany) && 
                                                                !ri.NormalRelationship.EntityFkSide.IsPureManyToManyIntermediate))
                                                    .ToList();          

    foreach(var relationshipInfo in allRelationshipInfosToTraverse)
    {
        if(relationshipInfo.NavigatorIsHidden)
        {
            continue;
        }
        
        switch(relationshipInfo.RelationshipType)
        {
            case EntityRelationshipType.ManyToOne:
            case EntityRelationshipType.OneToOne:
%>      public virtual <%=relationshipInfo.RelatedEntity.Name%> <%=relationshipInfo.Navigator%> { get; set;}
<%              break;
            case EntityRelationshipType.OneToMany:
            case EntityRelationshipType.ManyToMany:
%>      public ICollection<<%=relationshipInfo.RelatedEntity.Name%>> <%=relationshipInfo.Navigator%> { get; set;}
<%              break;
        }
    }
%>

... that emits generated code:

public virtual Employee TheEmpoyee { get; set;}
public ICollection<OrderDetail> OrderDetails { get; set;}

... where "TheEmployee" is the name of the navigator on the OrderEntity side (Order.TheEmployee of type Employee).

Maybe you need to clarify the required code, or maybe I misunderstood you. A good template to see this kind of code is [LLBLGen installation folder]\Frameworks\Entity Framework\Templates\V4\C#\dbContextEntityClass.lpt.

Another useful resource is the CoreAssemblies Reference Manual (LLBLGenPro.CoreAssemblies.ReferenceManual.chm). You can download it from the LLBLGen site's Customer Area.

David Elizondo | LLBLGen Support Team
basik
User
Posts: 123
Joined: 20-Aug-2012
# Posted on: 12-Jul-2013 11:32:00   

Thank you for your prompt response. That is very useful and I agree that my assumptions are based on a simple relationship case. Which is fine for what we are trying to achieve. I can see that customised templates are difficult to get working for all cases, but it saves so much time that anything which cannot be resolved 100% could be fixed in the generated code.

The sample and links you sent are appreciated and is helpful in resolving most of the cases I would like to cover.

To clarify what I wanted to achieve: Security has a FK field called CurrencyCode Currency has a 1:m relationship to Security So from the Security entity if I want the CurrencyCode I must walk the relationship:

Security sec = new Security();
string cc = sec.Currecy.CurrencyCode;

I have managed to resolve the issue using the code here.


            #region Properties
            <%
                        string propertyField = string.Empty;
                        string fkFieldNameWithContainter = string.Empty;
                        
                        var rinfo = GeneratorUtils.GetAllRelationshipInfosForEntity(_executingGenerator,entity);
                        
                        foreach(FieldMapping fm in fieldMappingsToTraverse.OrderBy(f=>f.MappedFieldName))
                        { 
                            if(fm.MappedFieldInstance.IsForeignKeyField) 
                            {
                                fkFieldNameWithContainter = EmitRelatedField(rinfo, entity.Name,fm.MappedFieldWrapper.PathAsStringForTargetFieldName);
                                propertyField = string.Format("t.{0}",fkFieldNameWithContainter);
                            }
                            else
                                propertyField = string.Format("t.{0}",fm.MappedFieldName);
                                
                            if(fm.MappedTarget.NETTypeAsString=="System.String") 
                            {
            %>this.Property(t => <%=propertyField%>).HasMaxLength(<%=fm.MappedTarget.Length%>);
            <%              }
                            else
                            {
            %>this.Property(t => <%=propertyField%>);
            <%              }
                        }
            %>#endregion


<~
    private string EmitRelatedField(List<RelationshipInfo> relationInfo, string forEntity,string fkFieldName)
    {
        string returnValue = string.Empty;
        
        foreach(RelationshipInfo ri in relationInfo.Where(ri=>ri.ForEntity.Name==forEntity))
        {
            foreach(var f in ri.RelatedEntity.Fields.Where(o=>o.Name==fkFieldName))
            {
                returnValue = f.FullNameWithContainer;
                break;
            }
        }
        
        return returnValue;
    }
~>