Finding Unmatched Records in Dataset Tables Using Linq
http://www.eggheadcafe.com/tutorials/aspnet/9b443d4a-0ec3-4599-be41-d4454c1fbd9b/finding-unmatched-records.aspx

1. LINQ(Language Integrated Query )
http://msdn.microsoft.com/ko-kr/library/bb397926.aspx
쿼리를 이용하여 개체와 데이터 간 격차를 줄여주는 기술.
객체지향 기술을 사용하여 네이티브에 정의되지 않은 정보 접근이나 통합의 복잡함을 경감시키는 방식으로  모든 Data소스에 대해 쿼리가 가능하게 한 framework  
주요장점들
   -  여러 조건을 필터링할 때 더욱 명료하며 읽기 쉽다.
   -  최소한의 응용 프로그램 코드를 사용하여 강력한 필터링, 정렬 및 그룹화 기능을 제공.
   -  거의 수정하지 않거나 약간만 수정하여 다른 데이터 소스에 이식할 수 있다.

1.1 다양한 종류의 데이터 소스와 형식에서 작업하기 위한 일관된 모델을 제공

1.1.1 쿼리 가능한 형식 
   - IEnumerable 또는 IEnumerable<(Of <(T>)>)
   - IQueryable<(Of <(T>)>) 같은 파생 인터페이스를 지원하는 형식
   - SQL Server 데이터베이스, XML 문서, ADO.NET 데이터 집합 

1.2. 코딩시 기본 쿼리 작업 특징들

// Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
1.2.0 쿼리생성(Query creation)
// var 키워드는 from 절에 지정된 데이터 소스를 검사하여 쿼리 변수의 형식을 추론하도록  컴파일러에 지시
// numQuery변수는는 이 쿼리식만 가지고 실행은 되지 않는다.
var numQuery =  
        from num in numbers where (num % 2) == 0
        select num;

1.2.1. 지연실행
foreach문 사용 시 실재로 실행을함
        foreach (int num in numQuery)
            Console.Write("{0,1} ", num);
1.2.2. 즉시실행강제
-  Count, Max, Average 및 First와 같은 집계함수를 수행 할 때는 내부적으로 내장 foreach문이 실행됨
       int numCount = numQuery .Count();
1.2.3.  실행결과 캐싱
-  ToList<(Of <(TSource>)>) 또는 ToArray<(Of <(TSource>)>) 메서드를 호출 결과를 캐싱
         List<int> numQuery2 = (from num in numbers where (num % 2) == 0 select num).ToList();
        // or like this:
        var numQuery3 = (from num in numbers where (num % 2) == 0 select num).ToArray();
1.2.4.
정렬
      var queryLondonCustomers3 = 
             from cust in customers where cust.City == "London"
            
orderby cust.Name ascending
             select cust;
1.2.5. 그룹화
-
group 절은 그룹의 키 값과 일치하는 하나 이상의 항목을 포함하는 IGrouping<(Of <(TKey, TElement>)>) 개체 시퀀스를 반환
      // Query variable is an IEnumerable<IGrouping<char, Student>>
      var studentQuery1 =
      from student in students
      group student by student.Last[0]; //char형 키를 저장하게된다.

1.2.5.1. 각 그룹에서 추가 쿼리 작업을 수행하려면  into 컨텍스트키워드를 이용하여 임시식별자를 만들어서 수행
      // custQuery is an IEnumerable<IGrouping<string, Customer>>
      var custQuery = from cust in customers
      group cust by cust.City into custGroup
      where
custGroup.Count() > 2
      orderby custGroup.Key
      select custGroup;

1.2.5.2. 그룹쿼리 결과얻기
      // Iterate group items with a nested foreach. This IGrouping encapsulates a sequence of Student objects, and a Key of type char.
      // For convenience, var can also be used in the foreach statement.
       foreach (IGrouping<char, Student> studentGroup in studentQuery2)
      {
            Console.WriteLine(studentGroup.Key);
            // Explicit type for student could also be used here.
            foreach (var student in studentGroup)
           {
                Console.WriteLine("   {0}, {1}", student.Last, student.First);
           }
       }
       
1.2.5.3. 그루핑 시 키 지정
        그룹 키는 문자열, 기본 제공 숫자 형식 또는 사용자 정의 명명된 형식이나 익명 형식과 같은 임의의 형식가능

1.2.5.3.1. 문자열값으로 그룹화예
 Query variable(studentQuery3 ) is an IEnumerable<IGrouping<string, Student>> 이 됨
 var studentQuery3 = from student in students group student by student.Last;

1.2.5.3.2. 부울값으로 그룹화 예
 group by 다음에 조건을 지정하여 조건에 해당하는 두 그룹으로 나누는 경우
 데이타가 아래와 같이 정의된경우,
         List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };
 다음과 같이 조건으로 그루핑하면 키값이 boolean형으로 저장됨
         var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

        // Execute the query and access items in each group
        foreach (var studentGroup in booleanGroupQuery)
        {
            Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }
1.2.5.3.3. 숫자범위로 그룹화 예
 group by 다음에 범위형태로 조건을 줄 수도 있다. 10단위로 키값이 그루핑되나봅니다.
        var studentQuery =
            from student in students
            let avg = (int)student.Scores.Average()
            group student by (avg == 0 ? 0 : avg / 10) into g
            orderby g.Key
            select g;           

        // Execute the query.
        foreach (var studentGroup in studentQuery)
        {
            int temp = studentGroup.Key * 10;
            Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }
1.2.5.3.3. 복합키로 그룹화 예
group person by new {name = person.surname, city = person.city};

1.2.6. 조인

1.2.6.1. 내부조인
  var innerJoinQuery =
  from category in categories
            join prod in products on category.ID equals prod.CategoryID
  select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

1.2.6.2. 그룹조인
  - into 식이 있는 join 절을 그룹 조인을 말한다. T-Sql에 이런게 없는걸로 아는데... 재미난 조인인듯
  - 그룹 조인은 왼쪽 소스 시퀀스의 요소를 오른쪽 소스 시퀀스에 있는 하나 이상의 일치하는 요소와 연결하는 계층적 결과를 생성
  - 왼쪽 소스의 요소와 일치하는 오른쪽 소스 시퀀스의 요소가 없을 경우 join 절은 해당 항목에 대해 빈 배열을 생성
  - 그룹 조인은 결과 시퀀스가 그룹으로 구성된다는 점을 제외하고 기본적으로 내부 동등 조인과 같다?
    var innerGroupJoinQuery = 
    from category in categories  
               join prod in products on category.ID equals prod.CategoryID
into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };
  - 그룹 조인의 결과를 다른 하위 쿼리의 생성기로 사용 (T-Sql에서 중첩쿼리에 해당하겠네요)
    var innerGroupJoinQuery2 =
    from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
    where prod2.UnitPrice > 2.50M
    select prod2;   

1.2.6.3.
왼쪽우선 외부조인 (Tsql의 Left Outer Join)
  - LINQ에서 왼쪽 우선 외부 조인을 수행하려면 DefaultIfEmpty 메서드를 그룹 조인과 함께 사용하여
    왼쪽 요소에 일치 항목이 없을 경우 생성할 기본 오른쪽 요소를 지정
    문법이 매끄럽지가 못하다는 느낌이...(그냥 Left Join으로 표기하지 않구.. 엄청 어렵게 표현T.T)
          var leftOuterJoinQuery =
          from category in categories
                   join prod in products on category.ID equals prod.CategoryID
into prodGroup
          from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
          select new { CatName = category.Name, ProdName = item.Name };

1.2.6.4. 개체 컬렉션 및 관계형 테이블의 조인
         LINQ 쿼리 식에서는 조인 작업이 개체 컬렉션에서 수행되며 두 개의 관계형 테이블과 동일한 방식으로 "조인"할 수 없다.
         LINQ에서 명시적 join 절은 두 개의 소스 콜렉션이 어떤 관계로도 연결되지 않은 경우에만 필요
         LINQ to SQL을 사용하는 경우 외래 키 테이블은 개체 모델에 기본 테이블의 속성으로 표현되므로 조인이 이미 된 상태임
        ( 예를 들어 Northwind 데이터베이스의 Customer 테이블은 Orders 테이블과 외래 키 관계가 있습니다.
          테이블을 개체 모델에 매핑하면 Customer 클래스에 해당 Customer와 연결된 Orders 컬렉션을 포함하는 Orders 속성이 자동생성됨)

 1.2.6.5. 복합키 조인 
         익명 형식의 복합 키를 만들거나, 비교할 값이 포함된 명명된 형식으로 복합 키를 만듬.
        (참고: db라는 collection의 속성으로 Orders와 Products가 이미 포함되었다고 가정함)
         var query = 
         from o in db.Orders
         from p in db.Products
                          join d in db.OrderDetails on new {o.OrderID, p.ProductID} equals new {d.OrderID,        d.ProductID}
         into details
         from d in details
         select new {o.OrderID, p.ProductID, d.UnitPrice};    

1.3. LINQ를 통한 데이터 변환
 LINQ(통합 언어 쿼리)는 데이터를 검색할 뿐 아니라 데이터를 변환하는 강력한 도구.
 LINQ 쿼리를 사용하면 소스 시퀀스를 입력으로 사용하고 다양한 방식으로 수정하여 새 출력 시퀀스를 만들 수 있다.
 LINQ 쿼리의 가장 강력한 기능은 select절을 이용한 새 형식을 만드는 기능

