Повернути результати анонімного типу?


194

Використовуючи простий приклад нижче, який найкращий спосіб повернути результати з декількох таблиць за допомогою Linq в SQL?

Скажіть, у мене дві таблиці:

Dogs:   Name, Age, BreedId
Breeds: BreedId, BreedName

Я хочу повернути всіх собак зі своїми BreedName. Я мав би всіх собак використовувати щось подібне без проблем:

public IQueryable<Dog> GetDogs()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select d;
    return result;
}

Але якщо я хочу собак з породами і спробувати це, у мене є проблеми:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result;
}

Тепер я розумію, що компілятор не дозволить мені повернути набір анонімних типів, оскільки очікує на Собак, але чи є спосіб повернути це без створення спеціального типу? Або мені потрібно створити власний клас для DogsWithBreedNamesта вказати цей тип у виділеному? Або є інший простіший спосіб?


Навіщо з цікавості, чому всі приклади Linq показують, використовуючи анонімні типи, якщо вони не працюють. Наприклад, цей приклад робитьforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot лиже

@Hot Licks - таблиця Клієнта в цих прикладах являє собою сутність, представлену класом. Приклад просто не відображає визначення цих класів.
Джонатан С.

Також не йдеться про те, що компілятор компілятора замінює "var" назвою класу.
Гарячі лизи

Відповіді:


213

Я схильний іти за цією схемою:

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public IQueryable<DogWithBreed> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new DogWithBreed()
                        {
                            Dog = d,
                            BreedName = b.BreedName
                        };
    return result;
}

Це означає, що у вас є додатковий клас, але його швидко та легко кодувати, легко розширювати, використовувати багаторазово та безпечно для типу.


Мені подобається такий підхід, але зараз я не впевнений, як відобразити ім'я собаки. Якщо я прив'язую результат до DataGrid, чи можу я отримати властивості від Dog, не визначаючи їх явно в класі DogWithBreed, або мені потрібно створити геттер / сеттер для кожного поля, яке я хочу відобразити?
Джонатан С.

4
Чи не дозволяють DataGrids вказати властивість як "Dog.Name"? Я зараз забуваю, чому я їх ненавиджу досить ніколи не використовувати їх ...
teedyay

@JonathanS. як ти зробив це у стовпчику шаблонів? Скажіть, будь ласка, я в подібній ситуації
rahularyansharma

Гей, мені подобається цей метод інкапсуляції двох класів в один. Це полегшує роботу. Не кажучи вже про створення простих класів, які використовуватимуться лише в сучасному контексті, роблять це більш чистим.
Лінгер

6
Це насправді не відповідає на питання, яке було в ОП, "чи є спосіб повернути це без створення спеціального типу"?
tjscience

69

Ви можете повернути анонімні типи, але це насправді не дуже .

У цьому випадку я думаю, що було б набагато краще створити відповідний тип. Якщо він буде використовуватися лише з типу, що містить метод, зробіть його вкладеним типом.

Особисто я хотів би, щоб C # отримав "названі анонімні типи" - тобто таку саму поведінку, як анонімні типи, але з іменами та деклараціями властивостей, але це все.

EDIT: Інші пропонують повернути собак, а потім отримати доступ до назви породи через шлях власності тощо. Це абсолютно розумний підхід, але IME призводить до ситуацій, коли ви робили запит певним чином через дані, які ви хочете використання - а метаінформація втрачається, коли ви просто повертаєтесь IEnumerable<Dog>- запит може очікувати, що ви будете використовувати (скажімо), Breedа не Ownerчерез деякі варіанти завантаження тощо, але якщо ви це забудете і почнете використовувати інші властивості, ваш додаток може працювати, але не так ефективно, як ви раніше передбачали. Звичайно, я можу говорити про сміття, або надмірно оптимізувати тощо.


3
Гей, я не є тим, хто не бажає функцій через страх перед тим, як їх зловживають, але чи можете ви уявити види грубого коду, який ми побачимо, якщо вони дозволять передавати названі анонімні типи? (тремтіння)
Дейв Маркл

19
Ми можемо побачити деякі зловживання. Ми також можемо побачити якийсь набагато простіший код, де ми просто хочемо кортежу. Не все має бути об’єктом зі складною поведінкою. Іноді "просто дані" - це Правильна річ. ІМО, звичайно.
Джон Скіт

1
Дякуємо, тож ваша перевага - створити типи, навіть якщо це одноразовий вигляд, такий як цей? У мене є багато звітів, які нарізають одні й ті самі дані по-різному і сподіваються не потрібно створювати всі ці різні типи (DogsWithBreeds, DogsWithOwnerNames тощо)
Джонатан С.

1
Я б намагався не нарізати його дуже багато способів, або розміщувати частину нарізки на місце, де потрібні дані, щоб ви могли використовувати анонімні типи, але поза цим, так. Це смокче певним чином, але таке я боюсь :(
Джон Скіт

17

Тільки, щоб додати свої два центи :-) Нещодавно я навчився способу поводження з анонімними об’єктами. Він може використовуватися лише при націлюванні на .NET 4 фреймворк і лише при додаванні посилання на System.Web.dll, але тоді це досить просто:

