77using Foundatio . Parsers . LuceneQueries . Visitors ;
88using Foundatio . Parsers . SqlQueries . Visitors ;
99using Foundatio . Xunit ;
10+ using Microsoft . Data . SqlClient ;
1011using Microsoft . EntityFrameworkCore ;
1112using Microsoft . EntityFrameworkCore . Infrastructure ;
1213using Microsoft . Extensions . DependencyInjection ;
@@ -70,14 +71,17 @@ public async Task CanSearchDefaultFields()
7071
7172 var context = parser . GetContext ( db . Employees . EntityType ) ;
7273
73- string sqlExpected = db . Employees . Where ( e => e . FullName . StartsWith ( " John" ) || e . Title . StartsWith ( " John") ) . ToQueryString ( ) ;
74- string sqlActual = db . Employees . Where ( """FullName.StartsWith(" John" ) || Title.StartsWith(" John") """ ) . ToQueryString ( ) ;
74+ string sqlExpected = db . Employees . Where ( e => EF . Functions . Contains ( e . FullName , " \" John* \" " ) || EF . Functions . Contains ( e . Title , " \" John* \" ") ) . ToQueryString ( ) ;
75+ string sqlActual = db . Employees . Where ( parser . ParsingConfig , """FTS.Contains(FullName, "\" John*\"" ) || FTS.Contains(Title, "\" John*\" ") """ ) . ToQueryString ( ) ;
7576 Assert . Equal ( sqlExpected , sqlActual ) ;
7677 string sql = await parser . ToDynamicLinqAsync ( "John" , context ) ;
77- sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
78- var results = await db . Employees . Where ( sql ) . ToListAsync ( ) ;
79- Assert . Single ( results ) ;
78+ sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
8079 Assert . Equal ( sqlExpected , sqlActual ) ;
80+
81+ await WaitForFullTextIndexAsync ( db , "ftCatalog" ) ;
82+
83+ var results = await db . Employees . Where ( parser . ParsingConfig , sql ) . ToListAsync ( ) ;
84+ Assert . Single ( results ) ;
8185 }
8286
8387 [ Fact ]
@@ -101,28 +105,31 @@ public async Task CanSearchWithTokenizer()
101105
102106 var context = parser . GetContext ( db . Employees . EntityType ) ;
103107
104- string sqlExpected = db . Employees . Where ( e => e . NationalPhoneNumber . StartsWith ( " 2142222222") ) . ToQueryString ( ) ;
105- string sqlActual = db . Employees . Where ( "NationalPhoneNumber.StartsWith( \" 2142222222\" )") . ToQueryString ( ) ;
108+ string sqlExpected = db . Employees . Where ( e => EF . Functions . Contains ( e . NationalPhoneNumber , " \" 2142222222* \" ") ) . ToQueryString ( ) ;
109+ string sqlActual = db . Employees . Where ( parser . ParsingConfig , "FTS.Contains(NationalPhoneNumber, \" \\ \" 2142222222* \\ \" \" )") . ToQueryString ( ) ;
106110 Assert . Equal ( sqlExpected , sqlActual ) ;
107111
108112 string sql = await parser . ToDynamicLinqAsync ( "214-222-2222" , context ) ;
109113 _logger . LogInformation ( sql ) ;
110- sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
111- var results = await db . Employees . Where ( sql ) . ToListAsync ( ) ;
112- Assert . Single ( results ) ;
113114 Assert . Equal ( sqlExpected , sqlActual ) ;
114115
116+ await WaitForFullTextIndexAsync ( db , "ftCatalog" ) ;
117+
118+ sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
119+ var results = await db . Employees . Where ( parser . ParsingConfig , sql ) . ToListAsync ( ) ;
120+ Assert . Single ( results ) ;
121+
115122 sql = await parser . ToDynamicLinqAsync ( "2142222222" , context ) ;
116123 _logger . LogInformation ( sql ) ;
117- sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
118- results = await db . Employees . Where ( sql ) . ToListAsync ( ) ;
124+ sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
125+ results = await db . Employees . Where ( parser . ParsingConfig , sql ) . ToListAsync ( ) ;
119126 Assert . Single ( results ) ;
120127 Assert . Equal ( sqlExpected , sqlActual ) ;
121128
122129 sql = await parser . ToDynamicLinqAsync ( "21422" , context ) ;
123130 _logger . LogInformation ( sql ) ;
124- sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
125- results = await db . Employees . Where ( sql ) . ToListAsync ( ) ;
131+ sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
132+ results = await db . Employees . Where ( parser . ParsingConfig , sql ) . ToListAsync ( ) ;
126133 Assert . Single ( results ) ;
127134 }
128135
@@ -142,8 +149,8 @@ public async Task CanHandleEmptyTokens()
142149
143150 string sql = await parser . ToDynamicLinqAsync ( "test" , context ) ;
144151 _logger . LogInformation ( sql ) ;
145- string sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
146- var results = await db . Employees . Where ( sql ) . ToListAsync ( ) ;
152+ string sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
153+ var results = await db . Employees . Where ( parser . ParsingConfig , sql ) . ToListAsync ( ) ;
147154 Assert . Empty ( results ) ;
148155 }
149156
@@ -302,11 +309,11 @@ public async Task CanUseCollectionDefaultFields()
302309
303310 var context = parser . GetContext ( db . Employees . EntityType ) ;
304311
305- string sqlExpected = db . Employees . Where ( e => e . Companies . Any ( c => c . Name . StartsWith ( " acme") ) ) . ToQueryString ( ) ;
306- string sqlActual = db . Employees . Where ( """Companies.Any(Name.StartsWith(" acme"))""" ) . ToQueryString ( ) ;
312+ string sqlExpected = db . Employees . Where ( e => e . Companies . Any ( c => EF . Functions . Contains ( c . Name , " \" acme* \" ") ) ) . ToQueryString ( ) ;
313+ string sqlActual = db . Employees . Where ( parser . ParsingConfig , """Companies.Any(FTS.Contains(Name, "\" acme*\" "))""" ) . ToQueryString ( ) ;
307314 Assert . Equal ( sqlExpected , sqlActual ) ;
308315 string sql = await parser . ToDynamicLinqAsync ( "acme" , context ) ;
309- sqlActual = db . Employees . Where ( sql ) . ToQueryString ( ) ;
316+ sqlActual = db . Employees . Where ( parser . ParsingConfig , sql ) . ToQueryString ( ) ;
310317 Assert . Equal ( sqlExpected , sqlActual ) ;
311318 }
312319
@@ -450,7 +457,7 @@ public IServiceProvider GetServiceProvider()
450457 parser . Configuration . UseEntityTypePropertyFilter ( p => p . Name != nameof ( Company . Description ) ) ;
451458 parser . Configuration . AddQueryVisitor ( new DynamicFieldVisitor ( ) ) ;
452459 parser . Configuration . SetDefaultFields ( [ "FullName" ] , SqlSearchOperator . Contains ) ;
453- parser . Configuration . UseFullTextSearch ( ) ;
460+ parser . Configuration . SetFullTextFields ( [ "Name" , "FullName" , "Title" , "NationalPhoneNumber" ] ) ;
454461 services . AddSingleton ( parser ) ;
455462 return services . BuildServiceProvider ( ) ;
456463 }
@@ -504,24 +511,23 @@ await db.Database.ExecuteSqlRawAsync(
504511 @"IF FULLTEXTSERVICEPROPERTY('IsFullTextInstalled') != 1
505512 BEGIN
506513 RAISERROR('Full-Text Search is not installed', 16, 1);
507- END" ) ;
514+ END
508515
509- await db . Database . ExecuteSqlRawAsync (
510- @"IF NOT EXISTS (SELECT * FROM sys.fulltext_catalogs WHERE name = 'ftCatalog')
516+ IF NOT EXISTS (SELECT * FROM sys.fulltext_catalogs WHERE name = 'ftCatalog')
511517 BEGIN
512518 CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
513- END" ) ;
519+ END
514520
515- await db . Database . ExecuteSqlRawAsync (
516- @"IF EXISTS (SELECT * FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('Employees'))
521+ IF EXISTS (SELECT * FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('Employees'))
517522 BEGIN
518523 DROP FULLTEXT INDEX ON Employees;
519- END" ) ;
524+ END
520525
521- await db . Database . ExecuteSqlRawAsync (
522- @"CREATE FULLTEXT INDEX ON Employees
526+ CREATE FULLTEXT INDEX ON Employees
523527 (
524- FullName LANGUAGE 1033
528+ FullName LANGUAGE 1033,
529+ NationalPhoneNumber LANGUAGE 1033,
530+ Title LANGUAGE 1033
525531 )
526532 KEY INDEX PK_Employees
527533 ON ftCatalog
@@ -530,6 +536,27 @@ ON ftCatalog
530536 return db ;
531537 }
532538
539+ private async Task WaitForFullTextIndexAsync ( DbContext db , string catalogName , int timeoutSeconds = 30 )
540+ {
541+ var end = DateTime . UtcNow . AddSeconds ( timeoutSeconds ) ;
542+
543+ while ( DateTime . UtcNow < end )
544+ {
545+ string sql = "SELECT FULLTEXTCATALOGPROPERTY(@catalogName, 'PopulateStatus') AS Value" ;
546+
547+ int status = await db . Database
548+ . SqlQueryRaw < int > ( sql , new SqlParameter ( "@catalogName" , catalogName ) )
549+ . SingleAsync ( ) ;
550+
551+ if ( status == 0 )
552+ return ;
553+
554+ await Task . Delay ( 500 ) ;
555+ }
556+
557+ throw new TimeoutException ( $ "Full-text catalog '{ catalogName } ' didn't finish populating in time.") ;
558+ }
559+
533560 private async Task ParseAndValidateQuery ( string query , string expected , bool isValid )
534561 {
535562#if ENABLE_TRACING
0 commit comments