Trả về kết quả loại ẩn danh?


194

Sử dụng ví dụ đơn giản dưới đây, cách tốt nhất để trả về kết quả từ nhiều bảng bằng Linq sang SQL là gì?

Nói rằng tôi có hai bảng:

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

Tôi muốn trả lại tất cả những con chó với họ BreedName. Tôi sẽ nhận được tất cả những con chó sử dụng một cái gì đó như thế này mà không có vấn đề:

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

Nhưng nếu tôi muốn chó có giống và thử điều này, tôi có vấn đề:

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

Bây giờ tôi nhận ra rằng trình biên dịch sẽ không cho phép tôi trả về một tập hợp các loại ẩn danh vì nó mong đợi Chó, nhưng có cách nào để trả lại cái này mà không phải tạo một loại tùy chỉnh không? Hay tôi phải tạo lớp của riêng mình DogsWithBreedNamesvà chỉ định kiểu đó trong phần chọn? Hoặc có một cách khác dễ dàng hơn?


Vì tò mò, tại sao tất cả các ví dụ Linq hiển thị sử dụng các loại ẩn danh, nếu chúng không hoạt động. Ví dụ: ví dụ này cóforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks

@Hot Licks - Bảng Khách hàng trong các ví dụ đó là một thực thể được đại diện bởi một lớp. Ví dụ này không xuất hiện để hiển thị các định nghĩa của các lớp đó.
Jonathan S.

Nó cũng không cho bạn biết rằng một trình biên dịch swizzle đang thay thế "var" bằng tên lớp.
Licks nóng

Câu trả lời:


213

Tôi có xu hướng đi cho mô hình này:

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

Điều đó có nghĩa là bạn có một lớp học thêm, nhưng nó nhanh chóng và dễ viết mã, dễ dàng mở rộng, có thể tái sử dụng và loại an toàn.


Tôi thích cách tiếp cận này nhưng bây giờ tôi không chắc làm thế nào để hiển thị tên của con chó. Nếu tôi ràng buộc kết quả với DataGrid, tôi có thể lấy các thuộc tính từ Dog mà không xác định rõ ràng chúng trong lớp DogWithBreed hay tôi phải tạo getter / setter cho từng trường mà tôi muốn hiển thị?
Jonathan S.

4
Do DataGrids không cho phép bạn chỉ định thuộc tính là "Dog.Name"? Bây giờ tôi quên tại sao tôi ghét chúng đủ để không bao giờ sử dụng chúng ...
teedyay

@JonathanS. Làm thế nào bạn làm điều này trong cột mẫu? Xin vui lòng cho tôi biết tôi đang ở trong tình huống tương tự
rahularyansharma

Này, tôi thích phương thức này gói gọn hai lớp thành một. Nó làm cho nó dễ dàng để làm việc với. Chưa kể việc tạo các lớp đơn giản chỉ được sử dụng trong bối cảnh hiện tại làm cho nó sạch hơn.
Linger

6
Điều này thực sự không trả lời câu hỏi mà OP có, "có cách nào để trả lại cái này mà không phải tạo một loại tùy chỉnh" không?
khoa học

69

Bạn có thể trả về các loại ẩn danh, nhưng nó thực sự không đẹp .

Trong trường hợp này tôi nghĩ sẽ tốt hơn nhiều nếu tạo ra loại phù hợp. Nếu nó chỉ được sử dụng từ trong kiểu chứa phương thức, hãy biến nó thành kiểu lồng nhau.

Cá nhân tôi muốn C # nhận "các loại ẩn danh có tên" - nghĩa là hành vi tương tự như các loại ẩn danh, nhưng với tên và khai báo tài sản, nhưng đó là nó.

EDIT: Những người khác đang đề nghị chó trở về, và sau đó truy cập tên giống thông qua đường dẫn thuộc tính, v.v ... Đó là một cách tiếp cận hoàn toàn hợp lý, nhưng IME dẫn đến tình huống bạn đã thực hiện một truy vấn theo cách cụ thể vì dữ liệu bạn muốn sử dụng - và thông tin meta đó bị mất khi bạn quay trở lại IEnumerable<Dog>- truy vấn có thể mong đợi bạn sử dụng (nói) Breedthay Ownervì một số tùy chọn tải, v.v., nhưng nếu bạn quên điều đó và bắt đầu sử dụng các thuộc tính khác, ứng dụng của bạn có thể hoạt động nhưng không hiệu quả như ban đầu bạn dự tính. Tất nhiên, tôi có thể nói chuyện rác rưởi, hoặc tối ưu hóa quá mức, v.v ...