...
using System.Web.Routing;
...

class Program
{
    static void Main(string[] args)
    {

        object anonymous = CallMethodThatReturnsObjectOfAnonymousType();
        //WHAT DO I DO WITH THIS?
        //I know! I'll use a RouteValueDictionary from System.Web.dll
        RouteValueDictionary rvd = new RouteValueDictionary(anonymous);
        Console.WriteLine("Hello, my name is {0} and I am a {1}", rvd["Name"], rvd["Occupation"]);
    }

    private static object CallMethodThatReturnsObjectOfAnonymousType()
    {
        return new { Id = 1, Name = "Peter Perhac", Occupation = "Software Developer" };
    }
}

Для того, щоб мати змогу додати посилання на System.Web.dll, вам доведеться дотримуватися порад rushonerok : Переконайтесь, що цільовою рамкою вашого [проекту] є ".NET Framework 4" not ".NET Framework 4 Profile Client".


2
ASP.NET Mvc School;)
T-moty

8

Ні, ви не можете повернути анонімні типи, не переглянувши хитрість.

Якщо ви не використовували C #, те, що ви шукали (повернення декількох даних без конкретного типу), називається Tuple.

Є безліч C # кортежних реалізацій, використовуючи показане тут , ваш код буде працювати так.

public IEnumerable<Tuple<Dog,Breed>> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new Tuple<Dog,Breed>(d, b);

    return result;
}

І на телефоні, що телефонує:

void main() {
    IEnumerable<Tuple<Dog,Breed>> dogs = GetDogsWithBreedNames();
    foreach(Tuple<Dog,Breed> tdog in dogs)
    {
        Console.WriteLine("Dog {0} {1}", tdog.param1.Name, tdog.param2.BreedName);
    }
}

7
Це не працює. Кидає NotSupportedException : тільки без параметрів Конструктори і ініціалізатор підтримуються в LINQ до Entities
mshsayem

1
Правда, клас Tuple не має конструктора за замовчуванням і таким чином не працюватиме правильно з LINQ для Entities. Однак це чудово працює з LINQ для SQL, як у питанні. Я не пробував цього, але це може працювати ... select Tuple.Create(d, b).
joshperry

1
Оскільки Tuples не підтримується деякими постачальниками LINQ, ви не могли вибрати анонімний тип, перетворити його в IEnumerable, а потім вибрати кортеж із цього?
TehPers

8

Ви можете зробити щось подібне:


public System.Collections.IEnumerable GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result.ToList();
}

8

Ви повинні ToList()спочатку використовувати метод, щоб взяти рядки з бази даних, а потім вибрати елементи як клас. Спробуйте це:

public partial class Dog {
    public string BreedName  { get; set; }}

List<Dog> GetDogsWithBreedNames(){
    var db = new DogDataContext(ConnectString);
    var result = (from d in db.Dogs
                  join b in db.Breeds on d.BreedId equals b.BreedId
                  select new
                  {
                      Name = d.Name,
                      BreedName = b.BreedName
                  }).ToList()
                    .Select(x=> 
                          new Dog{
                              Name = x.Name,
                              BreedName = x.BreedName,
                          }).ToList();
return result;}

Отже, фокус спочаткуToList() . Він одразу робить запит і отримує дані з бази даних. Другий трюк - вибір елементів та використання ініціалізатора об'єктів для генерації нових об'єктів із завантаженими елементами.

Сподіваюся, це допомагає.


8

У C # 7 тепер можна використовувати кортежі! ..., що виключає необхідність створення класу лише для повернення результату.

Ось зразок коду:

public List<(string Name, string BreedName)> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
             join b in db.Breeds on d.BreedId equals b.BreedId
             select new
             {
                Name = d.Name,
                BreedName = b.BreedName
             }.ToList();

    return result.Select(r => (r.Name, r.BreedName)).ToList();
}

Можливо, вам знадобиться встановити нульовий пакет System.ValueTuple.


4

Тепер я розумію, що компілятор не дозволить мені повернути набір анонімних типів, оскільки очікує на Собак, але чи є спосіб повернути це без створення спеціального типу?

Використовуйте використання об'єкт для повернення списку анонімних типів без створення спеціального типу. Це буде працювати без помилки компілятора (в .net 4.0). Я повернув список клієнтові і потім проаналізував його на JavaScript:

public object GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result;
}

1
На мою думку, було б більш правильним і читабельним, якби підпис вашого методу виглядав так: public IEnumerable <object> GetDogsWithBreedNames ()
пістолет-

3

Просто виберіть собак, а потім використовуйте dog.Breed.BreedName, це повинно працювати добре.

Якщо у вас багато собак, використовуйте DataLoadOptions.LoadWith, щоб зменшити кількість db-дзвінків.


2

