Tuesday, April 24, 2012

How to Implement Language Translation in WCF Data Service (OData) Part-2

In Part-1 of this post I explained language translation via <QueryView> and Entity Join approach that is suggested for scenario having just one translation table. This article present a generic approach that can be used with multiple translation tables

Problem :- 
Lets assume you have a Product Entity and and it has one fields that need to be translated. For simplicity I am Just using one table. Step by step solution is below
Solution 
  • Create your Main Table in EDMX and add the additional property (Translated Name)  
  • Also import all the translation tables 
 
  • In order support additional property you either you need <DefiningQuery> or <QueryView> element to explicitly supply select query. This solution uses  <QueryView>. See additional Null in query view.
  <EntitySetMapping Name="Products">
            <QueryView>
              select value
              NORTHWNDModel.Product
             
              (p.ProductID,
              p.ProductName,
              null,
              p.SupplierID,
              p.CategoryID,
              p.QuantityPerUnit,
              p.UnitPrice,
              p.UnitsInStock,
              p.UnitsOnOrder,
              p.ReorderLevel,
              p.Discontinued
              )
              from NORTHWNDModelStoreContainer.Products   as p
            </QueryView>
</EntitySetMapping>

  • Override "OnStartProcessingRequest"  in data service class
  protected override void OnStartProcessingRequest(ProcessRequestArgs args)
        {
            this.CurrentDataSource.ObjectMaterialized += new System.Data.Objects.ObjectMaterializedEventHandler(CurrentDataSource_ObjectMaterialized);
            base.OnStartProcessingRequest(args);
        }

  • Implement ObjectMaterialized even to do actual translation 
void CurrentDataSource_ObjectMaterialized(object sender, System.Data.Objects.ObjectMaterializedEventArgs e)
        {
            //This code uses hardecoded language parameter but if you want to pass it
            // dynamicly  use query paramteters

            string language = "IT-IT";
            var entities = this.CurrentDataSource;
            if (e.Entity.GetType() == typeof(Product))
            {
                Product entity = (Product)e.Entity;

                var translation = from code in entities.NameTranslations
                                  where (entity.ProductID==code.ProductID  &&
                                  code.Language == language)
                                  select code;

                if (translation.Count()>0)
                {
                    entity.ProductTranslatedName = translation.First().ProductNativeName;
                }
                else
                {
                    entity.ProductTranslatedName = "Not Found";
                }
            }
           

        }

Navigate to your service URL to see the result
  -<m:properties>
  <d:ProductID m:type="Edm.Int32">1</d:ProductID>
  <d:ProductName>Chai</d:ProductName>
  <d:ProductTranslatedName xml:space="preserve">IT_Chai</d:ProductTranslatedName>
  <d:SupplierID m:type="Edm.Int32">1</d:SupplierID>
  <d:CategoryID m:type="Edm.Int32">1</d:CategoryID>
  <d:QuantityPerUnit>10 boxes x 20 bags</d:QuantityPerUnit>
  <d:UnitPrice m:type="Edm.Decimal">18.0000</d:UnitPrice>
  <d:UnitsInStock m:type="Edm.Int16">39</d:UnitsInStock>
  <d:UnitsOnOrder m:type="Edm.Int16">0</d:UnitsOnOrder>
  <d:ReorderLevel m:type="Edm.Int16">10</d:ReorderLevel>
  <d:Discontinued m:type="Edm.Boolean">false</d:Discontinued>
  </m:properties>

- <m:properties>
  <d:ProductID m:type="Edm.Int32">3</d:ProductID>
  <d:ProductName>Aniseed Syrup</d:ProductName>
  <d:ProductTranslatedName>Not Found</d:ProductTranslatedName>
  <d:SupplierID m:type="Edm.Int32">1</d:SupplierID>
  <d:CategoryID m:type="Edm.Int32">2</d:CategoryID>
  <d:QuantityPerUnit>12 - 550 ml bottles</d:QuantityPerUnit>
  <d:UnitPrice m:type="Edm.Decimal">10.0000</d:UnitPrice>
  <d:UnitsInStock m:type="Edm.Int16">13</d:UnitsInStock>
  <d:UnitsOnOrder m:type="Edm.Int16">70</d:UnitsOnOrder>
  <d:ReorderLevel m:type="Edm.Int16">25</d:ReorderLevel>
  <d:Discontinued m:type="Edm.Boolean">false</d:Discontinued>
  </m:properties>


Suggested 
  1. Extract Language parameter dynamically.
  2. Querying database for each row in "CurrentDataSource_ObjectMaterialized" handler could be a big performance hit especially in case of large tables. I would suggest to implement caching of entire translation table at once. I will try to post another article on it once I have time.
Limitation 
  1. This approach will not work on projection ($select) query 
  2. Filter ($filter) will not work correctly on translated property  


No comments:

Post a Comment