3
Xin chào, tôi không phải là người không muốn các tính năng vì sợ chúng sẽ bị lạm dụng, nhưng bạn có thể tưởng tượng các loại mã xấu mà chúng ta sẽ thấy nếu chúng cho phép các loại ẩn danh được đặt tên không? (rùng mình)
Dave Markle

19
Chúng ta có thể thấy một số lạm dụng. Về cơ bản, chúng ta cũng có thể thấy một số mã đơn giản hơn nhiều, nơi chúng ta chỉ muốn một tuple. Không phải tất cả mọi thứ cần phải là một đối tượng với hành vi phức tạp. Đôi khi "chỉ là dữ liệu" là điều đúng. IMO, tất nhiên.
Jon Skeet

1
Cảm ơn, vì vậy, sở thích của bạn là tạo các loại ngay cả khi đó là cho chế độ xem một lần như thế này? Tôi có rất nhiều báo cáo cắt dữ liệu giống nhau theo nhiều cách khác nhau và hy vọng không phải tạo tất cả các loại khác nhau này (DogsWithBreeds, DogsWithOwnerNames, v.v.)
Jonathan S.

1
Tôi sẽ cố gắng không cần phải cắt nó theo nhiều cách, hoặc đặt phần cắt ở nơi cần dữ liệu để bạn có thể sử dụng các loại ẩn danh - nhưng ngoài ra, vâng. Nó hút theo một số cách, nhưng đó là cuộc sống tôi sợ :(
Jon Skeet

17

Chỉ để thêm giá trị hai xu của tôi :-) Gần đây tôi đã học được cách xử lý các đối tượng ẩn danh. Nó chỉ có thể được sử dụng khi nhắm mục tiêu khung .NET 4 và chỉ khi thêm một tham chiếu đến System.Web.dll nhưng nó khá đơn giản:

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

Để có thể thêm một tham chiếu đến System.Web.dll, bạn sẽ phải làm theo lời khuyên của rushonerok : Đảm bảo khung mục tiêu [dự án] của bạn là ".NET Framework 4" chứ không phải "Hồ sơ khách hàng .NET Framework 4".


2
Trường ASP.NET Mvc;)
T-moty

8

Không, bạn không thể trả lại các loại ẩn danh mà không cần thông qua một số mánh khóe.

Nếu bạn không sử dụng C #, những gì bạn sẽ tìm kiếm (trả lại nhiều dữ liệu mà không có loại cụ thể) được gọi là Tuple.

Có rất nhiều triển khai C # tuple, sử dụng cái được hiển thị ở đây , mã của bạn sẽ hoạt động như thế này.

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

Và trên trang web gọi điện:

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
Điều này không hoạt động. Ném một NotSupportedException : Chỉ nhà xây dựng parameterless và initializers được hỗ trợ trong LINQ to Entities
mshsayem

1
Đúng, lớp Tuple không có hàm tạo mặc định và sẽ không hoạt động chính xác với LINQ to Entities theo cách này. Tuy nhiên, nó hoạt động tốt với LINQ to SQL như trong câu hỏi. Tôi đã không thử điều này, nhưng nó có thể làm việc với ... select Tuple.Create(d, b).
joshperry

1
Vì Tuples không được hỗ trợ bởi một số nhà cung cấp LINQ, bạn không thể chọn loại ẩn danh, chuyển đổi nó thành IEnumerable, sau đó chọn một tuple từ đó?
TehPers

8

Bạn có thể làm một cái gì đó như thế này:


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

Bạn phải sử dụng ToList()phương thức trước để lấy hàng từ cơ sở dữ liệu và sau đó chọn các mục làm lớp. Thử cái này:

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

Vì vậy, mẹo là đầu tiênToList() . Nó ngay lập tức thực hiện truy vấn và lấy dữ liệu từ cơ sở dữ liệu. Bí quyết thứ hai là Chọn các mục và sử dụng trình khởi tạo đối tượng để tạo các đối tượng mới với các mục được tải.

Hi vọng điêu nay co ich.


8

Trong C # 7, bây giờ bạn có thể sử dụng các bộ dữ liệu! ... giúp loại bỏ nhu cầu tạo một lớp chỉ để trả về kết quả.

