使用动态LINQ展平一对多关系

时间:2009-05-14 15:40:42

标签: c# asp.net linq linq-to-sql gridview

是否可以使用动态LINQ压缩一对多关系?

例如,我可能有一个Users列表,User类包含许多UserPreferences的列表。 UserPreference类本质上是一个名称/值对。

用户将定义一组用户可用的用户首选项类型。

public class User
{
    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }

    public IList<UserPreference> UserPreferences
    {
        get;
        set;
    }
}

public class UserPreference
{
    public UserPreference(string name, object userValue)
    {
        this.Name = name;
        this.UserValue = userValue;
    }

    public string Name
    {
        get;
        set;
    }

    public object UserValue
    {
        get;
        set;
    }
}

因此,可以通过以下方式定义一个用户组:

List<User> users = new List<User>();

User user1 = new User();
user1.FirstName = "John";
user1.LastName = "Doe";

user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));

User user2 = new User();
user2.FirstName = "Jane";
user2.LastName = "Doe";

user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));

users.Add(user1);
users.Add(user2);

return users;

所需的输出是:

First Name      Last Name       Favorite Color      Favorite Mammal
John        Doe         Red         NULL
Jane        Doe         Blue            Dolphin

有没有办法创建一个匿名类型,以便UserPreferences会卷入用户?

例如,

var u = UserScopedSettingAttribute.Select("new (FirstName as FirstName, UserValue as FavoriteColor)", null);

string name = u.FirstName;
string color = u.FavoriteColor;

最终,此用户列表将绑定到ASP.NET GridView Web控件。此操作涉及大量数据,性能至关重要。

任何建议表示赞赏!

6 个答案:

答案 0 :(得分:1)

我知道它并没有完全回答你的问题,但是在运行时将字符串编译成新类就像dlinq一样,它总是有一种难闻的气味。考虑只使用像这样的DataTable,

DataTable prefs = new DataTable();
IEnumerable<DataColumn> cols = (from u in users
                                from p in u.UserPreferences
                                select p.Name)
                               .Distinct()
                               .Select(n => new DataColumn(n));

prefs.Columns.Add("FirstName");
prefs.Columns.Add("LastName");
prefs.Columns.AddRange(cols.ToArray());

foreach (User user in users)
{
    DataRow row = prefs.NewRow();
    row["FirstName"] = user.FirstName;
    row["LastName"] = user.LastName;
    foreach (UserPreference pref in user.UserPreferences)
    {
        row[pref.Name] = pref.UserValue;
    }
    prefs.Rows.Add(row);
}

答案 1 :(得分:1)

这应该这样做。展平通常使用SelectMany扩展方法完成,但在这种情况下我使用let表达式。删除null首选项的代码有点难看,可能会有所改进,但它可以工作:

        var flattenedUsers = from user in GetUsers()
                let favColor = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite color")
                let favMammal = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite mammal")
                select new
                           {
                               user.FirstName,
                               user.LastName,
                               FavoriteColor = favColor == null ? "" : favColor.UserValue,
                               FavoriteMammal = favMammal == null ? "" : favMammal.UserValue,
            };

答案 2 :(得分:0)

我最好的建议是不使用动态LINQ,而是添加一个flatuser类,然后遍历用户。这个代码很简单,如果你能够获得具有类似结果的linq查询,它将生成相同的代码,尽管你无法真正说明它的优化程度,因为它可能涉及一些会导致性能的连接惩罚而不仅仅是循环。如果您使用LINQ to SQL从数据库中提取此数据,那么您可以使用实体关系来使用linq而不是此循环来获取数据。

循环:

List<FlatUser> flatusers = new List<FlatUser>();
foreach (User u in users)
{
    foreach (UserPreference up in u.UserPreferences)
    {
        flatusers.Add(new FlatUser
        {
            FirstName = u.FirstName,
            LastName = u.LastName,
            Name = up.Name,
            UserValue = up.UserValue
        });
    }
}

扁平用户类:

public class FlatUser
{
    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public object UserValue
    {
        get;
        set;
    }
}

答案 3 :(得分:0)

不幸的是

var u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;

不起作用。当您使用DLINQ Select(字符串)时,您拥有的最强编译时类信息是Object,因此u.FirstName将引发编译错误。拉动运行时生成的匿名类的属性的唯一方法是使用反射。虽然,如果你可以等,这可以用C#4.0这样,

dynamic u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;

答案 4 :(得分:0)

可能有更有效的方法来实现这一点,但为了实际回答您的问题,我提出了以下代码。 (请注意,我之前从未使用过DynamicLinq,所以可能有更好的方法来使用它来实现目标。)

