Search code examples
.netlinqentity-framework-core.net-8.0entity-framework-8

Value object with linq cannot do contains method


I'm using EF Core 8 with an Oracle database.

I'm trying to do a select of data where a value is contained in a column. The SQL statement would be like :

SELECT * 
FROM TASKS 
WHERE NAME LIKE "%Task%"

I have the following linq code (Name is a ValueObject of type string):

 List<TaskEntity> tasks = await _dbSet.Where(x => x.Name.Value.Contains("Task"))
                                      .ToListAsync();

And the entity

public partial class TaskEntity 
{
    public DatabaseTableId Id { get; set; }
    public Name Name { get; set; }
}

But I'm getting this error:

System.InvalidOperationException: 'The LINQ expression
'DbSet()
.Where(t => t.Name.Value.Contains("Task"))' could not be translated.

The model builder:

modelBuilder.Entity<TaskEntity>(entity =>
{
    entity.HasKey(e => e.Id);

    entity.ToTable("TASKS");

    entity.Property(e => e.Id)
        .HasConversion<DatabaseTableIdConverter>()
        .ValueGeneratedOnAdd()
        .HasColumnType("NUMBER")
        .HasColumnName("ID");
    entity.Property(e => e.Name)
        .HasConversion<NameConverter>()
        .HasMaxLength(50)
        .IsUnicode(false)
        .HasColumnName("NAME");
});

Name VO (simplified):

public class Name
{
    public string Value { get; }

    public Name(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentNullException(nameof(Name), "Name cannot be null or empty.");
        }

        Value = value;
    }
}

Name converter:

public class NameConverter : ValueConverter<Name, string>
{
    /// <summary>
    /// Converts a <see cref="Name"/> to a <see cref="string"/> value to use it in EF
    /// </summary>
    public NameConverter() : base
    (
        Name => Name.Value,
        value => new Name(value))
    { }
}

I've tried everything, a contains method in the valueobject using EF.Function.Like... with FromSqlRaw does work but I need the IQueryable.


Solution

  • You can enhance your Name class by adding implicit conversion operators:

    public class Name
    {
        public string Value { get; }
    
        public Name(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentNullException(nameof(Name), "Name cannot be null or empty.");
            }
    
            Value = value;
        }
    
        public static implicit operator string(Name name) => name.Value;
        public static implicit operator Name(string value) => new Name(value);
    }
    

    This allows you to use explicit casting when querying:

    List<TaskEntity> tasks = await _dbSet
      .Where(x => ((string)x.Name).Contains("Task"))
      .ToListAsync();
    

    This has been tested with EF Core 8.