1.3.1. 여러 입력을 하나의 출력 시퀀스로 결합 
두 개의 메모리 내 데이터 구조를 결합하는 방법을 보여 주지만, XML이나 SQL 또는 DataSet 소스의 데이터를 결합하는 경우에도 동일한 원칙을 적용할 수 있다.        
        // Create the first data source.
        List<Student> students = new List<Student>()
        {
            new Student {First="Svetlana", Last="Omelchenko",  ID=111, Street="123 Main Street", City="Seattle", Scores= new List<int> {97, 92, 81, 60}},
            new Student {First="Claire", Last="O’Donnell",  ID=112, Street="124 Main Street", City="Redmond", Scores= new List<int> {75, 84, 91, 39}},
            new Student {First="Sven", Last="Mortensen", ID=113, Street="125 Main Street", City="Lake City", Scores= new List<int> {88, 94, 65, 91}},
        };

        // Create the second data source.
        List<Teacher> teachers = new List<Teacher>()
        {               
            new Teacher {First="Ann", Last="Beebe", ID=945, City = "Seattle"},
            new Teacher {First="Alex", Last="Robinson", ID=956, City = "Redmond"},
            new Teacher {First="Michiyo", Last="Sato", ID=972, City = "Tacoma"}
        };

        // Create the query.
        var peopleInSeattle =
        (
           from student in students
           where student.City == "Seattle"
           select student.Last
        )
        .Concat  // 쿼리의 union all 에 해당
       (
          from teacher in teachers
          where teacher.City == "Seattle"
          select teacher.Last
       );

        Console.WriteLine("The following students and teachers live in Seattle:");
       
        // Execute the query.
        foreach (var person in peopleInSeattle)
        {
            Console.WriteLine(person);
        }

1.3.2. 각 소스 요소의 하위 집합 선택 
 - 단순한 Select
    var query = from cust in Customers
            select cust.City;
 - 새로운 인스턴트스로 생성하며 select
   var query = from cust in Customer
            select new {Name = cust.Name, City = cust.City}; 

1.3.3. 메모리 내부 개체를 XML로 변환 
// Create the data source by using a collection initializer.
        List<Student> students = new List<Student>()
        {
            new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 81, 60}},
            new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List<int>{75, 84, 91, 39}},
            new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List<int>{88, 94, 65, 91}},
        };

        // Create the query. 이렇게 XElement를 이용하여~~~
        var studentsToXML =
        new XElement("Root",
            from student in students
            let x = String.Format("{0},{1},{2},{3}", student.Scores[0], student.Scores[1], student.Scores[2], student.Scores[3])
            select new XElement("student",
                       new XElement("First", student.First),
                       new XElement("Last", student.Last),
                       new XElement("Scores", x)
                    ) // end "student"
                ); // end "Root"

        // Execute the query.
        Console.WriteLine(studentsToXML);

1.3.4.  소스 요소에서 작업 수행
        // Data source.
        double[] radii = { 1, 2, 3 };

        // Query.
        IEnumerable<string> query =
            from rad in radii
            select String.Format("Area = {0}", (rad * rad) * 3.14);

        // Query execution.
        foreach (string s in query)
            Console.WriteLine(s);

1.4. 쿼리 구문과 메서드 구문 비교
NET CLR(공용 언어 런타임) 자체에는 쿼리 구문의 개념이 없고 컴파일 타임에 쿼리 식은 CLR이 인식가능한 메서드 호출로 변환됨
이러한 메서드를 표준 쿼리 연산자 라고 하며 Where, Select, GroupBy, Join, Max, Average,... 등이 있다.
쿼리 구문 대신 메서드 구문을 사용하여 이를 직접 호출할 수 있다.
          int[] numbers = { 5, 10, 8, 3, 6, 12};
         //Query syntax:
        IEnumerable<int> numQuery1 =
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

        //Method syntax:
        IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
둘의 결과는 동일하다.
Where(num => num % 2 == 0) 이 인라인 식을 람다식 이라한다.
람다식을 사용하지 않을 경우 익명 메서드나 제네릭 대리자 또는 식 트리와 같은 형식으로 코드를 작성해야 한다.
C#에서 =>는 "goes to"를 나타내는 람다 연산자
 

1.5. 람다식
http://msdn.microsoft.com/ko-kr/library/bb397687.aspx 계속
http://msdn.microsoft.com/ko-kr/library/bb397951.aspx 식트리
http://msdn.microsoft.com/ko-kr/library/bb882636.aspx
식과 문을 포함하고 대리자나 식 트리 형식을 만드는 데 사용할 수 있는 익명 함수
모든 람다 식에는 람다 연산자 =>("이동"이라고 읽음)사용
왼쪽에는 입력 매개 변수(있는 경우)를 지정하고 오른쪽에는 식 또는 문 블록이 위치
=> 연산자는 할당 연산자(=)와 우선 순위가 같으며 오른쪽 결합성이 있다.
예) x => x * x라는 람다 식은 "x는 x 곱하기 x로 이동"으로 읽으며, 이 식은 다음과 같이 대리자 형식에 할당할 수 있다.
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

메서드 기반 LINQ 쿼리에서 Where 및 Where 같은 표준 쿼리 연산자 메서드의 인수로 사용.
LINQ to Objects 및 LINQ to XML에서처럼 메서드 기반의 구문을 사용하여 Enumerable 클래스에서 Where 메서드를 호출하는 경우
매개 변수는 System..::.Func<(Of <(T, TResult>)>) 대리자 형식
람다식은 대리자를 만드는 가장 간단한 방법이다.
LINQ to SQL에서 System.Linq..::.Queryable 클래스 에서 호출하는 경우 매개 변수 형식은 System.Linq.Expressions..::.Expression<Func>이고, 여기서 Func는 입력 매개 변수를 5개까지 가질 수 있는 임의의 Func 대리자이다.

1.5.1. 식 람다(expression lamda)
- 오른쪽에 식이 있는 람다 식,  식 트리를 만드는 데 광범위하게 사용,식 람다는 식의 결과를 반환
 (input parameters) => expression
- 괄호는 람다 식에 입력 매개 변수가 하나뿐인 경우에만 생략
- 둘 이상의 입력 매개 변수는 다음과 같이 괄호로 묶고 쉼표로 구분
 (x, y) => x == y
- 컴파일러에서 입력 형식을 유추할 수 없는 경우 다음과 같이 형식을 명시적으로 지정
 (int x, string s) => s.Length > x
- 입력 매개 변수가 0개이면 다음과 같이 빈 괄호를 지정
 () => SomeMethod()
1.5.2.  식트리
System.Linq.Expressions 네임스페이스 필요
IQueryable<(Of <(T>)>)을 구현하는 데이터의 소스를 대상으로 하는 구조화된 쿼리를 나타내기 위해 식 트리를 사용
LINQ에서 Expression<(Of <(TDelegate>)>) 형식 변수에 할당되는 람다 식을 나타내는 데에도 사용
   Expression<Func<int, bool>> exprTree = num => num < 5;
   와같이 Delegate표현대신 Expression클래스 형식을 사용하여 트리형태로 식을저장
ParameterExpression,ConstantExpression,BinaryExpression등과같이 식에표현된 각 부분에 해당하는 클래스들존재

// Manually build the expression tree for the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
    Expression.Lambda<Func<int, bool>>(
        numLessThanFive,
        new ParameterExpression[] { numParam });
와 동일한 람다식표현
// Let the compiler generate the expression tree for the lambda expression num => num < 5.
Expression<Func<int, bool>> lambda2 = num => num < 5;
이정도 개념만 있어도 충분할듯, 더 자세한 부분은 MSDN을 찾아보시고 http://msdn.microsoft.com/ko-kr/library/bb882637.aspx


1.5.3. 문 람다 (statement lamda)
- 문 람다는 다음과 같이 중괄호 안에 문을 지정한다는 점을 제외하면 식 람다와 비슷
 (input parameters) => {statement;}
- 문 람다의 본문에 지정할 수 있는 문의 개수에는 제한이 없지만 일반적으로 2-3개 정도만 지정
  (문 람다는 식 트리를 만드는 데 사용될 수 없다)
 delegate void TestDelegate(string s);
 …
 TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
 myDel("Hello");

1.5.4. 표준 쿼리 연산자와 람다 식
- 대부분의 표준 쿼리 연산자에는 형식이 제네릭 대리자의 Func<(Of <(T, TResult>)>) 패밀리 중 하나인 입력 매개 변수를 사용
 public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
 이 경우 대리자를 Func<int,bool> myFunc로 인스턴스화할 수 있다.
 int는 입력 매개 변수이고, bool은 반환 값(반환 값은 항상 마지막 형식 매개 변수로 지정)

예) 5인지 여부판단하여 bool로 리턴
    Func<int, bool> myFunc = x => x == 5;
    bool result = myFunc(4); // returns false of course
- System.Linq.Queryable에 정의되어 있는 표준 쿼리 연산자의 경우와 같이 인수 형식이 Expression<Func>인 경우에도 람다 식을 사용.
  Expression<Func> 인수를 지정하면 식 트리에 람다 식이 컴파일됨

예) 2로 나누었을 때 나머지가 1인 정수(n)의 수를 계산
      int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
      int oddNumbers = numbers.Count(n => n % 2 == 1);
    시작부터 검사하여 6보다 작은 값들 추출
      var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
    2개의 파라메타를 입력받아 동적으로 임계치 변경
      var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

1.5.5. 람다 식에서의 형식 유추
입력 매개 변수의 형식은 대부분의 표준 쿼리 연산자에서 첫 번째 입력 형식은 소스 시퀀스 요소의 형식으로 컴파일러가 유추함
IEnumerable<Customer>을 쿼리할 경우 입력 변수가 Customer 개체로 유추
customers.Where(c => c.City == "London");
- 람다 식적용 규칙
  람다 식과 대리자 형식에 포함된 매개 변수 수가 같아야 한다. 
   람다 식의 각 입력 매개 변수는 해당되는 대리자 매개 변수로 암시적으로 변환될 수 있어야 한다.
  람다 식의 반환 값(있는 경우)은 대리자의 반환 형식으로 암시적으로 변환될 수 있어야 한다.
  람다 식의 "형식"을 비공식적으로 언급해야 할 경우 대리자 형식 또는 람다 식이 변환되는 Expression 형식을 의미

