2

To make the story short, i have a model like this.

public class User : IEntity
{
    public int Id { get; set; }
    public string Properties { get; set; }
    public DateTime? CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    [NotMapped]
    public UserDTO _Properties
    {
        get
        {
            return Properties == null ? null : JsonConvert.DeserializeObject<UserDTO>(Properties);
        }
        set
        {
            Properties = JsonConvert.SerializeObject(value);
        }
    }
}

And im storing json data inside the properties column, that part is ok, then i have a query where i need to search by Name or Email, so i have a Repository pattern and

alter procedure [dbo].[spSearchUsers]
@term nvarchar(max) AS BEGIN
select Id, JSON_VALUE(Properties, '$.FirstName') as FirstName,
    JSON_VALUE(Properties, '$.LastName') as LastName, 
    JSON_VALUE(Properties, '$.Email') as Email,
    JSON_VALUE(Properties, '$.Role') As [Role],
    JSON_VALUE(Properties, '$.ProgramInformation') as ProgramInformation,
    JSON_VALUE(Properties, '$.CertsAndCredentials') as CertsAndCredentials,
    JSON_VALUE(Properties, '$.Phone') as Phone,
    JSON_VALUE(Properties, '$.LogoUrl') as LogoUrl,
    JSON_VALUE(Properties, '$.ProfessionalPic') as ProfessionalPic,
    JSON_VALUE(Properties, '$.Biography') as Biography,
    CreatedAt, UpdatedAt
from Users
where CONCAT(JSON_VALUE(Properties, '$.FirstName'),' ',JSON_VALUE(Properties, '$.LastName')) like '%'+ @term +'%' 
    or JSON_VALUE(Properties, '$.Email') like '%'+ @term +'%'
    and JSON_VALUE(Properties, '$.IsActive') = 'true'
    and JSON_VALUE(Properties, '$.IsLockedOut') = 'false'
    and JSON_VALUE(Properties, '$.IsVerified') = 'true' END

When i execute the stored procedure inside the sql server management studio i get the information right:

exec spSearchUsers 'Raul'

I get this result:

1 Raul Alejandro Baez Camarillo [email protected] 0 NULL NULL NULL NULL NULL NULL 2021-11-16 03:08:09.6630000 2021-11-16 03:08:09.6630000

So now i want to consume that stored procedure in my controller, im using Repository pattern and i have tried using the context.Model.FromSqlRaw like this.

var result = await _context.Users.FromSqlRaw("EXEC dbo.spSearchUsers {0}", term).ToListAsync()

But when i execute it im getting this error:

System.InvalidOperationException: The required column 'Properties' was not present in the results of a 'FromSql' operation. at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.BuildIndexMap(IReadOnlyList1 columnNames, DbDataReader dataReader) at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.MoveNextAsync() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at AcadminRest.Controllers.UsersController.Search(String term) in D:\Projects\Accomplishment\AcadminRest\AcadminRest\Controllers\UsersController.cs:line 71 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)

Can someone help me getting this? thanks in advance.

2 Answers 2

2

Well - you could use Value conversion:

https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations#composite-value-objects

Another option would be to create a keyless entity for that specific query:

https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types?tabs=data-annotations

Edit: here's a bit on how to setup a keyless entity. https://stackoverflow.com/a/69924584/4122889

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks sommen, it helps me a lot solving my issue.
0

The options that sommmen gave are ones that I would have also offered up. In particular -- If you're not tied to your procedure at all.

Both options give you a simpler querying method within your repository. Personally -- I would go with the value conversion but... here are both options in more detail.

Value conversion: you would want to use your UserDTO and actually map it. Your model would look like so:

public class User : IEntity
{
    public int Id { get; set; }
    public UserDTO Properties { get; set; }
    public DateTime? CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

No need for an extra property. Then your db context would be updated to use a conversion for Properties:

// somewhere within OnModelCreating
modelBuilder.Entity<User>(entity =>
{
    entity.Property(r => r.Properties)
        .HasConversion(
            v => JsonConvert.SerializeObject(v),
            v => JsonConvert.DeserializeObject<UserDTO>(v)
        );
});

Now in your repository you can search with linq on your Users.

var result = await _context.Users
    .Where(u => u.Properties.FirstName.Contains(term) ||
                u.Properties.LastName.Contains(term) || 
                u.Properties.Email.Contains(term))
    .Where(u => u.IsActive && !u.IsLockedOut && u.IsVerified).ToListAsync();

-- OR --

The other option (but slightly different from just a keyless entity) - if you want to make use of the query that you already have in your procedure.
Create a view (UserDTO_Properties) with your query w/o the concatenation of first/last fields and then a keyless entity to go with your view.

You would take your UserDTO and create the keyless entity to map to your view. In your db context:

    // context prop for dbset
    DbSet<UserDTO> UserDTOs { get; set; }

    // and then somewhere in OnModelCreating
    modelBuilder.Entity<UserDTO>(entity =>
    {
        entity.ToTable("UserDTO_Properties");
        entity.HasNoKey();
    }

Now a similar query to get your results:

var result = await _context.UserDTOs
    .Where(u => u.FirstName.Contains(term) ||
                u.LastName.Contains(term) || 
                u.Email.Contains(term)).ToListAsync();

1 Comment

the first option is not working

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.