我创建了一个控制台应用程序,粘贴在帖子的类中,然后使用以下代码。

static void Main(string[] args)
{
    var users = GetUserGroup();
    var rows = users.SelectMany(x => x.UserPreferences.Select(y => new { x.FirstName, x.LastName, y.Name, y.UserValue }));
    var userProperties = rows.Select(x => x.Name).Distinct();
    foreach (var property in userProperties)
    {
        Console.WriteLine(property);
    }
    Console.WriteLine();

    // The hard-coded variety.
    var results = users.Select(x => new
    {
        x.FirstName,
        x.LastName,
        FavoriteColor = x.UserPreferences.Where(y => y.Name == "Favorite color").Select(y => y.UserValue).FirstOrDefault(),
        FavoriteAnimal = x.UserPreferences.Where(y => y.Name == "Favorite mammal").Select(y => y.UserValue).FirstOrDefault(),
    });

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // The dynamic variety.
    DynamicProperty[] dynamicProperties = new DynamicProperty[2 + userProperties.Count()];
    dynamicProperties[0] = new DynamicProperty("FirstName", typeof(string));
    dynamicProperties[1] = new DynamicProperty("LastName", typeof(string));
    int propIndex = 2;
    foreach (var property in userProperties)
    {
        dynamicProperties[propIndex++] = new DynamicProperty(property, typeof(string));
    }
    Type resultType = ClassFactory.Instance.GetDynamicClass(dynamicProperties);
    ConstructorInfo constructor = resultType.GetConstructor(new Type[] {});
    object[] constructorParams = new object[] { };
    PropertyInfo[] propInfoList = resultType.GetProperties();
    PropertyInfo[] constantProps = propInfoList.Where(x => x.Name == "FirstName" || x.Name == "LastName").OrderBy(x => x.Name).ToArray();
    IEnumerable<PropertyInfo> dynamicProps = propInfoList.Where(x => !constantProps.Contains(x));
    // The actual dynamic results creation.
    var dynamicResults = users.Select(user =>
    {
        object resultObject = constructor.Invoke(constructorParams);
        constantProps[0].SetValue(resultObject, user.FirstName, null);
        constantProps[1].SetValue(resultObject, user.LastName, null);
        foreach (PropertyInfo propInfo in dynamicProps)
        {
            var val = user.UserPreferences.FirstOrDefault(x => x.Name == propInfo.Name);
            if (val != null)
            {
                propInfo.SetValue(resultObject, val.UserValue, null);
            }
        }
        return resultObject;
    });
    //////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Display the results.
    var displayResults = dynamicResults;
    //var displayResults = results;

    if (displayResults.FirstOrDefault() != null)
    {
        PropertyInfo[] properties = displayResults.First().GetType().GetProperties();
        int columnWidth = Console.WindowWidth / properties.Length;

        int index = 0;
        foreach (PropertyInfo property in properties)
        {
            Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
            Console.Write(property.Name);
        }
        Console.WriteLine();

        foreach (var result in displayResults)
        {
            index = 0;
            foreach (PropertyInfo property in properties)
            {
                Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
                Console.Write(property.GetValue(result, null) ?? "(null)");
            }
            Console.WriteLine();
        }
    }

    Console.WriteLine("\r\nPress any key to continue...");
    Console.ReadKey();
}

static List<User> GetUserGroup()
{
    List<User> users = new List<User>();

    User user1 = new User();
    user1.FirstName = "John";
    user1.LastName = "Doe";
    user1.UserPreferences = new List<UserPreference>();

    user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));
    user1.UserPreferences.Add(new UserPreference("Birthday", "Friday"));

    User user2 = new User();
    user2.FirstName = "Jane";
    user2.LastName = "Doe";
    user2.UserPreferences = new List<UserPreference>();

    user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
    user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));

    users.Add(user1);
    users.Add(user2);

    return users;
}

答案 5 :(得分:0)

我认为这里务实的答案是说你试图强迫C#成为一种动态语言,任何解决方案都会真正推动C#达到极限。听起来您试图将仅在查询时确定的列的数据库查询转换为基于这些列并在运行时确定的集合。

Linq和Gridview绑定非常简洁,除了你必须开始考虑权衡使这个编译器弯曲解决方案工作的好处,这样你就不必自己动态生成gridview行和列。 / p>

此外,如果您关注性能,我会考虑生成原始HTML。依靠基于集合的WebForms控件来有效地显示大量数据可能会有点冒险。

你添加了几个OnItemDataBound事件,装箱和拆箱将真正搞砸了工程。我也假设你想要在行中添加交互式按钮和文本框,并且做1000个FindControls从来都不是很快。