1.5.6. 람다 식의 변수 범위
람다 식은 람다 식이 정의된 바깥쪽 메서드나 형식의 범위에 포함되어 있는 외부 변수를 참조할 수 있다.
이러한 방식으로 캡처되는 변수는 변수가 범위를 벗어나 가비지 수집되는 경우에도 람다 식에 사용할 수 있도록 저장된다.
외부 변수는 명확하게 할당해야만 람다 식에 사용할 수 있다
- 람다 식의 변수 범위규칙
  캡처된 변수는 해당 변수를 참조하는 대리자가 범위에서 벗어날 때까지 가비지 수집되지 않는다.
  람다 식에 사용된 변수는 외부 메서드에 표시되지 않는다.
  람다 식은 바깥쪽 메서드에서 ref 또는 out 매개 변수를 직접 캡처할 수 없습니다.
  람다 식의 return 문에 의해서는 바깥쪽 메서드가 반환되지는 않는다.?
  람다 식에는 포함된 익명 함수의 본문 또는 본문 외부를 대상으로 하는 goto 문, break 문 또는 continue 문이 포함될 수 없다

예)
    delegate bool D();
    delegate bool D2(int i);

    class Test
    {
        D del;
        D2 del2;
        public void TestMethod(int input)
        {
            int j = 0;
            // Initialize the delegates with lambda expressions.
            // Note access to 2 outer variables.
            // del will be invoked within this method.
            del = () => { j = 10;  return j > input; };

            // del2 will be invoked after TestMethod goes out of scope.
            del2 = (x) => {return x == j; };
           
            // Demonstrate value of j:
            // Output: j = 0
            // The delegate has not been invoked yet.
            Console.WriteLine("j = {0}", j);

            // Invoke the delegate.
            bool boolResult = del();

            // Output: j = 10 b = True
            Console.WriteLine("j = {0}. b = {1}", j, boolResult);
        }

        static void Main()
        {
            Test test = new Test();
            test.TestMethod(5);

            // Prove that del2 still has a copy of
            // local variable j from TestMethod.
            bool result = test.del2(10);

            // Output: True
            Console.WriteLine(result);
           
            Console.ReadKey();
        }
    }

2. LINQ to Objects
중간 LINQ 공급자나 API는 사용하지 않고 LINQ to SQL,LINQ to XML처럼 IEnumerable,IEnumerable<(Of <(T>)>) 파생 컬렉션을 직접 사용하는 LINQ쿼리를 말함
LINQ를 사용하여 List<(Of <(T>)>), Array 또는 Dictionary<(Of <(TKey, TValue>)>)와 같은 열거 가능한 컬렉션을 모두 쿼리가능

2.1. LINQ 및 문자열
문자열 및 문자열의 컬렉션을 쿼리하고 변환
일반적인 문자열 함수 및 정규식과 결합될 수 있다.

2.1.1 문자열에서 단어가 나오는 횟수 세기(LINQ)
 class CountWords
{
    static void Main()
    {
        string text = @"Historically, the world of data and the world of objects" +
          @" have not been well integrated. Programmers work in C# or Visual Basic" +
          @" and also in SQL or XQuery. On the one side are concepts such as classes," +
          @" objects, fields, inheritance, and .NET Framework APIs. On the other side" +
          @" are tables, columns, rows, nodes, and separate languages for dealing with" +
          @" them. Data types often require translation between the two worlds; there are" +
          @" different standard functions. Because the object world has no notion of query, a" +
          @" query can only be represented as a string without compile-time type checking or" +
          @" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" +
          @" objects in memory is often tedious and error-prone.";

        string searchTerm = "data";

        //Convert the string into an array of words
        string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries);

        // Create and execute the query. It executes immediately because a singleton value is produced. 즉시실행???
        // Use ToLowerInvariant to match "data" and "Data"
        var matchQuery = from word in source
                         where word.ToLowerInvariant() == searchTerm.ToLowerInvariant()
                         select word;

        // Count the matches.
        int wordCount = matchQuery.Count();
        Console.WriteLine("{0} occurrences(s) of the search term \"{1}\" were found.", wordCount, searchTerm);

    }
}
/* Output:
   3 occurrences(s) of the search term "data" were found.
*/

2.1.2 지정된 단어 집합이 들어 있는 문장 쿼리(LINQ)
//나눈 문장들에서 "Historically", "data" 및 "integrated"이라는 단어를 모두 포함한 문장을 반환.
        // Split the text block into an array of sentences.
        string[] sentences = text.Split(new char[] { '.', '?', '!' });

        // Define the search terms. This list could also be dynamically populated at runtime.
        string[] wordsToMatch = { "Historically", "data", "integrated" };

        // let에서 문장들 나누고, Distinct로 중복된 문장제거, Intersect로 교집합연산의 개수가 wordsToMatch의 개수와 동일한 문장만 Select
        // Note that the number of terms to match is not specified at compile time.
        var sentenceQuery = from sentence in sentences
                            let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' },StringSplitOptions.RemoveEmptyEntries)
                            where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
                            select sentence;
     
        // Execute the query. Note that you can explicitly type the iteration variable here even though sentenceQuery
        // was implicitly typed.
        foreach (string str in sentenceQuery)
        {
            Console.WriteLine(str);
        }
2.1.3 문자열의 문자 쿼리(LINQ)
String 클래스는 제네릭 IEnumerable<(Of <(T>)>) 인터페이스를 구현하기 때문에 모든 문자열을 문자 시퀀스로 쿼리할 수 있다.
class QueryAString
{
    static void Main()
    {
        string aString = "ABCDE99F-J74-12-89A";

        // Select only those characters that are numbers 숫자로~~
        IEnumerable<char> stringQuery =
          from ch in aString
          where Char.IsDigit(ch)
          select ch;

        // Execute the query
        foreach (char c in stringQuery)
            Console.Write(c + " ");

        // Call the Count method on the existing query.
        int count = stringQuery.Count();
        Console.WriteLine("Count = {0}", count);

        // Select all characters before the first '-'
        IEnumerable<char> stringQuery2 = aString.TakeWhile(c => c != '-');

        // Execute the second query
        foreach (char c in stringQuery2)
            Console.Write(c);

        Console.WriteLine(System.Environment.NewLine + "Press any key to exit");
        Console.ReadKey();

    }
}
/* Output:
  Output: 9 9 7 4 1 2 8 9
  Count = 8
  ABCDE99F
*/

2.1.4. LINQ 쿼리와 정규식 결합
Regex 클래스를 사용하여 텍스트 문자열에서 좀 더 복잡한 비교를 위해 정규식을 만드는 방법
class QueryWithRegEx
{
     // This method assumes that the application has discovery permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
    public static void Main()
    {
        // Modify this path as necessary.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder); //폴더의 모든 파일정보

        // Create the regular expression to find all things "Visual".
        System.Text.RegularExpressions.Regex searchTerm =
            new System.Text.RegularExpressions.Regex(@"Visual (Basic|C#|C\+\+|J#|SourceSafe|Studio)");

        // Search the contents of each .htm file.
        // Remove the where clause to find even more matches!
        // This query produces a list of files where a match was found, and a list of the matches in that file.
        // Note: Explicit typing of "Match" in select clause.
        // This is required because MatchCollection is not a generic IEnumerable collection.
        var queryMatchingFiles =
            from file in fileList
            where file.Extension == ".htm" //확장자가 htm인 것들중에
            let fileText = System.IO.File.ReadAllText(file.FullName) // 파일명만 fileText 목록(집합)으로두고
            let matches = searchTerm.Matches(fileText) // fileText에서 searchTerm에 해당하는 것들만
            where searchTerm.Matches(fileText).Count > 0 //이게 없으면 matches에 null도 들어가나??? 확인필요
            select new
            {
                name = file.FullName,
                matches = from System.Text.RegularExpressions.Match match in matches
                          select match.Value
            };

        // Execute the query.
        Console.WriteLine("The term \"{0}\" was found in:", searchTerm.ToString());

        foreach (var v in queryMatchingFiles)
        {
            // Trim the path a bit, then write the file name in which a match was found.
            string s = v.name.Substring(startFolder.Length - 1);
            Console.WriteLine(s);

            // For this file, write out all the matching strings
            foreach (var v2 in v.matches)
            {
                Console.WriteLine("  " + v2);
            }
        }
    } 
}
2.1.5. 두 목록 간의 차집합 구하기(LINQ)
 names1.txt에 있지만 names2.txt에는 없는 해당 줄을 출력
class CompareLists
{       
    static void Main()
    {
        // Create the IEnumerable data sources.
        string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
        string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");

        // Create the query. Note that method syntax must be used here.
        IEnumerable<string> differenceQuery =
          names1.Except(names2);

        // Execute the query.
        Console.WriteLine("The following lines are in names1.txt but not names2.txt");
        foreach (string s in differenceQuery)
            Console.WriteLine(s);
    }
}
2.1.6. 단어 또는 필드에 따라 텍스트 데이터 정렬 또는 필터링(LINQ)
쉼표로 구분된 값과 같은 구조화된 텍스트 줄을 해당 줄에서 임의의 필드를 기준으로 정렬하는 방법
scores.csv의 필드는 학생의 ID 번호와 4개의 테스트 점수를 나타낸다고 가정
public class SortLines
{
    static void Main()
    {
        // Create an IEnumerable data source
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Change this to any value from 0 to 4.
        int sortField = 1; //두번째 필드 말하죠.. (0부터시작)

        Console.WriteLine("Sorted highest to lowest by field [{0}]:", sortField);

        // Demonstrates how to return query from a method.
        // The query is executed here.
        foreach (string str in RunQuery(scores, sortField))
        {
            Console.WriteLine(str);
        }
    }

    // Returns the query variable, not query results!
    static IEnumerable<string> RunQuery(IEnumerable<string> source, int num)
    {
        // Split the string and sort on field[num]
        var scoreQuery = from line in source
                         let fields = line.Split(',')
                         orderby fields[num] descending
                         select line;

        return scoreQuery;
    }
}
/* Output (if sortField == 1):
   Sorted highest to lowest by field [1]:
    116, 99, 86, 90, 94
    120, 99, 82, 81, 79
    111, 97, 92, 81, 60
    114, 97, 89, 85, 82
    121, 96, 85, 91, 60
    122, 94, 92, 91, 91
    117, 93, 92, 80, 87
    118, 92, 90, 83, 78
    113, 88, 94, 65, 91
    112, 75, 84, 91, 39
    119, 68, 79, 88, 92
    115, 35, 72, 91, 70
 */
