0

Trying to reduce the code duplication by using generic methods to send a parameterized query to DB. Went for the following approach, but the query does not seem to resolve the implementation member's value. It works if the return type is strictly defined in GetQuery method, but then it's just back to square one. It also works if the CountryCode value is hardcoded inside GetQuery method.

Why doesn't the CountryCode use the value from the FrenchChocolate instance?

Interface:

public interface IChocolate 
{
    public string ManufacturerId { get; set; }
    public string CountryCode { get; } 
}

Implementation:

public class FrenchChocolate : IChocolate
{
    public string ManufacturerId { get; set; }
    public string CountryCode => "FR";
    // ...other properties...
}

Query method (yes, it's ugly, feel free to propose a better way)

private static Expression<Func<T, bool>> GetQuery<T>(string manufacturerId) where T: IChocolate
{
    T obj = (T)Activator.CreateInstance(typeof(T));
    return c => c.ManufacturerId == manufacturerId && c.CountryCode == obj.CountryCode; 
}

Method call:

var frenchChoc = await GetChocFromDb<FrenchChocolate>(GetQuery<FrenchChocolate>("123"));

This works, but defeats the purpose:

private static Expression<Func<**FrenchChocolate**, bool>> GetQuery<T>(string manufacturerId) where T: IChocolate
{
    T obj = (T)Activator.CreateInstance(typeof(T));
    return c => c.ManufacturerId == manufacturerId && c.CountryCode == obj.CountryCode; 
}

This as well:

var frenchChoc = await GetChocFromDb<FrenchChocolate>(c => c.ManufacturerId == manufacturerId && c.CountryCode == "FR");
dzookatz
  • 213
  • 4
  • 17
  • Can you show what you mean by "if the return type is strictly defined in `GetQuery` method" and "the extension does not seem to resolve"? – Sweeper Jul 22 '21 at 11:00
  • Sure. Edited the question and added the examples. – dzookatz Jul 22 '21 at 11:06
  • Try putting `obj.CountryCode` into a local variable first? – Sweeper Jul 22 '21 at 11:11
  • Your first example doesnt make a whole load of sense. Its returning an expression which expects an instance of `T` to be passed in, but your instantiating an instance of `T` within the function too. Hence im slightly confusecd what you're tring to achieve here?! – Jamiec Jul 22 '21 at 11:13
  • @Sweeper isn't that the same? Anyway, tried it and the result is the same. – dzookatz Jul 22 '21 at 11:15
  • @Jamiec The idea is exactly **not** to use the instance, but to work with just the type. There _should_ be no implementation, but didn't find a way - hence the question :) – dzookatz Jul 22 '21 at 11:18
  • The type doesn't have a property called `CountryCode` - it's entirely uinclear what you're trying to do. Not to mention it [works exactly as I expect](https://dotnetfiddle.net/Qr6hg5) even though you said it doesnt. – Jamiec Jul 22 '21 at 11:21
  • Put another way - all `FrenchChocolate` instances have a country code of `FR` (you hardcoded it) so if you call `GetQuery("123")` why would you need to check the country code of the instance passed in to the expression?? You presumably only need to check the manufacturer matches what you passed to `GetQuery` as an argument. – Jamiec Jul 22 '21 at 11:24
  • @Jamiec Thanks for taking the time to try it out. The interface implementation is just for illustrative purposes; there are other types that are not related to one another except they have Id. I am trying to reduce the code to get the value from the DB for different entities, since the query is almost the same, except for the Id and the hardcoded part. I.e. `var belgianChoc = GetChocFromDb(GetQuery("123"));`. Applogies if the question's intention is unclear. – dzookatz Jul 22 '21 at 11:36
  • So why are you not passing in the expected country code same as manufacturerId? `GetQuery("123,"FR")`? If you're just building up a known query for types of `IChocolate` surely that makes sense – Jamiec Jul 22 '21 at 11:41
  • @Jamiec sorry, tried so many combinations that I made a mistake. The `GetQuery("123", "FR")` doesn't work as well. Updated the question. – dzookatz Jul 22 '21 at 13:13
  • This is all so convoluted, All you're doing is building up an `Expression>`. How you choose to do that is up to you. Theres no magic here. If you were writing it longhand, you'd write `GetChocFromDb(c => c.ManufacturerId == "123" && c.CountryCode == "FR");`. If you want a function to make that shorter then fine but again, zero magic involved – Jamiec Jul 22 '21 at 13:24

1 Answers1

0

Lets take a step backwards, if you were to write this out longhand, without your GetQuery function, you'd write:

var frenchChoc = await GetChocFromDb<FrenchChocolate>(c => c.ManufacturerId == "123" && c.CountryCode == "FR");

Now, if you want to offload making that query to a GetQuery function it would simply be

private static Expression<Func<T, bool>> GetQuery<T>(string manufacturerId, string countryCode) where T: IChocolate
{
    return c => c.ManufacturerId == manufacturerId && c.CountryCode == countryCode; 
}  

And

var frenchChoc = await GetChocFromDb<FrenchChocolate>(GetQuery<FrenchChocolate>("123","FR"));
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • Debugging `GetQuery` returns this `((MySolution.Interfaces.IChocolate)$c).ManufacturerId == ...` (shortened for brevity) but should return just `$c.ManufacturerId == ...` in order for the GetChocFromDb to work, right? – dzookatz Jul 22 '21 at 22:27
  • Exact thing that happens is: https://stackoverflow.com/q/3613701/1340960 – dzookatz Jul 23 '21 at 09:18
  • @dzookatz does the accepted answer on that fix your problem (ie include `class` constraint) – Jamiec Jul 23 '21 at 11:09
  • Not entirely, but it fixes the "cast" on the left side of the expression. The right side still has "casts". Sorry for the wording. – dzookatz Jul 23 '21 at 11:44