在实体的Linq查询中动态使用DisplayName作为别名

时间:2017-07-15 19:32:38

标签: c# entity-framework linq alias data-annotations

我有Manufacturer实体,如下所示:

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

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

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

如您所见,每个字段都有DisplayName个数据注释。在正常方式中,我通过以下代码选择制造商表的行:

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

我想找到一种方法,在Linq查询中动态地将DisplayName作为别名。

我认为我必须使用一个生成查询的方法:

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

我可以通过以下代码获取所有DisplayName

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

但不知道那是怎么回事。有没有办法动态地将DisplayName作为 Linq查询的别名?

更新 作为回答之一,当使用.ToList来获取实体中的行时,它会返回一个列表,该列表使用DisplayNameAttribute投影模型,但是新的东西是当创建Linq查询时使用2个实体,列出项目您在查询中确切说的行。例如:

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

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

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

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; }
}

和查询:

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;
            }

正如您在本案中所看到的,因为查询有2个名称字段,必须为它们声明不同的别名,而不是等于实体中的DisplayNameAttribute。有没有人知道一种方法来将输出列表与实体中定义的DisplayNameAttribute相同?

1 个答案:

答案 0 :(得分:1)

在查看DataGridView源代码后,我发现数据源绑定到一组对象,这些对象的属性为DisplayNameAttribute,它将在列标题中显示这些属性值该财产。

因此,您只需要将LINQ查询投影到查看已定义属性的模型或实体。

例如,我有以下代码:

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; }
}

如果您运行此列表,您会看到列标题来自DisplayNameAttribute,而不是属性名称。

要投影以查看模型,您可以使用AutoMapper自动映射字段。

<强>更新

您可以使用Expando对象实现此行为。 让我警告你,虽然在你的上下文中调用.ToList()之前你不能使用它。因为默认情况下这不会转换为有效的SQL查询。

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;
}

要使用它,请尝试这样:

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

更新2

当然我运行了代码并成功构建。我的设置是VS2017与.NET 4.6.2。但是,我也为它创建了一个.Net小提琴,它可用here