2.1.7. 구분된 파일의 필드 다시 정렬후 파일로 저장.
- spreadsheet1.csv라는 일반 텍스트 내용
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121

-파일을 읽어 쉼표로 구분하고,  3번쨰 필드로 정렬한 후 다시 파일로 저장
        // Create the IEnumerable data source
        string[] lines = System.IO.File.ReadAllLines(@"../../../spreadsheet1.csv");

        // Create the query. Put field 2 first, then
        // reverse and combine fields 0 and 1 from the old field
        IEnumerable<string> query =
            from line in lines
            let x = line.Split(',')
            orderby x[2] 
            select x[2] + ", " + (x[1] + " " + x[0]);

        // Execute the query and write out the new file. Note that WriteAllLines
        // takes a string[], so ToArray is called on the query.
        System.IO.File.WriteAllLines(@"../../../spreadsheet2.csv", query.ToArray());

2.1.7. 문자열 컬렉션 결합 및 비교
텍스트 줄이 포함된 파일을 병합한 다음 결과를 정렬하는 방법
특히 두 개의 텍스트 줄 집합에서의 간단한 연결, 공용 구조체 및 교집합을 수행
- names1.txt 내용
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
- names2.txt 내용
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

        static void OutputQueryResults(IEnumerable<string> query, string message)
        {
            Console.WriteLine(System.Environment.NewLine + message);
            foreach (string item in query)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("{0} total names in list", query.Count());
        }
//위 함수가 미리 정의돼 있다고 가정
            //Put text files in your solution folder
            string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
            string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

            //Simple concatenation and sort. Duplicates are preserved.
            IEnumerable<string> concatQuery = fileA.Concat(fileB).OrderBy(s => s);

            // Pass the query variable to another function for execution.
            OutputQueryResults(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");

            // Concatenate and remove duplicate names based on default string comparer.(병합)
            IEnumerable<string> uniqueNamesQuery =
                fileA.Union(fileB).OrderBy(s => s); //병합하면서 distinct도 될터
            OutputQueryResults(uniqueNamesQuery, "Union removes duplicate names:");

            // Find the names that occur in both files (based on default string comparer). (교집합)
            IEnumerable<string> commonNamesQuery =
                fileA.Intersect(fileB);
            OutputQueryResults(commonNamesQuery, "Merge based on intersect:");

            // Find the matching fields in each list. Merge the two results by using Concat,
            // and then
sort using the default string comparer.
            string nameMatch = "Garcia";

            IEnumerable<String> tempQuery1 =
                from name in fileA
                let n = name.Split(',')
                where n[0] == nameMatch //,로 분리후 0번필드중에  nameMatch와 일치하는  것들 

                select name;

            IEnumerable<string> tempQuery2 =
                from name2 in fileB
                let n2 = name2.Split(',')
                where n2[0] == nameMatch
                select name2;

            IEnumerable<string> nameMatchQuery =
                tempQuery1.Concat(tempQuery2).OrderBy(s => s); // 이러면 union효과가 되나? 아님 Left join?? 확인필요
            OutputQueryResults(nameMatchQuery, String.Format("Concat based on partial name match \"{0}\":", nameMatch));

2.1.8. 여러 소스로 개체 컬렉션 채우기(두 파일읽어서 조인효과 구현)
- scores.csv
111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91
- names.csv
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
        // These data files are defined in How to: Join Content from Dissimilar Files (LINQ)
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type.
        // var could be used instead of an explicit type.
        // Note the dynamic creation of a list of ints for the TestScores member. We skip 1 because the first string
        // in the array is the student ID, not an exam score.
        IEnumerable<Student> queryNamesScores =
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            where x[2] == s[0]  // score의 첫번째 컬럼과 names의 두번째 컬럼과 조인(학생ID)
            select new Student()  //이렇게 새로운 타입으로 new
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]), //타입명시 
                ExamScores = (from scoreAsText in s.Skip(1) //첫번째 값은 ID이므로 skip 
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory for faster access in future queries. Could be useful with
        // very large data files.
        List<Student> students = queryNamesScores.ToList();

        // Display the results and perform one further calculation.
        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }
2.1.9. 그룹을 사용하여 파일을 여러 파일로 분할
- names1.csv
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
- names2.csv
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

        string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
        string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

        // Concatenate and remove duplicate names based on default string comparer
        var mergeQuery = fileA.Union(fileB);

        // Group the names by the first letter in the last name.
        var groupQuery = from name in mergeQuery
                         let n = name.Split(',')
                         group name by n[0][0] into g  //제일 첫글자 , n[0]은 첫번쨰 컬럼
                         orderby g.Key
                         select g;

        // Create a new file for each group that was created
        // Note that nested foreach loops are required to access
        // individual items with each group.
        foreach (var g in groupQuery)
        {
            // Create the new file name.
            string fileName = @"../../../testFile_" + g.Key + ".txt";
            // Write file. 그룹별로 파일생성
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
            {
                foreach (var item in g)
                {
                    sw.WriteLine(item);
                    // Output to console for example purposes.
                    Console.WriteLine("   {0}", item);
                }
            }
        }

2.1.10. 서로 다른 파일의 콘텐츠 조인 
코드만 봐도 될듯
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
        // Name:    Last[0],                First[1],      ID[2],     Grade Level[3]
        //                  Omelchenko,    Svetlana,      11,          2
        // Score:   StudentID[0],  Exam1[1]   Exam2[2],  Exam3[3],  Exam4[4]
        //          111,           97,        92,        81,        60

        // This query joins two dissimilar spreadsheets based on common ID value.
        // Multiple from clauses are used instead of a join clause
        // in order to store results of id.Split.
        IEnumerable<string> scoreQuery1 =
            from name in names
            let nameFields = name.Split(',')
            from id in scores
            let scoreFields = id.Split(',')
            where nameFields[2] == scoreFields[0]
            select nameFields[0] + "," + scoreFields[1] + "," + scoreFields[2]
                   + "," + scoreFields[3] + "," + scoreFields[4];

        // Pass a query variable to a method and
        // execute it in the method. The query itself
        // is unchanged.
        OutputQueryResults(scoreQuery1, "Merge two spreadsheets:");

2.1.10. CSV 텍스트 파일의 열 값 계산 

         string[] lines = System.IO.File.ReadAllLines(@"../../../scores.csv");
          .....
        IEnumerable<IEnumerable<int>> query = from line in strs
            let x = line.Split(',')
            let y = x.Skip(1)
            select (
                            from str in y
                            select Convert.ToInt32(str));

        // Execute and cache the results for performance.
        // ToArray could also be used here.
        var results = query.ToList();

        // Find out how many columns we have.
        int columnCount = results[0].Count();

        // Perform aggregate calculations on each column.           
        // One loop for each score column in scores.
        // We can use a for loop because we have already executed the columnQuery in the call to ToList.
        for (int column = 0; column < columnCount; column++)
        {
            var res2 = from row in results
                       select row.ElementAt(column);

            double average = res2.Average(); //컬럼에 대한 average
            int max = res2.Max();
            int min = res2.Min();

            // 1 is added to column because Exam numbers
            // begin with 1
            Console.WriteLine("Exam #{0} Average: {1:##.##} High Score: {2} Low Score: {3}",
                          column + 1, average, max, min);
        }

2.2. LINQ 및 리플렉션
LINQ를 리플렉션과 함께 사용하여 지정한 검색 조건에 일치하는 메서드에 대한 특정 메타데이터를 검색할 수 있다

using System.Reflection;
using System.IO;
namespace LINQReflection
{
    class ReflectionHowTO
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken= b77a5c561934e089");
            var pubTypesQuery = 
            from type in assembly.GetTypes() // 타입목록 추출
            where type.IsPublic
            from method in type.GetMethods() // 각 타입에서 메소드목록 추출
            where method.ReturnType.IsArray == true 
                      || ( method.ReturnType.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
                     && method.ReturnType.FullName != "System.String" )
            group method.ToString() by type.ToString();
            // 이렇게 되면 타입콜렉션의 속성으로 method가 들어가는 형태임. 아래에서 키(콜렉션 내에서 타입을 식별하는)별로 메소드 출력
            foreach (var groupOfMethods in pubTypesQuery)
            {
                Console.WriteLine("Type: {0}", groupOfMethods.Key);
                foreach (var method in groupOfMethods)
                {
                    Console.WriteLine("  {0}", method);
                }
            }

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    } 
}

2.3. LINQ 및 파일 디렉터리
문자열을 다루면서 파일에 관련된 내용이 나오긴 했지만 여기에서 더 상세히 다룸.

2.1.1. 지정된 특성 또는 이름을 갖는 파일 쿼리 
//주어진 경로의 파일정보 가져오는 함수  
// This method assumes that the application has discovery  permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);           
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
...
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        //Create the query
        IEnumerable<System.IO.FileInfo> fileQuery =
            from file in fileList
            where file.Extension == ".txt"
            orderby file.Name
            select file;

        //Execute the query. This might write out a lot of files!
        foreach (System.IO.FileInfo fi in fileQuery)         
             Console.WriteLine(fi.FullName);    // Create and execute a new query by using the previous
        // query as a starting point. fileQuery is not
        // executed again until the call to Last()
        var newestFile =
            (from file in fileQuery
            orderby file.CreationTime
            select new { file.FullName, file.CreationTime })
            .Last();

        Console.WriteLine("\r\nThe newest .txt file is {0}. Creation time: {1}",
            newestFile.FullName, newestFile.CreationTime);

