Выровнять отношения один-ко-многим с помощью динамического LINQ - PullRequest
2 голосов
/ 14 мая 2009

Можно ли сгладить отношения один ко многим с помощью динамического 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. В эту операцию будет вовлечен большой объем данных, и производительность будет критической.

Любые предложения приветствуются!

Ответы [ 6 ]

1 голос
/ 30 мая 2009

Это должно сделать это. Сглаживание обычно выполняется методом расширения SelectMany, но в этом случае я использую выражение let. Код для удаления нулевых настроек немного некрасив и может быть улучшен, но он работает:

        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,
            };
1 голос
/ 14 мая 2009

Я знаю, что это не совсем отвечает на ваш вопрос, но компилирование строк в новые классы во время выполнения, как это делает 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);
}
0 голосов
/ 30 мая 2009

Я думаю, что прагматический ответ здесь состоит в том, чтобы сказать, что вы пытаетесь заставить C # стать динамическим языком, и любое решение будет действительно подталкивать C # к его пределам. Похоже, вы пытаетесь преобразовать запрос к базе данных столбцов, которые определяются только во время запроса, в коллекцию, которая основана на этих столбцах и определяется во время выполнения.

Связывание Linq и Gridview действительно красиво и лаконично, и все, кроме того, вам нужно задуматься о том, чтобы взвесить выгоду от работы этого решения по изгибу компилятора, чтобы вам не пришлось самостоятельно генерировать строки и столбцы gridview. 1003 *

Также, если вы беспокоитесь о производительности, я бы подумал о создании необработанного HTML. Опираясь на элементы управления на основе коллекции WebForms для эффективного отображения больших наборов данных, может оказаться рискованным.

Вы добавляете пару событий OnItemDataBound, и бокс и распаковка действительно скроют работу. Я также предполагаю, что вы захотите добавить интерактивные кнопки и текстовые поля к строкам, и выполнение 1000 FindControls никогда не было быстрым.

0 голосов
/ 30 мая 2009

Возможно, есть более эффективные способы сделать это, но чтобы ответить на ваш вопрос, я придумал следующий код. (Обратите внимание, что я никогда не работал с 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;
}
0 голосов
/ 15 мая 2009

К сожалению

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

не будет работать. Когда вы используете DLINQ Select (string), самая сильная информация о классе времени компиляции, которую вы имеете, это 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;
0 голосов
/ 14 мая 2009

Мое лучшее предложение - не использовать динамический LINQ, а добавить класс flatuser, а затем перебирать пользователей. Код для этого прост, и если бы вы смогли получить запрос linq с похожими результатами, он сгенерировал бы такой же код, хотя вы не можете сказать, насколько оптимизированным он будет, поскольку он может включать некоторые объединения, которые повлекут за собой производительность. штраф вместо просто зацикливания. Если вы извлекаете это из базы данных, используя LINQ to SQL, то вы можете использовать отношение сущностей для получения данных, используя linq вместо этого цикла.

Loop:

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...