-3

I have Manufacturer entity as below:

class Manufacturer
{
    [Key]
    public int Id { get; set; }

    [DisplayName("Manufacturer Code")]
    public int Code { get; set; }

    [DisplayName("Manufacturer Name")]
    public string Name { get; set; }
}

As you see every filed have DisplayName data annotations. In Normal way I select the rows of Manufacturer table by below code:

dataGridView1.DataSource = DatabaseContext.Set<Manufacturer>()
    .Select(m => new 
    {
        Id = m.Id,
        Code = m.Code,
        Name = m.Name,
    })
    .ToList();

I want to find a way that Dynamically put DisplayName as alias in Linq query.

I think I must a Method that generate the query something like:

dataGridView1.DataSource = DatabaseContext.Set<Manufacturer>()
    .Select(m => new 
    {
        Id = m.Id,
        [Manufacturer Code] = m.Code,
        [Manufacturer Name] = m.Name,
    })
    .ToList();

I could get the all DisplayName by below code:

public static Dictionary<string, string> GetEntityDisplayName(this Type type)
{
    return TypeDescriptor.GetProperties(type)
        .Cast<PropertyDescriptor>()
        .ToDictionary(p => p.Name, p => p.DisplayName);
}

But dont know how do that. Is there any way to put DisplayName as alias of Linq query dynamically?

Update: As one of answer say when use .ToList in get rows from an entity it return a list that project the model with DisplayNameAttribute, But the new thing is When create Linq query that use 2 entity, to list project the row that you exactly say in query. For example:

class Manufacturer
    {
        [Key]
        public int Id { get; set; }

        [DisplayName("Manufacturer Code")]
        public int Code { get; set; }

        [DisplayName("Manufacturer Name")]
        public string Name { get; set; }
    }

And:

class Good
{
    [Key]
    [DisplayName("ID")]
    public int Id { get; set; }

    [DisplayName("Good Name")]
    public string Name { get; set; }

    public int? ManufacturerId { get; set; }

    public virtual Manufacturer Manufacturer { get; set; }
}

And The Query:

using (AppDbContext db = new AppDbContext())
            {
                var res2 = db.Goods.Select(m => new
                {
                    Id = m.Id,
                    GoodsName = m.Name,
                    ManufacturerName = m.Manufacturer.Name,
                }).ToList();

                dataGridView1.DataSource = res2;
            }

As you see in this case, because the query have 2 name field, must declare different alias for them and its not the equal to DisplayNameAttribute in entity. Are anyone know a way to project the output list same as DisplayNameAttribute defined in entity?

Ali
  • 3,373
  • 5
  • 42
  • 54
  • 1
    Would this suit your requirements? https://stackoverflow.com/a/27275258/3296133 - the method in this post takes a type, and creates a datatable which you can then bind to the datagridview as required. – DeeKayy90 Jul 18 '17 at 06:20
  • @DeeKayy90 I want to put the alias in Linq query... do have suggestion for that? – Ali Jul 18 '17 at 06:33
  • 2
    I think the question is more why do you want an alias in your LINQ query? your query is presumably going to a database so you need the actual `Name` field to get translated to SQL. I think you should be looking for translating the result of you query if that's your goal? – dougajmcdonald Jul 18 '17 at 06:37
  • Can you share a desired LINQ query with those aliases in use? Also to be able to use them in LINQ-to-SQL you have to write your own query provider to take into account those aliases. – Mihail Stancescu Jul 18 '17 at 06:38
  • 1
    @combo_ci The linked post would allow you to create your list of required fields (as you already do), then instead of binding the datagridview to your list, you can bind it to a datatable - the one returned from that method. All you would need to provide is the list, and it would return the table with the `DisplayName` as the column name (i.e. alias) – DeeKayy90 Jul 18 '17 at 06:42
  • @DeeKayy90 Of course your suggested way may be could solve the problem in other way but My goal is generate the linq query (with display name) dynamically. – Ali Jul 18 '17 at 06:50
  • 1
    Anonymous type member names must be valid C# identifiers so no spaces are allowed. Any alternative you would like? – NetMage Jul 19 '17 at 21:44

1 Answers1

1

After looking at the DataGridView source, I've seen that the datasource is bound to a collection of objects that have properties with DisplayNameAttribute on them it will display those attributes values in the column header of that property.

So, you only need to project your LINQ queries to view models or entities that have defined the attribute.

For example, I have the following code:

var listOfEntities = new List<CustomEntity>
{
      new CustomEntity {Id = 1, Name = "Name1", Value = "Value1"},
      new CustomEntity {Id = 2, Name = "Name2", Value = "Value2"},
      new CustomEntity {Id = 3, Name = "Name3", Value = "Value3"},
      new CustomEntity {Id = 4, Name = "Name4", Value = "Value4"},
};
dataGridView1.DataSource = listOfEntities;