2.1.2. 확장명에 따라 파일 그룹화 

        // Take a snapshot of the file system.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

        // Used in WriteLine to trim output lines.
        int trimLength = startFolder.Length;

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // Create the query.
        var queryGroupByExt =
            from file in fileList
            group file by file.Extension.ToLower() into fileGroup
            orderby fileGroup.Key //그루핑이 되면서 동일그룹에 키값들이 할당됨에 유의
            select fileGroup;
      PageOutput(trimLength, queryGroupByExt); // 바로아래에 정의

   private static void PageOutput( int rootLength,
                                    IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>> groupByExtList)
    {
        // Flag to break out of paging loop.
        bool goAgain = true;

        // "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
        int numLines = Console.WindowHeight - 3;

        // Iterate through the outer collection of groups.
        foreach (var filegroup in groupByExtList)
        {
            // Start a new extension at the top of a page.
            int currentLine = 0;

            // Output only as many lines of the current group as will fit in the window.
            do
            {
                Console.Clear();
                Console.WriteLine(filegroup.Key == String.Empty ? "[none]" : filegroup.Key);

                // Get 'numLines' number of items starting at number 'currentLine'.
                var resultPage = filegroup.Skip(currentLine).Take(numLines);

                //Execute the resultPage query
                foreach (var f in resultPage)
                {
                    Console.WriteLine("\t{0}", f.FullName.Substring(rootLength));
                }

                // Increment the line counter.
                currentLine += numLines;

                // Give the user a chance to escape.
                Console.WriteLine("Press any key to continue or the 'End' key to break...");
                ConsoleKey key = Console.ReadKey().Key;
                if (key == ConsoleKey.End)
                {
                    goAgain = false;
                    break;
                }
            } while (currentLine < filegroup.Count());

            if (goAgain == false)
                break;
        }
    }
2.1.3. 폴더 집합의 전체 바이트 수 쿼리
    static long GetFileLength(string filename)
    {
        long retval;
        try { System.IO.FileInfo fi = new System.IO.FileInfo(filename);
            retval = fi.Length;
        }
        catch (System.IO.FileNotFoundException)
        {
            // If a file is no longer present,
            // just add zero bytes to the total.
            retval = 0;
        }
        return retval;
    }
.....
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\VC#";

        // Take a snapshot of the file system.
        // This method assumes that the application has discovery permissions
        // for all folders under the specified path.
        IEnumerable<string> fileList = System.IO.Directory.GetFiles(startFolder, "*.*", System.IO.SearchOption.AllDirectories);

        var fileQuery = from file in fileList
                        select GetFileLength(file);

        // Cache the results to avoid multiple trips to the file system.
        long[] fileLengths = fileQuery.ToArray();

        // Return the size of the largest file
        long largestFile = fileLengths.Max();

        // Return the total number of bytes in all the files under the specified folder.
        long totalBytes = fileLengths.Sum();

        Console.WriteLine("There are {0} bytes in {1} files under {2}",
            totalBytes, fileList.Count(), startFolder);
        Console.WriteLine("The largest files is {0} bytes.", largestFile);
2.1.3. 두 폴더의 내용 비교
세 가지 방법
- 두 개의 파일 목록이 동일한지 여부를 지정하는 부울 값 쿼리
- 양쪽 파일에 있는 파일을 검색하는 교차 부분 쿼리
- 한 폴더에는 있지만 다른 폴더에는 없는 파일을 검색하는 차집합 쿼리

    class FileCompare : System.Collections.Generic.IEqualityComparer<System.IO.FileInfo>
    {
        public FileCompare() { }

        public bool Equals(System.IO.FileInfo f1, System.IO.FileInfo f2)
        {
            return (f1.Name == f2.Name && f1.Length == f2.Length); //파일명과 길이비교
        }

        // Return a hash that reflects the comparison criteria. According to the
        // rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
        // also be equal. Because equality as defined here is a simple value equality, not
        // reference identity, it is possible that two or more objects will produce the same
        // hash code.
        public int GetHashCode(System.IO.FileInfo fi)
        {
            string s = String.Format("{0}{1}", fi.Name, fi.Length);
            return s.GetHashCode();
        }
    }
라는 Compare클래스 미리정의 된 경우
            string pathA = @"C:\TestDir";
            string pathB = @"C:\TestDir2"; // Take a snapshot of the file system.
            IEnumerable<System.IO.FileInfo> list1 = GetFiles(pathA);
            IEnumerable<System.IO.FileInfo> list2 = GetFiles(pathB);

            //A custom file comparer defined below
            FileCompare myFileCompare = new FileCompare();

            // This query determines whether the two folders contain identical file lists, based on the custom file comparer
            // that is defined in the FileCompare class.
            // The query executes immediately because it returns a bool.
            bool areIdentical = list1.SequenceEqual(list2, myFileCompare);
            if (areIdentical == true)
                Console.WriteLine("the two folders are the same");
            else
                Console.WriteLine("The two folders are not the same");

            // Find the common files. It produces a sequence and doesn't execute until the foreach statement.
            var queryCommonFiles = list1.Intersect(list2, myFileCompare);
           if (queryCommonFiles.Count() > 0)
           {
                Console.WriteLine("The following files are in both folders:");
                foreach (var v in queryCommonFiles)
                {
                    Console.WriteLine(v.FullName); //shows which items end up in result list
                }
            }
            else
            {
                Console.WriteLine("There are no common files in the two folders.");
            }

            // Find the set difference between the two folders.
            // For this example we only check one way.
            var queryList1Only = (from file in list1 select file).Except(list2, myFileCompare);

            Console.WriteLine("The following files are in list1 but not list2:");
            foreach (var v in queryList1Only)
            {
                Console.WriteLine(v.FullName);
            }
2.1.4. 디렉터리 트리에서 가장 큰 파일을 하나 이상 쿼리 
파일 크기(바이트)와 관련된 5개의 쿼리방법
- 가장 큰 파일의 크기(바이트)를 검색하는 방법
- 가장 작은 파일의 크기(바이트)를 검색하는 방법
- 지정한 루트 폴더에 있는 하나 이상의 폴더에서 FileInfo 개체의 가장 큰 파일이나 가장 작은 파일을 검색하는 방법
- 가장 큰 10개 파일과 같은 시퀀스를 검색하는 방법
- 지정한 크기보다 작은 파일을 무시하여 파일 크기(바이트)에 따라 파일을 그룹으로 정렬하는 방법
       string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        // fileList is an IEnumerable<System.IO.FileInfo>
        var fileList = GetFiles(startFolder);

        //Return the size of the largest file
        long maxSize =
            (from file in fileList
             let len = GetFileLength(file)
             select len).Max();

        Console.WriteLine("The length of the largest file under {0} is {1}",
            startFolder, maxSize);

        // Return the FileInfo object for the largest file by sorting and selecting from beginning of list
        System.IO.FileInfo longestFile =
            (from file in fileList
            let len = GetFileLength(file)
            where len > 0
            orderby len descending
            select file).First();

        Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes",
                            startFolder, longestFile.FullName, longestFile.Length);

        //Return the FileInfo of the smallest file
        System.IO.FileInfo smallestFile =
            (from file in fileList
            let len = GetFileLength(file)
            where len > 0
            orderby len ascending
            select file).First();

        Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes",
                            startFolder, smallestFile.FullName, smallestFile.Length);

        //Return the FileInfos for the 10 largest files queryTenLargest is an IEnumerable<System.IO.FileInfo>
        var queryTenLargest =
            (from file in fileList
            let len = GetFileLength(file)
            orderby len descending
            select file).Take(10);

        Console.WriteLine("The 10 largest files under {0} are:", startFolder);

        foreach (var v in queryTenLargest)
        {
            Console.WriteLine("{0}: {1} bytes", v.FullName, v.Length);
        }


        // Group the files according to their size, leaving out files that are less than 200000 bytes.
        var querySizeGroups =
            from file in fileList
            let len = GetFileLength(file)
            where len > 0
            group file by (len / 100000) into fileGroup
            where fileGroup.Key >= 2
            orderby fileGroup.Key descending
            select fileGroup;


        foreach (var filegroup in querySizeGroups)
        {
            Console.WriteLine(filegroup.Key.ToString() + "00000");
            foreach (var item in filegroup)
            {
                Console.WriteLine("\t{0}: {1}", item.Name, item.Length);
            }
        }

2.1.5. 디렉터리 트리의 중복 파일 쿼리
 첫 번째 쿼리에서는 간단한 키를 사용하여 일치하는 항목을 확인(이름은 같지만 내용은 다를 수 있는 파일임)
 두 번째 쿼리에서는 복합 키를 사용하여 FileInfo 개체의 세 가지 속성에 대한 일치 여부를 확인