Ви не можете повернути анонімні типи безпосередньо, але ви можете зафіксувати їх за допомогою загального методу. Так само і у більшості методів розширення LINQ. Тут немає ніякої магії, хоча, схоже, вони повернуть анонімні типи. Якщо параметр є анонімним, результат також може бути анонімним.

var result = Repeat(new { Name = "Foo Bar", Age = 100 }, 10);

private static IEnumerable<TResult> Repeat<TResult>(TResult element, int count)
{
    for(int i=0; i<count; i++)
    {
        yield return element;
    }
}

Нижче прикладу на основі коду з оригінального запитання:

var result = GetDogsWithBreedNames((Name, BreedName) => new {Name, BreedName });


public static IQueryable<TResult> GetDogsWithBreedNames<TResult>(Func<object, object, TResult> creator)
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                    join b in db.Breeds on d.BreedId equals b.BreedId
                    select creator(d.Name, b.BreedName);
    return result;
}

0

Що ж, якщо ви повернете Собак, ви зробите:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    return from d in db.Dogs
           join b in db.Breeds on d.BreedId equals b.BreedId
           select d;
}

Якщо ви хочете, щоб Порода не потребувала завантаження і не ледача, просто використовуйте відповідну конструкцію DataLoadOptions .


0

BreedIdв Dogтаблиці, очевидно, зовнішній ключ до відповідного рядка в Breedтаблиці. Якщо ваша база даних налаштована належним чином, LINQ до SQL повинен автоматично створити зв'язок між двома таблицями. Отриманий клас собак матиме властивість Порода, а клас Порода повинен мати колекцію Собаки. Встановивши це таким чином, ви все одно можете повернутися IEnumerable<Dog>, що є об’єктом, що включає властивість породи. Єдине застереження полягає в тому, що вам потрібно попередньо завантажити об'єкт породи разом із об'єктами собаки в запиті, щоб вони могли отримати доступ після того, як контекст даних буде розміщений, і (як запропонував інший плакат) застосувати метод колекції, який спричинить запит, який потрібно виконати негайно (у цьому випадку ToArray):

public IEnumerable<Dog> GetDogs()
{
    using (var db = new DogDataContext(ConnectString))
    {
        db.LoadOptions.LoadWith<Dog>(i => i.Breed);
        return db.Dogs.ToArray();
    }

}

Потім доступ до породи для кожної собаки тривіально:

foreach (var dog in GetDogs())
{
    Console.WriteLine("Dog's Name: {0}", dog.Name);
    Console.WriteLine("Dog's Breed: {0}", dog.Breed.Name);        
}

0

Якщо основна ідея полягає у тому, щоб зробити оператор вибору SQL, що надсилається на сервер бази даних, матимуть лише необхідні поля, а не всі поля Entity, тоді ви можете це зробити:

public class Class1
{
    public IList<Car> getCarsByProjectionOnSmallNumberOfProperties()
    {

        try
        {
            //Get the SQL Context:
            CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext dbContext 
                = new CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext();

            //Specify the Context of your main entity e.g. Car:
            var oDBQuery = dbContext.Set<Car>();

            //Project on some of its fields, so the created select statment that is
            // sent to the database server, will have only the required fields By making a new anonymouse type
            var queryProjectedOnSmallSetOfProperties 
                = from x in oDBQuery
                    select new
                    {
                        x.carNo,
                        x.eName,
                        x.aName
                    };

            //Convert the anonymouse type back to the main entity e.g. Car
            var queryConvertAnonymousToOriginal 
                = from x in queryProjectedOnSmallSetOfProperties
                    select new Car
                    {
                        carNo = x.carNo,
                        eName = x.eName,
                        aName = x.aName
                    };

            //return the IList<Car> that is wanted
            var lst = queryConvertAnonymousToOriginal.ToList();
            return lst;

        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.ToString());
            throw;
        }
    }
}

0

Спробуйте це отримати динамічні дані. Ви можете конвертувати код для Список <>

public object GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result.FirstOrDefault();
}

dynamic dogInfo=GetDogsWithBreedNames();
var name = dogInfo.GetType().GetProperty("Name").GetValue(dogInfo, null);
var breedName = dogInfo.GetType().GetProperty("BreedName").GetValue(dogInfo, null);

0

Якщо у вашій базі даних є встановлення відносин із обмеженим обмеженням ключа на BreedId, ви цього ще не отримали?

Відображення відносин DBML

Тож я зараз можу зателефонувати:

internal Album GetAlbum(int albumId)
{
    return Albums.SingleOrDefault(a => a.AlbumID == albumId);
}

І в коді, який закликає:

var album = GetAlbum(1);

foreach (Photo photo in album.Photos)
{
    [...]
}

Тож у вашому випадку ви б кликали щось на зразок dog.Breed.BreedName - як я вже сказав, це покладається на вашу базу даних, створену з цими відносинами.

Як уже згадували інші, DataLoadOptions допоможе зменшити виклики до бази даних, якщо це проблема.


0

Це не точно відповідає вашому запитанню, але Google привів мене сюди на основі ключових слів. Ось як можна запитати анонімний тип зі списку:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.