Đây là một mã mẫu:

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

Bạn có thể cần phải cài đặt gói nuget System.ValueTuple.


4

Bây giờ tôi nhận ra rằng trình biên dịch sẽ không cho phép tôi trả về một tập hợp các loại ẩn danh vì nó mong đợi Chó, nhưng có cách nào để trả lại cái này mà không phải tạo một loại tùy chỉnh không?

Sử dụng đối tượng sử dụng để trả về danh sách các loại Ẩn danh mà không tạo loại tùy chỉnh. Điều này sẽ hoạt động mà không có lỗi trình biên dịch (trong .net 4.0). Tôi đã trả lại danh sách cho khách hàng và sau đó phân tích cú pháp trên 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
Theo tôi, sẽ chính xác và dễ đọc hơn nếu chữ ký phương thức của bạn trông như thế này: công khai IEnumerable <object> GetDogsWithBreedNames ()
pistol-pete

3

Chỉ cần chọn chó, sau đó sử dụng dog.Breed.BreedName , điều này sẽ hoạt động tốt.

Nếu bạn có nhiều chó, hãy sử dụng DataLoadOptions.LoadWith để giảm số lượng cuộc gọi db.


2

Bạn không thể trả về các loại ẩn danh trực tiếp, nhưng bạn có thể lặp chúng thông qua phương thức chung của bạn. Vì vậy, hầu hết các phương pháp mở rộng LINQ. Không có phép thuật trong đó, trong khi có vẻ như họ sẽ trả lại các loại ẩn danh. Nếu tham số là kết quả ẩn danh cũng có thể được ẩn danh.

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

Dưới đây là một ví dụ dựa trên mã từ câu hỏi ban đầu:

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

Chà, nếu bạn trở về Chó, bạn sẽ làm:

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

Nếu bạn muốn Breed háo hức và không lười tải, chỉ cần sử dụng cấu trúc DataLoadOptions thích hợp .


0

BreedIdtrong Dogbảng rõ ràng là một khóa ngoại đối với hàng tương ứng trong Breedbảng. Nếu bạn đã thiết lập cơ sở dữ liệu của mình đúng cách, LINQ to SQL sẽ tự động tạo liên kết giữa hai bảng. Lớp Dog kết quả sẽ có thuộc tính Breed và lớp Breed nên có bộ sưu tập Chó. Thiết lập theo cách này, bạn vẫn có thể quay lại IEnumerable<Dog>, đây là một đối tượng bao gồm thuộc tính giống. Nhắc nhở duy nhất là bạn cần tải trước đối tượng giống cùng với các đối tượng chó trong truy vấn để chúng có thể được truy cập sau khi bối cảnh dữ liệu đã được xử lý và (như một poster khác đã đề xuất) thực hiện một phương thức trên bộ sưu tập sẽ gây ra truy vấn sẽ được thực hiện ngay lập tức (ToArray trong trường hợp này):

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

}

Sau đó, việc truy cập giống cho mỗi con chó là chuyện nhỏ:

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

0

Nếu ý tưởng chính là tạo câu lệnh chọn SQL được gửi đến máy chủ Cơ sở dữ liệu chỉ có các trường bắt buộc và không phải tất cả các trường Thực thể, thì bạn có thể làm điều này:

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

Hãy thử điều này để có được dữ liệu động. Bạn có thể chuyển đổi mã cho Danh sách <>

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

Nếu bạn có một thiết lập mối quan hệ trong cơ sở dữ liệu của bạn với một hạn chế khóa trước trên BreedId bạn đã không nhận được điều đó?

Ánh xạ mối quan hệ DBML

Vì vậy, bây giờ tôi có thể gọi:

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

Và trong mã gọi đó là:

var album = GetAlbum(1);

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

Vì vậy, trong trường hợp của bạn, bạn sẽ gọi một cái gì đó như dog.Breed.BreedName - như tôi đã nói, điều này phụ thuộc vào cơ sở dữ liệu của bạn được thiết lập với các mối quan hệ này.

Như những người khác đã đề cập, DataLoadOptions sẽ giúp giảm các cuộc gọi cơ sở dữ liệu nếu đó là một vấn đề.


0

Điều này không trả lời chính xác câu hỏi của bạn, nhưng Google đã dẫn tôi đến đây dựa trên các từ khóa. Đây là cách bạn có thể truy vấn một loại ẩn danh từ danh sách:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.