class QueryDuplicateFileNames
{
    static void Main(string[] args)
    {  
        // Uncomment QueryDuplicates2 to run that query.
        QueryDuplicates();           
        // QueryDuplicates2();

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

    static void QueryDuplicates()
    {
        // Change the root drive or folder if necessary
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // used in WriteLine to keep the lines shorter
        int charsToSkip = startFolder.Length;

        // var can be used for convenience with groups.
        var queryDupNames =
            from file in fileList
            group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
            where fileGroup.Count() > 1
            select fileGroup;

        // Pass the query to a method that will
        // output one page at a time.
        PageOutput<string,string>(queryDupNames);
    }

    // A Group key that can be passed to a separate method.
    // Override Equals and GetHashCode to define equality for the key.
    // Override ToString to provide a friendly name for Key.ToString()
    class PortableKey
    {
        public string Name { get; set; }
        public DateTime CreationTime { get; set; }
        public long Length {get;  set;}

        public override bool Equals(object obj)
        {
            PortableKey other = (PortableKey)obj;
            return other.CreationTime == this.CreationTime &&
                   other.Length == this.Length &&
                   other.Name == this.Name;
        }

        public override int GetHashCode()
        {
            string str = String.Format("{0}{1}{2}", this.CreationTime, this.Length, this.Name);
            return str.GetHashCode();
        }
        public override string ToString()
        {
            return String.Format("{0} {1} {2}", this.Name, this.Length, this.CreationTime);
        }
    }
    static void QueryDuplicates2()
    {
        // Change the root drive or folder if necessary.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

        // Make the the lines shorter for the console display
        int charsToSkip = startFolder.Length;

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // Note the use of a compound key. Files that match
        // all three properties belong to the same group.
        // A named type is used to enable the query to be
        // passed to another method. Anonymous types can also be used
        // for composite keys but cannot be passed across method boundaries
        //
        var queryDupFiles =
            from file in fileList
            group file.FullName.Substring(charsToSkip) by
                new PortableKey{ Name=file.Name, CreationTime=file.CreationTime, Length=file.Length } into fileGroup
            where fileGroup.Count() > 1

            select fileGroup;

        var list = queryDupFiles.ToList();

        int i = queryDupFiles.Count();

        PageOutput<PortableKey, string>(queryDupFiles);              
    }


    // A generic method to page the output of the QueryDuplications methods
    // Here the type of the group must be specified explicitly. "var" cannot
    // be used in method signatures. This method does not display more than one
    // group per page.
    private static void PageOutput<K,V>(IEnumerable<System.Linq.IGrouping<K, V>> groupByExtList)
    {
        // Flag to break out of paging loop.
        bool goAgain = true;

        // "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
        int numLines = Console.WindowHeight - 3;

        // Iterate through the outer collection of groups.
        foreach (var filegroup in groupByExtList)
        {
            // Start a new extension at the top of a page.
            int currentLine = 0;

            // Output only as many lines of the current group as will fit in the window.
            do
            {
                Console.Clear();
                Console.WriteLine("Filename = {0}", filegroup.Key.ToString() == String.Empty ? "[none]" : filegroup.Key.ToString());

                // Get 'numLines' number of items starting at number 'currentLine'.
                var resultPage = filegroup.Skip(currentLine).Take(numLines);

                //Execute the resultPage query
                foreach (var fileName in resultPage)
                {
                    Console.WriteLine("\t{0}", fileName);
                }

                // Increment the line counter.
                currentLine += numLines;

                // Give the user a chance to escape.
                Console.WriteLine("Press any key to continue or the 'End' key to break...");
                ConsoleKey key = Console.ReadKey().Key;
                if (key == ConsoleKey.End)
                {
                    goAgain = false;
                    break;
                }
            } while (currentLine < filegroup.Count());

            if (goAgain == false)
                break;
        }
    }


    // This method assumes that the application has discovery
    // permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
}

2.1.6. 폴더의 파일 내용 쿼리  

        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        string searchTerm = @"Visual Studio";

        // Search the contents of each file.
        // A regular expression created with the RegEx class
        // could be used instead of the Contains method.
        // queryMatchingFiles is an IEnumerable<string>.
        var queryMatchingFiles =
            from file in fileList
            where file.Extension == ".htm"
            let fileText = GetFileText(file.FullName)
            where fileText.Contains(searchTerm)
            select file.FullName;

        // Execute the query.
        Console.WriteLine("The term \"{0}\" was found in:", searchTerm);
        foreach (string filename in queryMatchingFiles)
        {
            Console.WriteLine(filename);
        }

3. LINQ to XML
http://msdn.microsoft.com/ko-kr/library/bb387098.aspx
DOM(문서 개체 모델) XML 프로그래밍 인터페이스와 유사.
LINQ to XML의 쿼리 기능은 기능 면에서(구문 면에서는 아니지만) XPath 및 XQuery와 유사.
클래스들

3.1. LINQ to XML과 DOM 비교
LINQ to XML과 현재 주로 사용되는 XML 프로그래밍 API인 W3C DOM(문서 개체 모델)의 몇 가지 주요 차이점

3.1.1. W3C DOM에서는 상향식으로 XML 트리를 빌드. 즉, 문서를 만들고 요소를 만든 다음 요소를 문서에 아래코드와 같이 추가 하는 형식이지만
LINQ to XML에서 함수 생성을 사용하여 훨씬 직관적인 코드를 생성할 수 있다.
 예) DOM의 Microsoft 구현인 XmlDocument를 사용하여 XML 트리를 만드는 일반적인 방법
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("Name");
name.InnerText = "Patrick Hines";
XmlElement phone1 = doc.CreateElement("Phone");
phone1.SetAttribute("Type", "Home");
phone1.InnerText = "206-555-0144";       
XmlElement phone2 = doc.CreateElement("Phone");
phone2.SetAttribute("Type", "Work");
phone2.InnerText = "425-555-0145";       
XmlElement street1 = doc.CreateElement("Street1");       
street1.InnerText = "123 Main St";
XmlElement city = doc.CreateElement("City");
city.InnerText = "Mercer Island";
XmlElement state = doc.CreateElement("State");
state.InnerText = "WA";
XmlElement postal = doc.CreateElement("Postal");
postal.InnerText = "68042";
XmlElement address = doc.CreateElement("Address");
address.AppendChild(street1);
address.AppendChild(city);
address.AppendChild(state);
address.AppendChild(postal);
XmlElement contact = doc.CreateElement("Contact");
contact.AppendChild(name);
contact.AppendChild(phone1);
contact.AppendChild(phone2);
contact.AppendChild(address);
XmlElement contacts = doc.CreateElement("Contacts");
contacts.AppendChild(contact);
doc.AppendChild(contacts);
이 코딩 스타일은 XML 트리의 구조에 대한 많은 정보를 시각적으로 제공하지 않는다.
LINQ to XML에서는 이러한 XML 트리 생성 방법을 지원하며 추가적으로 다음과 같이
함수 생성에서는 XElement 및 XAttribute 생성자를 사용하여 XML 트리를 빌드하는 방법을 제공

비교) LINQ to XML에서 함수 생성을 사용하여 동일한 XML 트리를 생성하는 방법
XElement contacts =
    new XElement("Contacts",
        new XElement("Contact",
            new XElement("Name", "Patrick Hines"),
            new XElement("Phone", "206-555-0144",
                new XAttribute("Type", "Home")),
            new XElement("phone", "425-555-0145",
                new XAttribute("Type", "Work")),
            new XElement("Address",
                new XElement("Street1", "123 Main St"),
                new XElement("City", "Mercer Island"),
                new XElement("State", "WA"),
                new XElement("Postal", "68042")
            )
        )
    );
3.1.2 LINQtoXML에서는  바로 위 코드와 같이  LINQ XML 요소(XElement)로 직접 XML핸들링이 가능하다.
XML 파일에서 T:System.Xml.Linq.XElement 개체를 직접 로드도 가능
T:System.Xml.Linq.XElement 개체를 파일이나 스트림으로 serialize가능


반면에 W3C DOM에서는 아래코드와 같이 XML 문서가 XML 트리의 논리 컨테이너로 사용
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("Name");
name.InnerText = "Patrick Hines";
doc.AppendChild(name);
여러 문서에서 요소를 사용하려면 여러 문서의 노드를 가져와야 하지만 LINQ to XML에서는 이러한 복잡한 작업이 필요 없다.
LINQ to XML을 사용할 때 문서의 루트 수준에서 주석이나 처리 명령을 추가하려는 경우에만 XDocument 클래스를 사용.

3.1.3 LINQ to XML에서는 이름 및 네임스페이스의 처리가 간단하다.
기존 DOM 프로그래밍에서는 이름, 네임스페이스 및 네임스페이스 접두사의 처리는 XML 프로그래밍의 복잡한 부분.
LINQ to XML에서는 네임스페이스 접두사를 처리하는 요구 사항을 제거하여 이름과 네임스페이스를 단순화.
네임스페이스 접두사를 제어할 수 있다.
네임스페이스 접두사를 명시적으로 제어하지 않을경우
LINQ to XML에서는 serialize할 때 네임스페이스 접두사를 할당하거나, 기본 네임스페이스를 사용하여 serialize해야한다.
기본 네임스페이스가 사용되는 경우 생성되는 문서에는 네임스페이스 접두사가 없다.
기존 DOM 프로그래밍 방식에서는 노드의 이름을 변경할 수 없다.
대신 새 노드를 만들고 모든 자식 노드를 새 노드에 복사해야 하므로 원래 노드 ID가 손실됩니다.
LINQ to XML에서는 노드에서 XName 속성을 설정할 수 있도록 하여 이 문제를 방지합니다.

3.1.3.1. XML 네임스페이스 작업(네임스페이스 접두사 제어)
 LINQ to XML에서 XML 이름을 나타내는 클래스는 XName
 - 네임스페이스를 사용하여 문서 만들기(C#)(LINQ to XML)

 예1) 네임스페이스가 하나 포함된 문서를생성. 기본적으로 LINQ to XML은 기본 네임스페이스를 사용하여 이 문서를 serialize
  // Create an XML tree in a namespace.
XNamespace aw = "http://www.adventure-works.com";
XElement root = new XElement(aw + "Root", new XElement(aw + "Child", "child content"));
Console.WriteLine(root);
결과)
<Root xmlns="http://www.adventure-works.com">
  <Child>child content</Child>
</Root>

예2) 네임스페이스 접두사가 포함된 네임스페이스를 선언
// Create an XML tree in a namespace, with a specified prefix
XNamespace aw = "http://www.adventure-works.com";
XElement root = new XElement(aw + "Root",
    new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com"),
    new XElement(aw + "Child", "child content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com">
  <aw:Child>child content</aw:Child>
</aw:Root>

예3)두 네임스페이스가 포함된 문서를 만드는 방법
두 네임스페이스 중 하나는 기본 네임스페이스이고 다른 하나는 접두사가 포함된 네임스페이스
루트 요소에 네임스페이스 특성을 포함하면 http://www.adventure-works.com이 기본 네임스페이스가 되도록 네임스페이스가 serialize되고
www.fourthcoffee.com이 "fc" 접두사를 사용하여 serialize됩니다.
기본 네임스페이스를 선언하는 특성을 만들려면 네임스페이스 없이 이름이 "xmlns"인 특성을 만듭니다.
특성 값은 기본 네임스페이스 URI입니다.
// The http://www.adventure-works.com namespace is forced to be the default namespace.
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
    new XAttribute("xmlns", http://www.adventure-works.com), //기본 네임스페이스가 된다.
    new XAttribute(XNamespace.Xmlns + "fc", "www.fourthcoffee.com"),
    new XElement(fc + "Child",
        new XElement(aw + "DifferentChild", "other content")
    ),
    new XElement(aw + "Child2", "c2 content"),
    new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
결과)
<Root xmlns="http://www.adventure-works.com" xmlns:fc="www.fourthcoffee.com">
  <fc:Child>
    <DifferentChild>other content</DifferentChild>
  </fc:Child>
  <Child2>c2 content</Child2>
  <fc:Child3>c3 content</fc:Child3>
</Root>

예4)네임스페이스 접두사가 있는 두 가지 네임스페이스가 포함된 문서를 만들기
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
    new XAttribute(XNamespace.Xmlns + "aw", aw.NamespaceName),
    new XAttribute(XNamespace.Xmlns + "fc", fc.NamespaceName),
    new XElement(fc + "Child",
        new XElement(aw + "DifferentChild", "other content")
    ),
    new XElement(aw + "Child2", "c2 content"),
    new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com" xmlns:fc="www.fourthcoffee.com">
  <fc:Child>
    <aw:DifferentChild>other content</aw:DifferentChild>
  </fc:Child>
  <aw:Child2>c2 content</aw:Child2>
  <fc:Child3>c3 content</fc:Child3>
