44

I have an example model that looks like this:

public class PersonModel
{
     public int Id {get; set;}
     public string FirstName {get; set;}
     public string Lastname {get; set;}
     public string City {get; set;}
}

In my repository I want to create a search method where I pass in my model - but not all fields will always be populated. I want to create a WHERE and AND based on if a field in the model is populated or not. If the field is not populated then I do not want to create a WHERE clause for it.

For example - if I pass in FirstName = "Bob" and City = "Boston" then I want my search to look like this:

SELECT * FROM PersonTable WHERE FirstName = @firstName AND City = @city

Since I did not pass in Id or LastName I don't want them added to the query. If I just pass in City = "Boston" then I want it to look like this:

SELECT * FROM PersonTable WHERE City = @city

My repo method would look something like this

using Dapper;
public List<PersonModel> Search(PersonModel model)
{
//db = DbConnection connection
    var selectSql = "SELECT * FROM PersonTable "; //build out where clause somehow
    return db.Query<PersonModel>(selectSql).ToList();
}

My question is how would I build this out in my repo method properly?

Marcos Dimitrio
  • 6,651
  • 5
  • 38
  • 62
Eldorian
  • 603
  • 1
  • 6
  • 18

6 Answers6

84

You can also use Dapper's SqlBuilder.

Note that you'll have to install the Dapper.SqlBuilder NuGet package since it doesn't come with Dapper's main distribution.

Here is an example:

    [Test]
    public void Test()
    {
        var model = new PersonModel {FirstName = "Bar", City = "New York"};

        var builder = new SqlBuilder();

        //note the 'where' in-line comment is required, it is a replacement token
        var selector = builder.AddTemplate("select * from table /**where**/");

        if (model.Id > 0)
            builder.Where("Id = @Id", new { model.Id });

        if (!string.IsNullOrEmpty(model.FirstName))
            builder.Where("FirstName = @FirstName", new { model.FirstName });

        if (!string.IsNullOrEmpty(model.Lastname))
            builder.Where("Lastname = @Lastname", new { model.Lastname });

        if (!string.IsNullOrEmpty(model.City))
            builder.Where("City = @City", new { model.City });

        Assert.That(selector.RawSql, Is.EqualTo("select * from table WHERE FirstName = @FirstName AND City = @City\n"));

        //var rows = sqlConnection.Query(selector.RawSql, selector.Parameters);
    }

You can find some examples here.

Marcos Dimitrio
  • 6,651
  • 5
  • 38
  • 62
Void Ray
  • 9,849
  • 4
  • 33
  • 53
21

This should do the trick for you, clean and simple:

var selectSql = "SELECT * FROM PersonTable WHERE (@FirstName IS NULL OR FirstName =  @FirstName) AND (@LastName IS NULL OR LastName =  @LastName) AND (@City IS NULL OR City =  @City) AND (@Id IS NULL OR Id =  @Id) OPTION(RECOMPILE)";

return conn.Query<PersonModel>(selectSql, new
{
     model.FirstName,
     model.Lastname,
     model.City,
     Id = model.Id == 0? (int?)null: (int?)model.Id        
}).ToList();
Hans Kesting
  • 38,117
  • 9
  • 79
  • 111
JFM
  • 753
  • 12
  • 16
  • 5
    This is cleaner than building your SQL with C# conditionals. Consider using OPTION(RECOMPILE), so that query optimisation can take account of the actual parameters supplied. – bbsimonbb Oct 03 '16 at 10:13
  • 1
    I would have chosen this as the accepted answer if I hadn't chosen to use Dapper.SqlBuilder. – Eldorian Oct 03 '16 at 15:58
  • 1
    And for `List` or `List` ***parameters*** ? – Kiquenet Mar 15 '18 at 15:52
  • Just to reiterate bbsimonbbs point, if you do not add `OPTION (RECOMPILE)` your query can be VERY slow in some instances. SQL will optimize for one execution path and only that path will run reasonably. – Marie Sep 10 '19 at 17:17
  • In regards of the OPTION(RECOMPILE) statement, thank you very much bbsimonbb and Marie for stating this, I've now added it to the post – JFM Sep 11 '19 at 19:01
  • The downside of this is that you have to recompile the query every time which is very inefficient. – Jon49 Dec 19 '19 at 17:21
8

DapperQueryBuilder is an alternative to Dapper.SqlBuilder but much easier to use:

var query = cn.QueryBuilder($"SELECT * FROM PersonTable WHERE 1=1");

