I have some collections in a mongo DB: autocomplete.brands and autocomplete.makes
Each of these collections have items based on the same pattern that have a Name property:
public class AutocompleteEntity
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Culture")]
public string Culture { get; set; }
[BsonElement("Key")]
public string Key { get; set; }
}
I would like to use generics for searching in each of these collections, here is what I have.
The high-level service using repositories:
public class AutocompleteHandler : IAutocompleteHandler
{
private readonly IAutocompleteRepository<Brands> _brandsRepository;
private readonly IAutocompleteRepository<Makes> _makesRepository;
public AutocompleteHandler(
IAutocompleteRepository<Brands> brandsRepository,
IAutocompleteRepository<Makes> makesRepository
)
{
_brandsRepository = brandsRepository;
_makesRepository = makesRepository;
}
public async Task<ICollection<string>> SearchForBrand(string name, string culture)
{
return await _brandsRepository.Search(name, culture);
}
public async Task<ICollection<string>> SearchForMake(string name, string culture)
{
return await _makesRepository.Search(name, culture);
}
}
For this to work I defined a class for each repository type:
public interface IAutocompleteCollection
{
}
public class Brands : IAutocompleteCollection
{
}
public class Makes : IAutocompleteCollection
{
}
and the generic repository:
public class AutocompleteRepository<T> : IAutocompleteRepository<T> where T : IAutocompleteCollection
{
private readonly IMongoCollection<AutocompleteEntity> _collection;
public AutocompleteRepository(IMongoTenantResolver mongoResolver)
{
var name = typeof(T).Name;
var collectionName = $"autocomplete.{char.ToLowerInvariant(name[0]) + name.Substring(1)}";
_collection = mongoResolver
.GetMongoDbInstance()
.GetCollection<AutocompleteEntity>(collectionName);
}
public async Task<List<string>> Search(string name, string culture)
{
var filter = Builders<AutocompleteEntity>.Filter.Regex("Name", new BsonRegularExpression(name, "i"));
filter &= Builders<AutocompleteEntity>.Filter.Eq("Culture", culture);
var data = await _collection.FindAsync(filter);
var items = await data.ToListAsync();
return items.Select(i => i.Name).ToList();
}
}
I am using the empty classes Brands and Makes in order to be able to resolve the collection by name in the generic repository:
var name = typeof(T).Name;
var collectionName = $"autocomplete.{char.ToLowerInvariant(name[0]) + name.Substring(1)}";
Is there a more elegant way to use generics without relying on empty classes/interface or is that the best way to use generics in my case?
After @RobertHarvey 's answer, I am adding this information. Generics usage is useful to me because I am using Dependency Injection and thus I can have this one-liner in Startup.cs:
services.AddTransient(typeof(IAutocompleteRepository<>), typeof(AutocompleteRepository<>));
I can then add many other autocomplete types without the need to register them individually in the Startup. Also because of DI usage, I am not sure how I can inject an enum value into the constructor.
public enum CollectionName
{
Brands,
Makes
}
// Constructor
public AutoCompleteRepository(IMongoTenantResolver resolver, CollectionName name)
{
// Name of collection is name.ToString(). Preserves static type checking,
// avoids empty classes and interfaces.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With