</aw:Root>

예5)확장된 이름이 포함된 문자열을 전달하여 위와 동일한 결과얻기
{}안의 내용을 파싱해야하므로 성능이 명시적으로 XNamespace를 선언하는 경우보다 떨어진다.
// Create an XML tree in a namespace, with a specified prefix
XElement root = new XElement("{http://www.adventure-works.com}Root",
    new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com"),
    new XElement("{http://www.adventure-works.com}Child", "child content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com">
  <aw:Child>child content</aw:Child>
</aw:Root>

3.1.3.2. C#에서 기본 네임스페이스 범위(LINQ to XML)
XML 트리에 나타나는 기본 네임스페이스는 쿼리의 범위에 포함되지 않는다.
기본 네임스페이스에 있는 XML을 사용하는 경우 XNamespace 변수를 선언하고 로컬 이름과 결합하여
쿼리에서 사용할 정규화된 이름을 만들어야만 쿼리식에서 사용할 수 있다.

예1)집합에서는 기본 네임스페이스의 XML이 로드되지만 적절하지 않게 쿼리되는 경우의 예
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
    <Child>1</Child>
    <Child>2</Child>
    <Child>3</Child>
    <AnotherChild>4</AnotherChild>
    <AnotherChild>5</AnotherChild>
    <AnotherChild>6</AnotherChild>
</Root>");
IEnumerable<XElement> c1 =
    from el in root.Elements("Child")
    select el;
Console.WriteLine("Result set follows:");
foreach (XElement el in c1)
    Console.WriteLine((int)el);
Console.WriteLine("End of result set");
결과)
Result set follows:
End of result set

예2) 집합에서는 네임스페이스의 XML을 쿼리할 수 있도록 필요한 수정을 하는 방법을
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
    <Child>1</Child>
    <Child>2</Child>
    <Child>3</Child>
    <AnotherChild>4</AnotherChild>
    <AnotherChild>5</AnotherChild>
    <AnotherChild>6</AnotherChild>
</Root>");
XNamespace aw = "http://www.adventure-works.com";
IEnumerable<XElement> c1 =
    from el in root.Elements(aw + "Child") //명시적으로 지정.
    select el;
Console.WriteLine("Result set follows:");
foreach (XElement el in c1)
    Console.WriteLine((int)el);
Console.WriteLine("End of result set");
결과)
Result set follows:
1
2
3
End of result set

3.1.4 XML을 로드하기 위한 정적 메서드 지원 
예) 파일에서 XML 로드
XElement booksFromFile = XElement.Load(@"books.xml");
Console.WriteLine(booksFromFile);

3.1.5 DTD 구문에 대한 지원 제거  
LINQ to XML에서는 엔터티와 엔터티 참조에 대한 지원 기능을 제거하여 XML 프로그래밍을 더욱 단순화.  

3.1.6 조각에 대한 지원   
LINQ to XML에서는 XmlDocumentFragment 클래스와 동일한 항목을 제공하지만 대부분의 경우에 XmlDocumentFragment 개념은 XNode의 IEnumerable<(Of <(T>)>)이나 XElement의 IEnumerable<(Of <(T>)>)로 형식화된 쿼리의 결과에 의해 처리될 수 있다.

3.1.7 XPathNavigator에 대한 지원  
LINQ to XML에서는 System.Xml.XPath 네임스페이스의 확장 메서드를 통해 XPathNavigator를 지원.
System.Xml.XPath..::.Extensions 클래스의 멤버함수를 이용

3.1.7.1 Extensions..::.CreateNavigator 메서드
XNode에 대해 XPathNavigator를 만듬.
이 메서드에서 반환되는 XPathNavigator를 사용하여 XML 트리를 편집할 수 없다. (CanEdit 속성은 false로 반환).
XDocumentType 노드에 대해 XPathNavigator를 만들 수 없다.(문서형식(XDocumentType)은 XPath 데이터 모델과 관련이 없다)
네임스페이스 선언은 왼쪽에서 오른쪽으로 보고.
XmlDocument 네임스페이스는 오른쪽에서 왼쪽으로 보고.
이 메서드에서 반환되는 탐색기에 대해 MoveToId 메서드는 지원되지 않음.
이 메서드를 사용하여 XSLT 변환가능.

 예)XML 트리를 만들고, XML 트리에서 XPathNavigator를 만들고, 새 문서를 만들고, 새 문서에 쓸 XmlWriter를 만든다.
그런 다음 XPathNavigator 및 XmlWriter를 변환에 전달하여 XSLT 변환을 호출할 수 있다.
변환이 완료된 후 새 XML 트리는 변환 결과로 채운다.

XSLT 변환을 수행하려면 XmlReader 또는 XPathNavigator를 사용.
XmlReader를 사용할 때 더 빨리 실행되는 변환도 있고, XPathNavigator를 사용할 때 더 빨리 실행되는 변환도 있습니다.

string xslMarkup = @"<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
    <xsl:template match='/Parent'>
        <Root>
            <C1><xsl:value-of select='Child1'/></C1>
            <C2><xsl:value-of select='Child2'/></C2>
        </Root>
    </xsl:template>
</xsl:stylesheet>";

XDocument xmlTree = new XDocument(
    new XElement("Parent",
        new XElement("Child1", "Child1 data"),
        new XElement("Child2", "Child2 data")
    )
);

XDocument newTree = new XDocument();
using (XmlWriter writer = newTree.CreateWriter()) {
    // Load the style sheet.
    XslCompiledTransform xslt = new XslCompiledTransform();
    xslt.Load(XmlReader.Create(new StringReader(xslMarkup)));

    // Execute the transform and output the results to a writer.
    xslt.Transform(xmlTree.CreateNavigator(), writer);
}

Console.WriteLine(newTree);
결과)
<Root>
  <C1>Child1 data</C1>
  <C2>Child2 data</C2>
</Root>

3.1.7.2 Extensions..::.XPathEvaluate 메서드
XPath 식을 평가
예) 다음 예제에서는 특성이 있는 작은 XML 트리를 만든 다음 XPathEvaluate 메서드를 사용하여 특성을 검색합니다.
String xml = "<root a='value'/>";
XDocument d = XDocument.Parse(xml);
IEnumerable att = (IEnumerable)d.XPathEvaluate("/root/@a");
Console.WriteLine(att.Cast<XAttribute>().FirstOrDefault());
결과)
a="value"

3.1.7.3 Extensions..::.XPathSelectElement 메서드
XPath 식을 사용하여 XElement를 선택
예) 작은 XML 트리를 만든 다음 XPathSelectElement를 사용하여 단일 요소를 선택
XElement root = new XElement("Root",
    new XElement("Child1", 1),
    new XElement("Child2", 2),
    new XElement("Child3", 3),
    new XElement("Child4", 4),
    new XElement("Child5", 5),
    new XElement("Child6", 6)
);
XElement el = root.XPathSelectElement("./Child4");
Console.WriteLine(el);
결과)
<Child4>4</Child4>

3.1.7.4 Extensions..::.XPathSelectElements 메서드
XPath 식을 사용하여 요소 컬렉션을 선택
예) 작은 XML 트리를 만든 다음 XPathSelectElements를 사용하여 요소 집합을 선택합
XElement root = new XElement("Root",
    new XElement("Child1", 1),
    new XElement("Child1", 2),
    new XElement("Child1", 3),
    new XElement("Child2", 4),
    new XElement("Child2", 5),
    new XElement("Child2", 6)
);
IEnumerable<XElement> list = root.XPathSelectElements("./Child2");
foreach (XElement el in list)
    Console.WriteLine(el);
결과)
<Child2>4</Child2>
<Child2>5</Child2>
<Child2>6</Child2>

3.1.8 공백 및 들여쓰기에 대한 지원   
서식이 있는 XML을 serialize하는 경우 XML 트리의 유효 공백만 유지(LINQ to XML의 기본 동작)
LINQ to XML에서는 DOM의 경우처럼 serialize된 Whitespace 노드 형식을 사용하는 대신 XText 노드로 공백을 저장.

3.1.9 주석에 대한 지원    
LINQ to XML에서 주석을 사용하여 임의의 형식에 대한 임의의 개체를 XML 트리의 XML 구성 요소와 연결
주석은 XML infoset의 일부가 아니므로 serialize되거나 deserialize되지 않는다.
아래 메서드호출(형식별로 주석을 검색가능)
AddAnnotation - XObject의 주석 목록에 개체를 추가합니다.
Annotation - 지정된 형식의 첫 번째 주석 개체를 XObject에서 가져옵니다.
Annotations -  XObject에 대한 지정된 형식의 주석 컬렉션을 가져옵니다.
RemoveAnnotations - 지정된 형식의 주석을 XObject에서 제거합니다.

