title: Entity Framework Linq query is client-evaluated when using custom type with ValueConverter
body: <p>I'm working with Microsoft's <strong>Entity Framework Core</strong> and tried to utilize <strong><a href="https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions" rel="noreferrer">ValueConverters</a></strong> to allow for custom types in my database model entities. The point is to have my own type which I can customize and which shields the rest of the code from a type actually used in database.</p>
  
  <p><em>(Sadly, the legacy code access the model entities directly with no interface, so this is what I am left with unless I do a significant overhaul.)</em></p>
  
  <p>It mostly works, but <strong>my problem</strong> is that Entity Framework is not able to convert my type to database type for a <strong>where clause</strong> <em>(possibly others, but this is what I have encountered)</em> and instead does a <strong>client-side evaluation</strong>, which obviously is a performace issue, as all candidates are queried.</p>
  
  <p>So, I wonder if anyone has encountered this and if there is a solution, or if I have to try something different.</p>
  
  <hr>
  
  <p>If you want some code, there it is. I tried to trim it down, so the implementation is a bit odd, but it still fails in the same way.</p>
  
  <p>Let's call my custom struct type <code>ItemId</code>, make it hold a string and allow it to be created from either long or string:</p>
  
  <pre><code>public struct ItemId
  {
      public string Data;
  
      public ItemId(long data)
      {
          Data = data.ToString();
      }
  
      public ItemId(string data)
      {
          Data = data;
      }
  
      public override bool Equals(object obj)
      {
          return obj is ItemId itemId &amp;&amp; Data == itemId.Data;
      }
  
      public override int GetHashCode()
      {
          return HashCode.Combine(Data);
      }
  
      public static bool operator ==(ItemId id1, ItemId id2)
      {
          return id1.Data == id1.Data;
      }
  
      public static bool operator !=(ItemId id1, ItemId id2)
      {
          return !(id1== id1);
      }
  }
  </code></pre>
  
  <p>Then, there is my converter for a database which stores 64-bit numeric Ids. I strongly suspect that hand-written Expressions are unnecessary, as the <a href="https://github.com/aspnet/EntityFrameworkCore/tree/master/src/EFCore/Storage/ValueConversion" rel="noreferrer">build-in converters</a> often don't use them and they seem to work fine, but I've added them in attempt to fix my problem:</p>
  
  <pre><code>public class ItemIdToLongConverter : ValueConverter&lt;ItemId, long&gt;
  {
      public ItemIdToLongConverter(ConverterMappingHints mappingHints = null)
          : base(ToLong(), ToItemId(), mappingHints)
      { }
  
     protected static Expression&lt;Func&lt;ItemId, long&gt;&gt; ToLong()
      {
          var data = typeof(ItemId).GetField(nameof(ItemId.StringData));
          var tryParseMethod = typeof(long).GetMethod(
              nameof(long.TryParse),
              new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(long).MakeByRefType() });
  
          var param = Expression.Parameter(typeof(ItemId));
          var parsedVariable = Expression.Variable(typeof(long));
  
          return Expression.Lambda&lt;Func&lt;ItemId, long&gt;&gt;(
              Expression.Block(
                  typeof(long),
                  new[] { parsedVariable },
                  Expression.Condition(
                      Expression.Call(
                          tryParseMethod,
                          Expression.Field(param, data),
                          Expression.Constant(NumberStyles.Any),
                          Expression.Constant(CultureInfo.InvariantCulture, typeof(IFormatProvider)),
                          parsedVariable),
                      parsedVariable,
                      Expression.Constant(default(long), typeof(long)))),
              param);
      }
  
      protected static Expression&lt;Func&lt;long, ItemId&gt;&gt; ToItemId()
      {
          var ctor = typeof(ItemId).GetConstructor(new[] { typeof(long) });
  
          var param = Expression.Parameter(typeof(long));
  
          return Expression.Lambda&lt;Func&lt;long, ItemId&gt;&gt;(
              Expression.Block(
                  typeof(ItemId),
                  Expression.New(ctor, param)
              ),
              param);
      }
  }
  </code></pre>
  
  <p>I register my converter in the model in this fashion:</p>
  
  <pre><code>modelBuilder.Entity&lt;MyTable&gt;(entity =&gt;
  {
      ...
      entity.Property(e =&gt; e.ItemId).HasConversion(new ItemIdToLongConverter()).ValueGeneratedNever();
      ...
  });
  </code></pre>
  
  <p>And here is a query which gets client-evaluated because it can not convert <code>id</code> to database type:</p>
  
  <pre><code>var id = new ItemId(100);
  dbContext.MyTable.FirstOrDefault(x =&gt; x.ItemId == id);
  </code></pre>
  
  <p>Curiously, this oddly structured one gets translated fine:</p>
  
  <pre><code>var ids = Enumerable.Repeat(new ItemId(100), 1);
  dbContext.MyTable.FirstOrDefault(x =&gt; ids.Contains(x.ItemId));
  </code></pre>