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

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

Ответ 1

Я склоняюсь к этому шаблону:

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

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

Ответ 2

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

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

Лично я хотел бы, чтобы С# получал "named anonymous types" - то есть такое же поведение, что и анонимные типы, но с именами и объявлениями свойств, но это.

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

Ответ 3

Просто чтобы добавить стоимость моих двух центов:-) Недавно я узнал способ обработки анонимных объектов. Его можно использовать только при таргетинге на платформу .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".

Ответ 4

Нет, вы не можете возвращать анонимные типы, не пройдя какие-то обманы.

Если вы не использовали С#, то, что вы искали бы (возврат нескольких данных без конкретного типа), называется Tuple.

Существует множество реализаций кортежей С#, используя приведенный здесь ваш код будет работать следующим образом.

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

Ответ 5

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

Надеюсь, что это поможет.

Ответ 6

Вы можете сделать что-то вроде этого:


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

Ответ 7

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

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

Ответ 8

Просто выберите собак, затем используйте dog.Breed.BreedName, это должно работать нормально.

Если у вас много собак, используйте DataLoadOptions.LoadWith, чтобы уменьшить количество вызовов db.

Ответ 9

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

Ответ 10

Если у вас есть настройка отношений в вашей базе данных с ограничением ключа foriegn на BreedId, вы уже не получили это?

Отображение отношений DBML http://www.doodle.co.uk/userfiles/image/relationship.png

Итак, теперь я могу позвонить:

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 поможет уменьшить вызовы базы данных, если это проблема.

Ответ 11

Хорошо, если вы возвращаетесь к собакам, вы бы сделали:

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 был загружен и не был ленивым, просто используйте конструкцию

Ответ 12

BreedId в таблице Dog, очевидно, является внешним ключом к соответствующей строке в таблице Breed. Если ваша база данных настроена правильно, LINQ to SQL должна автоматически создавать связь между этими двумя таблицами. Полученный класс Dog будет иметь свойство породы, а класс породы должен иметь коллекцию собак. Создав этот способ, вы все равно можете вернуть 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 Name: {0}", dog.Name);
    Console.WriteLine("Dog Breed: {0}", dog.Breed.Name);        
}

Ответ 13

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

Ответ 14

В С# 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 nuget.