예1) XElement에 주석을 추가
 public class MyAnnotation {
    private string tag;
    public string Tag {get{return tag;} set{tag=value;}}
    public MyAnnotation(string tag) {
        this.tag = tag; }} public class Program {
    public static void Main(string[] args) {  
        MyAnnotation ma = new MyAnnotation("T1");
        XElement root = new XElement("Root", "content");
        root.AddAnnotation(ma);

        MyAnnotation ma2 = (MyAnnotation)root.Annotation<MyAnnotation>();
        Console.WriteLine(ma2.Tag);
    }
}
결과)
T1

예2)  요소의 주석을 검색.
public class MyAnnotation {
    private string tag;
    public string Tag {get{return tag;} set{tag=value;}}
    public MyAnnotation(string tag) {
        this.tag = tag; }} class Program {
    static void Main(string[] args) {  
        XElement root = new XElement("Root", "content");
        root.AddAnnotation(new MyAnnotation("T1"));
        root.AddAnnotation(new MyAnnotation("T2"));
        root.AddAnnotation("abc");
        root.AddAnnotation("def");

        IEnumerable<MyAnnotation> annotationList;
        annotationList = root.Annotations<MyAnnotation>();
        foreach (MyAnnotation ma in annotationList)
            Console.WriteLine(ma.Tag);
        Console.WriteLine("----");

        IEnumerable<string> stringAnnotationList;
        stringAnnotationList = root.Annotations<string>();
        foreach (string str in stringAnnotationList)
            Console.WriteLine(str);
    }
}
결과)
T1
T2
----
abc
def

3.1.10 스키마 정보에 대한 지원
LINQ to XML에서는 System.Xml.Schema 네임스페이스의 확장 메서드를 통해 XSD 유효성 검사를 지원
XML 트리가 XSD를 준수하는지 확인할 수 있으며, PSVI(Post-Schema-Validation Infoset)를 사용하여 XML 트리를 채울 수 있다
예1) XmlSchemaSet을 만든 다음 스키마 집합에 대해 두 XDocument 개체의 유효성을 검사.
string xsdMarkup =
    @"<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
       <xsd:element name='Root'>
        <xsd:complexType>
         <xsd:sequence>
          <xsd:element name='Child1' minOccurs='1' maxOccurs='1'/>
          <xsd:element name='Child2' minOccurs='1' maxOccurs='1'/>
         </xsd:sequence>
        </xsd:complexType>
       </xsd:element>
      </xsd:schema>";
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup)));

XDocument doc1 = new XDocument(
    new XElement("Root",
        new XElement("Child1", "content1"),
        new XElement("Child2", "content1")
    )
);

XDocument doc2 = new XDocument(
    new XElement("Root",
        new XElement("Child1", "content1"),
        new XElement("Child3", "content1")
    )
);

Console.WriteLine("Validating doc1");
bool errors = false;
doc1.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("doc1 {0}", errors ? "did not validate" : "validated");

Console.WriteLine();
Console.WriteLine("Validating doc2");
errors = false;
doc2.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("doc2 {0}", errors ? "did not validate" : "validated");
결과)
Validating doc1
doc1 validated

Validating doc2
The element 'Root' has invalid child element 'Child3'. List of possible elements expected: 'Child2'.
doc2 did not validate

예2) :Track('ctl00_rs1_mainContentContainer_cpe497038_c|ctl00_rs1_mainContentContainer_ctl23',this);" href="http://msdn.microsoft.com/ko-kr/library/bb387025.aspx">샘플 XML 파일: 고객 및 주문(LINQ to XML)에 있는 XML 문서가 :Track('ctl00_rs1_mainContentContainer_cpe497038_c|ctl00_rs1_mainContentContainer_ctl24',this);" href="http://msdn.microsoft.com/ko-kr/library/bb675181.aspx">샘플 XSD 파일: 고객 및 주문에 있는 스키마별로 유효한지 확인한 다음 소스 XML 문서를 수정합니다. 여기에서는 첫 번째 고객에 대한 CustomerID 특성을 변경합니다. 변경한 후에는 주문이 존재하지 않는 고객을 참조하게 되므로 XML 문서가 더 이상 유효하지 않습니다.
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", "CustomersOrders.xsd");

Console.WriteLine("Attempting to validate");
XDocument custOrdDoc = XDocument.Load("CustomersOrders.xml");
bool errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");

Console.WriteLine();
// Modify the source document so that it will not validate.
custOrdDoc.Root.Element("Orders").Element("Order").Element("CustomerID").Value = "AAAAA";
Console.WriteLine("Attempting to validate after modification");
errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");
결과)
Attempting to validate
custOrdDoc validated

Attempting to validate after modification
The key sequence 'AAAAA' in Keyref fails to refer to some key.
custOrdDoc did not validate


3.2. LINQ to XML과 다른 XML 기술 비교
LINQ to XML, XmlReader, XSLT, MSXML 및 XmlLite와 같은 XML 기술을비교

3.2.1 LINQ to XML 와 XmlReader
XmlReader는 캐시하지 않으며 정방향으로만 작동하는 빠른 파서.
LINQ to XML은 XmlReader를 기반으로 구현되었으며 두 기술은 밀접하게 통합되어 있다.( XmlReader를 단독으로 사용가능)

예)초당 수백 개의 XML 문서를 구문 분석할 웹 서비스를 빌드하는데 문서의 구조가 동일하여
XML의 구문을 분석하는 코드의 구현을 하나만 작성하면 되는 경우 XmlReader를 단독으로 사용할 수 있습니다.
반면에 크기가 작은 다양한 XML 문서의 구문을 분석하는 시스템을 빌드하는데 각 문서의 구조가 서로 다르면
LINQ to XML에서 제공하는 성능 향상 기능을 사용할 수 있습니다.

3.2.2 LINQ to XML 와 XSLT
LINQ to XML과 XSLT는 모두 광범위한 XML 문서 변환 기능을 제공
XSLT는 규칙 기반의 선언적 방법.
고급 XSLT 프로그래머는 상태 비저장 방법을 강조하는 함수형 프로그래밍 스타일로 XSLT를 작성.
이 경우 부작용 없이 구현되는 순수 함수를 사용하여 변환을 작성할 수 있다.
이 규칙 기반 방법 또는 함수 방법은 대부분의 개발자에게 익숙하지 않으며 배우는 데 많은 시간과 노력이 필요.

XSLT는 고성능 응용 프로그램을 생성하는 생산성이 매우 높은 시스템.
예를 들어 규모가 큰 일부 웹 회사에서는 다양한 데이터 저장소에서 가져온 XML에서 HTML을 생성하는 방법으로 XSLT를 사용.
관리되는 XSLT 엔진은 XSLT를 CLR 코드로 컴파일하며 일부 시나리오에서 네이티브 XSLT 엔진보다 성능이 훨씬 좋다.
그러나 XSLT에서는 개발자가 C# 및 Visual Basic 지식을 활용할 수 없으며 복잡하고 다른 프로그래밍 언어로 코드를 작성해야 합니다.
C#(또는 Visual Basic) 및 XSLT와 같은 통합되지 않은 두 가지 개발 시스템을 사용하면 소프트웨어 시스템을 개발하고 유지 관리하기가 더 어렵습니다.

LINQ to XML 쿼리 식을 완전히 익히고 나면 강력한 LINQ to XML 변환 기술을 쉽게 사용할 수 있다.
기본적으로 다양한 소스에서 데이터를 가져와서 XElement 개체를 동적으로 생성하고 전체 데이터를 새 XML 트리로 어셈블하는 함수 생성을 사용하여 XML 문서를 만든다.
변환을 통해 완전히 새로운 문서가 생성될 수 있다.
LINQ to XML에서는 비교적 쉽고 직관적으로 변환을 생성할 수 있으며 생성되는 코드도 쉽게 읽을 수 있다.
따라서 개발 및 유지 관리 비용이 줄어듭니다.

LINQ to XML은 XSLT를 대체하기 위한 것이 아니다.
XSLT는 여전히 복잡하고 문서 중심적인 XML(특히 문서 구조가 제대로 정의되지 않은 경우) 변환에 사용할 수 있다.

XSLT는 W3C(World Wide Web Consortium) 표준이므로 표준 기술만 사용해야 하는 요구 사항이 있는 경우 XSLT가 더 적합할 수 있다.
XSLT는 XML이므로 프로그래밍 방식으로 조작할 수 있다.

3.2.3 LINQ to XML과 MSXML 비교
MSXML은 Microsoft Windows에 포함된 XML을 처리할 수 있는 COM 기반 기술.
MSXML은 XPath 및 XSLT를 지원하는 DOM의 기본적인 구현을 제공하며 캐시하지 않는 이벤트 기반의 SAX2 파서도 포함하고 있다.
MSXML은 성능이 좋으며 대부분의 시나리오에서 기본적으로 안전.
AJAX 스타일의 응용 프로그램에서 클라이언트측 XML 처리를 수행하기 위해 Internet Explorer에서 MSXML에 액세스할 수 있다.
C++, JavaScript 및 Visual Basic 6.0을 비롯한 COM을 지원하는 모든 프로그래밍 언어에서 MSXML을 사용할 수 있다.
CLR(공용 언어 런타임) 기반의 관리 코드에서는 MSXML을 사용하지 않는 것이 좋다.

3.2.4 LINQ to XML과 XmlLite 비교
XmlLite는 캐시하지 않으며 정방향으로만 작동하는 가져오기 파서.
개발자는 주로 C++와 함께 XmlLite를 사용.
관리 코드와 함께 XmlLite를 사용하는 것은 권장되지 않는다.
XmlLite의 주요 이점은 대부분의 시나리오에서 안전하며 간단하고 빠른 XML 파서다.
 XmlLite에서 위협에 노출되는 영역은 매우 작다.
신뢰할 수 없는 문서의 구문을 분석해야 하고 서비스 거부나 데이터 노출과 같은 공격으로부터 보호하려면 XmlLite를 선택하는 것이 좋다.
XmlLite는 LINQ(통합 언어 쿼리)와 통합되지 않았으며 LINQ에 핵심적인 프로그래머 생산성 향상 기능을 제공하지 않는다.

+ Recent posts