public class CustomEntity
{
    public int Id { get; set; }
    [DisplayName("Custom name header")]
    public string Name { get; set; }
    [DisplayName("Custom name value")]
    public string Value { get; set; }
}

If you run this you will see that the column headers are those from the DisplayNameAttribute, not the properties name.

To project to view models you can use AutoMapper for automatic mapping of fields.

UPDATE

You can probably achieve this behavior using an Expando object. Let me warn you though you can not use this until you call .ToList() on your context. Because this will not translate by default to a valid SQL query.

using System.Reflection;
/*...*/
public static object ToDynamicDisplayName(object input)
{
    var type = input.GetType();
    dynamic dObject = new ExpandoObject();
    var dDict = (IDictionary<string, object>)dObject;

    foreach (var p in type.GetProperties())
    {
        var prop = type.GetProperty(p.Name);
        var displayNameAttr = p.GetCustomAttribute<DisplayNameAttribute>(false);
        if (prop == null || prop.GetIndexParameters().Length != 0) continue;
        if (displayNameAttr != null)
        {
            dDict[displayNameAttr.DisplayName] = prop.GetValue(input, null);
        }
        else
            dDict[p.Name] = prop.GetValue(input, null);
    }
    return dObject;
}

To use it try it like this:

var myDynamicCollection = DatabaseContext.Set<Manufacturer>().ToList().Select(m => 
                          ToDynamicDisplayName(m));

UPDATE 2

Of course I've ran the code and built successfully. My set-up is VS2017 with .NET 4.6.2. But, I've also created a .Net Fiddle for it, it's available here.

MJK
  • 3,434
  • 3
  • 32
  • 55
Mihail Stancescu
  • 4,088
  • 1
  • 16
  • 21
  • My question is _any way to put DisplayName as alias of Linq query dynamically_, is your answer do that?? – Ali Jul 18 '17 at 07:25
  • My answer says you don't have to do that, you have only to define the attribute on properties you want to display and it will work automatically. – Mihail Stancescu Jul 18 '17 at 07:26
  • Yes its work automatically..but i want to find a way that put DisplayName as alias in Linq query ..if you have a suggestion please tell to me. thanks – Ali Jul 18 '17 at 07:37
  • Why would you want to work with Dynamic types instead of ViewModels? I'm sorry, maybe I don't fully understand what you are trying to achieve. – Mihail Stancescu Jul 18 '17 at 07:41
  • What I'm saying is, let me know why you want to do that, so I know how to approach this. – Mihail Stancescu Jul 18 '17 at 07:56
  • Dear Mihail, I know way that say in answer and use it in my application... But want to find _a way to put DisplayName as alias of Linq query dynamically_ ...Maybe because I want to know more about the Linq ... and it reason i set _100 reputation_ to find this answer – Ali Jul 18 '17 at 08:15
  • 7
    @combo_ci Putting a bounty does not make the question better. If by *dynamically* you mean anonymous type projection, apparently you can't (except you write your own compiler) because anonymous types are compiler feature and are identified by the name, type and order of the properties. Creating a concrete class for projection is viable and right solution for me. – Ivan Stoev Jul 18 '17 at 09:08
  • When I run put the ToDynamicDisplayName in my app give the error _'PropertyInfo' does not contain a definition for 'GetCustomAttribute' and no extension method 'GetCustomAttribute' accepting a first argument of type 'PropertyInfo' could be found (are you missing a using directive or an assembly reference?)_ – Ali Jul 18 '17 at 11:00
  • 1
    @IvanStoev about _Putting a bounty does not make the question better_ I could say i dont put bounty to _Convert the question to good question_, Sometime (as the SO put in the bounty page) some reason like _This question has not received enough attention_ lead to put bounty on question..BTW thanks for your attention Ivan :) – Ali Jul 18 '17 at 11:57
  • @combo_ci Well, putting a bounty brings attention for sure :) But here I'm just supporting the above answer (before the update, because EF does not support projection to dynamic objects and WF data binding does not work with them either). – Ivan Stoev Jul 18 '17 at 12:26
  • @MihailStancescu did you try put `ToDynamicDisplayName` result as `datagrideview` source? – Ali Jul 19 '17 at 07:19
  • I haven't, because there is no need for that, for that you can use the first example. The `ToDynamiceDisplayName` is useful if you want to use that collection in other use cases (e.g. export it, save to csv, to `DataTable`, etc.), not bind it directly to the `datagridview`. – Mihail Stancescu Jul 19 '17 at 07:28