Вернуть результаты анонимного типа?

194

Используя простой пример ниже, каков наилучший способ вернуть результаты из нескольких таблиц, используя Linq to 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;
}

Теперь я понимаю, что компилятор не позволит мне вернуть набор анонимных типов, поскольку он ожидает Dogs, но есть ли способ вернуть это без необходимости создания пользовательского типа? Или я должен создать свой собственный класс DogsWithBreedNamesи указать этот тип в select? Или есть другой способ проще?

Джонатан С.
источник
Просто из любопытства, почему все примеры Linq показывают использование анонимных типов, если они не работают. Например, этот пример делаетforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks
@Hot Licks - таблица Customer в этих примерах является сущностью, представленной классом. Пример просто не показывает определения этих классов.
Джонатан С.
Он также не говорит вам, что swizzle компилятора заменяет «var» именем класса.
Hot Licks

Ответы:

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

Это означает, что у вас есть дополнительный класс, но его можно легко и быстро кодировать, легко расширять, использовать повторно и сохранять тип.

teedyay
источник
Мне нравится такой подход, но теперь я не уверен, как отобразить имя собаки. Если я связываю результат с DataGrid, могу ли я получить свойства от Dog, не определяя их явно в классе DogWithBreed, или мне нужно создать метод получения / установки для каждого поля, которое я хочу отобразить?
Джонатан С.
4
Разве DataGrids не позволяют указывать свойство как «Dog.Name»? Теперь я забыл, почему я ненавижу их настолько, что никогда не использую их ...
teedyay
@JonathanS. как ты это сделал в шаблонной колонке? пожалуйста, скажи мне, что я нахожусь в подобной ситуации
rahularyansharma
Эй, мне нравится этот метод инкапсуляции двух классов в один. С ним легко работать. Не говоря уже о создании простых классов, которые будут использоваться только в текущем контексте, сделать его чище.
задержка
6
Это действительно не отвечает на вопрос, который имел ОП, «есть ли способ вернуть это без необходимости создания пользовательского типа»?
tjscience
69

Вы можете возвращать анонимные типы, но это действительно не красиво .

В этом случае я думаю, что было бы гораздо лучше создать соответствующий тип. Если он будет использоваться только из типа, содержащего метод, сделайте его вложенным.

Лично я хотел бы, чтобы C # получал «именованные анонимные типы» - то есть такое же поведение, что и анонимные типы, но с именами и объявлениями свойств, но это все.

РЕДАКТИРОВАТЬ: Другие предлагают вернуть собак, а затем получить доступ к названию породы через путь свойства и т. Д. Это совершенно разумный подход, но 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», а не «.NET Framework 4 Client Profile».

Петер Перхач
источник
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);
    }
}
joshperry
источник
7
Это не работает. Выдает исключение NotSupportedException : в LINQ to Entities поддерживаются только конструкторы и инициализаторы без параметров
mshsayem,
1
Правда, класс Tuple не имеет конструктора по умолчанию и не будет корректно работать с LINQ to Entities таким образом. Однако он работает нормально с LINQ to SQL, как в вопросе. Я не пробовал это, но это может сработать ... select Tuple.Create(d, b).
Джошперри
1
Поскольку кортежи не поддерживаются некоторыми поставщиками 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();
}
tjscience
источник
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();
}

Возможно, вам придется установить пакет nuget System.ValueTuple.

Росди Касим
источник
4

Теперь я понимаю, что компилятор не позволит мне вернуть набор анонимных типов, поскольку он ожидает Dogs, но есть ли способ вернуть это без необходимости создания пользовательского типа?

Используйте use object для возврата списка анонимных типов без создания пользовательского типа. Это будет работать без ошибки компилятора (в .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, чтобы уменьшить количество вызовов БД.

Андрей Щекин
источник
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;
}

Если вы хотите, чтобы Breed загружался с полной отдачей, а не с отложенной загрузкой, просто используйте соответствующую конструкцию DataLoadOptions .

Дейв Маркл
источник
0

BreedIdв Dogтаблице, очевидно, внешний ключ к соответствующей строке в Breedтаблице. Если ваша база данных настроена правильно, LINQ to SQL должен автоматически создать связь между двумя таблицами. Полученный класс Dog будет иметь свойство Breed, а класс Breed должен иметь коллекцию Dogs. Настроив его таким образом, вы все равно можете вернуть 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);        
}
kad81
источник
0

Если основная идея состоит в том, чтобы оператор SQL select, отправляемый на сервер базы данных, имел только обязательные поля, а не все поля 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);
Yargicx
источник
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});
Даниил
источник