1

Let's say I have a class:

public class Customer
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

And now I want to create a generic Get() method that might query Customer or any one of several other classes that also have a [key] field defined.

public T Get<T>(int id)
{            
    string json = DoSomething(); // <-- making it easy for this post
    List<T> items = JsonConvert.DeserializeObject<List<T>>(json);            
    return items.FirstOrDefault(i => i. ????? = id);
}

I'm not sure how to use Linq to generically specify the [key] field.

Thanks!

Ray Krungkaew
  • 6,652
  • 1
  • 17
  • 28
Casey Crookston
  • 13,016
  • 24
  • 107
  • 193

2 Answers2

1

Hope this helps:

public interface IBase
{
    int Id { get; }
}
public class Customer : IBase
{
    public string Name { get; set; }
    public int Id { get ; set ; }
}
public T Get<T>(int id) where T : IBase
{
    string json = DoSomething(); // <-- making it easy for this post
    List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
    return items.FirstOrDefault(i => i.Id == id);
}

Just implement the interface IBase in all other classes.

Sadique
  • 22,572
  • 7
  • 65
  • 91
1

For what is worth I think using contracts is a better way to solve this. But in case you or someone else actually need to check for the attribute here's the answer:

public static T Get<T>(int id)
{
    string json = DoSomething(); // <-- making it easy for this post
    List<T> items = JsonConvert.DeserializeObject<List<T>>(json);

    return items.FirstOrDefault(
        item => (int)item.GetType()
                         .GetProperties()
                         .FirstOrDefault(
                            p => Attribute.IsDefined(p, typeof(KeyAttribute))
                         ).GetValue(item) == id
    );
}

As far a this part of your question:

I'm not sure how to use Linq to generically specify the [key] field.

The attribute is KeyAttribute you can know that by navigating to the definition (pressing F12 if you're using VS or checking the docs in case your editor doesn't support this feature.

Things to consider:

  • this is using Reflection reasonably heavily, so it will never have the best performance. That being said you can cache the result from GetProperties() somewhere for faster lookups.
  • It's hardcoding the cast to int but it appears that's what you're after.
  • If the collection is null it'll throw an exception.
  • So you would be reflecting on every query? When just using Naming convention you can achieve compile time goodness that the `Id` property will be generally the Key attribute. Anyways its clear the question wanted a Compile time solution. https://stackoverflow.com/questions/26521670/type-constraints-in-attributes - And btw there is `NO CAST` anywhere – Sadique Nov 07 '19 at 08:18
  • Yes. It would of course use reflection every time you call the method. The question asked for the presence of an attribute, which cannot be done at compile time at the moment. And there is a cast, in the return statement. –  Nov 07 '19 at 08:32
  • `There is no cast`. The parameter itself in the `Get` method is an `int`. Look carefully. – Sadique Nov 07 '19 at 08:36
  • Then how do you call this? (int)item.GetType()..…? Look carefully at the (int) part. –  Nov 07 '19 at 08:36
  • I meant there is no cast needed. `class Class1 : IBase { public int Id { get; } void meth() { Get(1); }` – Sadique Nov 07 '19 at 08:40
  • So now we went from there is no cast to not needed. Ok. Have it your way sir. I added this example because OP asked for it and it could help others, not to have a discussion about pointless remarks. –  Nov 07 '19 at 08:42
  • I realized it later what you meant in your answer, sorry about that. – Sadique Nov 07 '19 at 08:42