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


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

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

14 176

14 ответов:

Я склонен идти по этой схеме:

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

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

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

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

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

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

просто чтобы добавить мои два цента' стоит :-) Недавно я узнал способ обработки анонимных объектов. Он может использоваться только при таргетинге на платформу .NET 4 и только при добавлении ссылки на систему.Сеть.но тогда все довольно просто:

...
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.Сеть.dll вам придется следовать rushonerok это!--5-->: убедитесь, что целевой платформой вашего [проекта] является ".NET Framework 4", а не " клиент .NET Framework 4 Профиль."

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

Если вы не используете C#, то, что вы будете искать (возвращая несколько данных без конкретного типа), называется кортежем.

есть много реализаций кортежа 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);
    }
}

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

надеюсь, что это помогает.

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


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

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

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

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

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

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

Если у вас есть настройка отношений в вашей базе данных с ограничением ключа 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)
{
    [...]
}

Так что в вашем случае вы звоните что-то вроде собаки.Порода.BreedName - как я уже сказал, Это зависит от настройки вашей базы данных это отношение.

Как уже упоминалось, DataLoadOptions поможет уменьшить вызовы базы данных, если это проблема.

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

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

Ну, если вы возвращаете собак, вы бы сделали:

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, позволяющие строительство.

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

Если основная идея состоит в том, чтобы оператор SQL select, отправленный на сервер базы данных, имел только необходимые поля, а не все поля сущности, то u может сделать это:

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