if (model.Id > 0) 
    query += $"AND Id = {model.Id}";

if (!string.IsNullOrEmpty(model.FirstName))
    query += $"AND FirstName = {model.FirstName}";

if (!string.IsNullOrEmpty(model.Lastname))
    query += $"AND Lastname = {model.Lastname}";

if (!string.IsNullOrEmpty(model.City))
    query += $"AND City = {model.City}";


var results = query.Query<Person>(); 

Query<Person>() will invoke Dapper passing the underlying SQL and parameters - and the underlying query is fully parametrized SQL (WHERE FirstName = @p0 AND LastName = @p1, etc). Parameters are automatically captured from the string interpolation (but it's safe against SQL injection).

--

ALTERNATIVE SYNTAX
Similar to Dapper.SqlBuilder you can also use the /**where**/ syntax (which will automatically join the conditions, no need to manually use AND or 1==1 trick):

var query = cn.QueryBuilder($@"
    SELECT * 
    FROM PersonTable
   /**where**/
");

if (model.Id > 0)
    query.Where($"Id = {model.Id}");

if (!string.IsNullOrEmpty(model.FirstName))
    query.Where($"FirstName = {model.FirstName}");

if (!string.IsNullOrEmpty(model.Lastname))
    query.Where($"Lastname = {model.Lastname}");

if (!string.IsNullOrEmpty(model.City))
    query.Where($"City = {model.City}");


var results = query.Query<Person>(); 

Disclaimer: I'm the author of the library

drizin
  • 1,737
  • 1
  • 18
  • 44
  • Simpler way is not working for me. Compiler says cannot implicitly convert from string to QueryBuilder. What am I missing here... – ClownCoder Mar 31 '22 at 02:14
  • 1
    @ClownCoder either you're not using the latest nuget package, or (most probable) you're trying to append strings instead of FormattableString (string interpolation, note the $ sign). – drizin Apr 01 '22 at 00:06
  • indeed. I was using an old package (1.1.0) by the time of writing this comment, last version is 1.2.4. Thanks a lot! – ClownCoder Apr 01 '22 at 01:55
  • Isn't this vulnerable to SQL injection attacks? – taiji123 May 02 '23 at 21:04
  • 1
    @taiji123 nope, it's safe. The library is exactly for that - it's not regular string interpolation. Check the docs. https://github.com/Drizin/DapperQueryBuilder/ – drizin May 02 '23 at 22:36
0
bool isFirstWhereSet = false;
bool isCityWhereSet = false;
string sqlQuery = "SELECT * FROM PersonTable "  ;
if (! String.IsNullOrEmpty(model.FirstName ))
{
sqlQuery  =sqlQuery  + "WHERE FirstName =@FirstName" ;
isFirstWhereSet = true;
}

if (! String.IsNullOrEmpty(model.City))
{
isCityWhereSet  = true ;
if (! isFirstWhereSet )
sqlQuery  = sqlQuery  + " WHERE City = @city";
else
sqlQuery  = sqlQuery  + " AND City = @city";
}



if (isFirstWhereSet == true && isCityWhereSet == true )
 return db.Query<PersonModel>(sqlQuery , new { FirstName = model.FirstName  , City = mode.City}).ToList();
else if (isFirstWhereSet == true && isCityWhereSet  == false)
 return db.Query<PersonModel>(sqlQuery , new { FirstName = model.FirstName }).ToList();
else if (isFirstWhereSet == false && isCityWhereSet  == true)
 return db.Query<PersonModel>(sqlQuery , new { City= model.City}).ToList();
else
{
 return db.Query<PersonModel>(sqlQuery).ToList();
}
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
FakeisMe
  • 464
  • 3
  • 9
0

You can use the ExpressionExtensionSQL library. This library converts lambda expressions to where clauses, and can be used with dapper and ADO.

0

This syntax code worked for me and not facing any issue.

var builder = new SqlBuilder();
if (Condition)
{
    builder.Where($"LOWER(ColumnName) LIKE @SubjectId", new { Value = Class.Obj  });
}
if (Condition)
{
    builder.Where($"LOWER(Domain) = @Domain", new { Domain = Obj[1] });
}

var selector = builder.AddTemplate("SELECT * FROM TableName /**where**/");

using (var connection = new SqlConnection(connectionString))
{
    var results = await connection.QueryAsync(selector.RawSql, selector.Parameters);
                return results.ToList();
}
hardik patel
  • 221
  • 4
  • 8