locked
WCF から得られた Entities に対して LINQ は発行できるでしょうか? RRS feed

  • 質問

  • http://msdn.microsoft.com/ja-jp/library/ee707362(v=vs.91).aspx

    においては、WCF を通して SQL Server への Query を Entities という塊で取得し、それを xaml 上の DataGrid の Items.DataSource に代入することによって、画面表示を得ています。Sample Application としては、DataGrid にデ-タを表示して完了でいいのでしょうが、実際の Application では、Database の各 Record の1個1個の「要素」を取得しそれを画面配置する、というのが通常だと思われます。そのためには Entities という中身の見えないものから、1個1個の要素を取り出さねばなりません。端的に言えば、SQL Server からデ-タを取得し、それを MainPage.xaml.cs 内の多次元配列に代入する、というような Sample Application があれば実際の開発に直結するわけです。もちろん、その多次元配列を実際に使うわけではなく、抽象度の高い Class の Instance に封入された「要素」を素っ裸にして取り出すところを教えてください。

    (1) Silverlight 内( MainPage.xaml.cs 内 ) において、上記のようにして得られた Entities に対して LINQ は発行できるのでしょうか。

    (2) Entities から要素を取り出すよりも、いっそのこと DataGrid から取り出す方がわかり易いでしょうか。

    2010年8月7日 19:55

回答

  • (1)Entities は IEnumerable<T>  型(この場合IEnumerable<Customer>)なので、LINQ を使って操作できます。

    (2)DataGrid から取り出すよりも Entities から取り出す方が、「サービスから取得したデータ」というのがはっきりして分かりやすいと、私は思います。



    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答の候補に設定 山本春海 2010年8月24日 8:04
    • 回答としてマーク 山本春海 2010年9月6日 5:21
    2010年8月7日 22:18
  • 参考にされたページをもとに、自分も試してみました。

    Load メソッドで取得したデータを LINQ で操作したい場合、

    CustomerDomainContext context = new CustomerDomainContext();
    LoadOperation<Customer> operation = context.Load(context.GetCustomerQuery());
    operation.Completed += (s, ea) =>
    {
      // Entities の操作は Completed イベントハンドラ内で行う
      LoadOperation<Customer> op = (LoadOperation<Customer>)s;
      Customer customer = op.Entities.ElementAt(1); // 1番目の要素を取得
      _nameTextBox.Text = customer.Name; // TextBox に表示
    };
    
    

    こんな風に、Completed イベントに登録したハンドラ内だと System.Linq のメソッド群を使って、Entities から要素を取り出せます。

    Completed イベントだけでなく、Load の引数に指定できるコールバックメソッド内でも、同じことができます。

     


    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答の候補に設定 山本春海 2010年8月24日 8:04
    • 回答としてマーク 山本春海 2010年9月6日 5:21
    2010年8月8日 4:33

すべての返信

  • (1)Entities は IEnumerable<T>  型(この場合IEnumerable<Customer>)なので、LINQ を使って操作できます。

    (2)DataGrid から取り出すよりも Entities から取り出す方が、「サービスから取得したデータ」というのがはっきりして分かりやすいと、私は思います。



    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答の候補に設定 山本春海 2010年8月24日 8:04
    • 回答としてマーク 山本春海 2010年9月6日 5:21
    2010年8月7日 22:18
  • >LINQ を使って操作できます。

    そのはずだと思って、自分で試しているのですが中々進みません。また、よろしくお願いします。

    2010年8月8日 0:08
  • 参考にされたページをもとに、自分も試してみました。

    Load メソッドで取得したデータを LINQ で操作したい場合、

    CustomerDomainContext context = new CustomerDomainContext();
    LoadOperation<Customer> operation = context.Load(context.GetCustomerQuery());
    operation.Completed += (s, ea) =>
    {
      // Entities の操作は Completed イベントハンドラ内で行う
      LoadOperation<Customer> op = (LoadOperation<Customer>)s;
      Customer customer = op.Entities.ElementAt(1); // 1番目の要素を取得
      _nameTextBox.Text = customer.Name; // TextBox に表示
    };
    
    

    こんな風に、Completed イベントに登録したハンドラ内だと System.Linq のメソッド群を使って、Entities から要素を取り出せます。

    Completed イベントだけでなく、Load の引数に指定できるコールバックメソッド内でも、同じことができます。

     


    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答の候補に設定 山本春海 2010年8月24日 8:04
    • 回答としてマーク 山本春海 2010年9月6日 5:21
    2010年8月8日 4:33
  • >Completed イベントに登録したハンドラ内

    far beyond です。それどころか、どこをどう検索しても、引っかかりませんでした。感謝、感謝です。念のために確認ですが

    > Customer customer = op.Entities.ElementAt(1); // 1番目の要素を取得
    の部分は、データベース用語で言うところの「 ID=1 の record を 1 件取得 」ということなんでしょうね。

    それから、Silverlight から SQL Server へデ-タを戻す側もどうせ far beyond でしょうから、できましたら Sample を Up しておいていただけると助かります。そして助かるのが私だけでなく、多くの人々であることを願っています( この辺の Silverright のフォーラムで、投稿がほとんどない、ということはどういうことを意味しているのでしょうか、、、)。

    2010年8月8日 5:34
  • ブログにコードを掲載したので、参考にどうぞ。

    http://d.hatena.ne.jp/griefworker/20100809/silverlight_wcf_ria_services_entity_framework

    Visual Studio が生成したコードは省略していますが、自分が書いたコードはすべて載せています。


    なかむら(http://d.hatena.ne.jp/griefworker)
    2010年8月9日 1:41
  • http://msdn.microsoft.com/ja-jp/library/ff713719(v=vs.91).aspx

    ファイル Employee.xaml.cs の中で以下のように記述することによって、SQL Server から届く幾重にも厚化粧されたデ-タを丸裸にできるようです。ただし、このようにデ-タを丸裸にして画面配置に用いたりした場合、デ-タを SQL Server に戻す時に、一発、ではいかないということかもしれません。

    namespace HRApp.Views {
        public partial class EmployeeList : Page {
            OrganizationContext _OrganizationContext = new OrganizationContext(); // 注意
            public EmployeeList()  {
                InitializeComponent();
            }

            // Executes when the user navigates to this page.
            protected override void OnNavigatedTo(NavigationEventArgs e)  {
            }

            protected void MyTry() {                                                                       // User Code
                foreach (Employee emp in _OrganizationContext.Employees) {
                    string s = (string)emp.Gender;
                }

                foreach(PurchaseOrderDetail p in _OrganizationContext.PurchaseOrderDetails  )  {
                    DateTime t = (DateTime)p.DueDate;
                }
            }
        }

    2010年8月13日 2:42
  • Build が通ったので上記でいいのかと思ったら、_OrganizationContext.Employees の中身は空でした。下記のものが正解でした。InitializeComponent(); に続けて書いてもだめでした。ここでは MyB というボタンの EventHandoler の中に書きました。本当は画面表示と同時に値が欲しいわけですから、これでは不十分かもしれません。また、できたと思っていた複数のテ-ブルからのデ-タ取得ですが、この書方では、テ-ブルの数だけ DomainDataSource が必要です。無理やり2つ、用意しましたら、正解の表示を出力した直後に、UnHandled Error ということで、難しいですね。

    namespace HRApp.Views {
        public partial class EmployeeList : Page {
            public EmployeeList() {
                InitializeComponent();
            }

            // Executes when the user navigates to this page.
            protected override void OnNavigatedTo(NavigationEventArgs e)  {
            }

            private void MyB_Click(object sender, RoutedEventArgs e) {
                OrganizationContext MyC = (OrganizationContext)(employeeDataSource.DomainContext);    // こっちでした
                foreach (Employee emp in MyC.Employees) {
                    MyB.Content += (string)emp.Gender;
                }
            }

        }
    }

    2010年8月13日 8:24
  • なかむらさんに教えていただいた方法ですと、下記のようになりますが、こちらのほうは、画面表示の直後に MyT の値としてセットされ、画面に表示されます。やっぱ、こっちですね。2つのテ-ブルからデ-タを取り出すのもできるかもしれません。

    namespace HRApp.Views
    {
        public partial class EmployeeList : Page
        {
            OrganizationContext _OrganizationContext = new OrganizationContext();
            public EmployeeList()
            {
                InitializeComponent();
                LoadOperation<Employee> operation = _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                operation.Completed += (s, ea) =>
                {
                    LoadOperation<Employee> op = (LoadOperation<Employee>)s;
                    foreach (Employee emp in op.Entities)
                    {
                        MyT.Text += (string)emp.Gender;
                    }
                };

            }

            // Executes when the user navigates to this page.
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }

        }
    }

    2010年8月13日 21:06
  • 複数のテ-ブルからデ-タを取得しようとして下記のように書いてみますと MyT に表示された後、 unhandled exception が起こります。その挙動は、前々回の投稿と全く同じエラーの状況です。

    namespace HRApp.Views
    {
        public partial class EmployeeList : Page
        {
            OrganizationContext _OrganizationContext = new OrganizationContext();
            public EmployeeList()
            {
                InitializeComponent();
                LoadOperation<Employee> operation = _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                operation.Completed += (s, ea) =>
                {
                    LoadOperation<Employee> op = (LoadOperation<Employee>)s;
                    foreach (Employee emp in op.Entities)
                    {
                        MyT.Text += (string)emp.Gender;
                    }
                };

                LoadOperation<PurchaseOrderDetail> oqeration = _OrganizationContext.Load(_OrganizationContext.GetPurchaseOrderDetailQuery());
                oqeration.Completed += (t, eb) =>
                {
                    LoadOperation<PurchaseOrderDetail> oq = (LoadOperation<PurchaseOrderDetail>)t;
                    foreach (PurchaseOrderDetail pur in oq.Entities)
                    {
                        MyT.Text += ((int)pur.ProductID).ToString();
                    }
                };
            }

            // Executes when the user navigates to this page.
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }

        }
    }

     

    2010年8月13日 21:27
  • 1つのテ-ブルに関してのみですが、ようやく Entities に対して LINQ を発行できるところまで来ました。これは select 文ですが、Update や insert もできるんですかね。method で書くよりも SQL 文で書いたほうが、自分としては見やすいのです。method ですが、、、昔 ASP.NET で DataSet に対して Find という method が SQL のごくごく限定された機能を代行していましたが、あの Find の発展形という感じ?

    namespace HRApp.Views
    {
        public partial class EmployeeList : Page
        {
            OrganizationContext _OrganizationContext = new OrganizationContext();
            public EmployeeList()
            {
                InitializeComponent();
                LoadOperation<Employee> operation = _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                operation.Completed += (s, ea) =>
                {
                    LoadOperation<Employee> op = (LoadOperation<Employee>)s;
                    var records= from x in op.Entities where(x.VacationHours>90) select x;     // この書き方なら SQL 文に見える
                    foreach (Employee emp in records)
                    {
                        MyT.Text += (string)emp.Gender;
                    }

                };

            }

            // Executes when the user navigates to this page.
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }

        }
    }

    2010年8月13日 21:50
  • Insert です。

    例外処理の部分を省略すれば、この程度で行けるようです。ただし、Employee という Table の場合、特殊な Column が設定されており、Employee の Instance を手動で作成するのが難しいので、下記の Code はエラーが発生します。ですから参考までです。Primary Key 以外は null を許容するに設定しておけば手動作成も簡単です。手元ではそのような Table に対して、動作することを確認しております。

    ファイル Employee.xaml.cs の中身

    namespace HRApp.Views {
        public partial class EmployeeList : Page {
            public EmployeeList() {
                InitializeComponent();
            }

            protected override void OnNavigatedTo(NavigationEventArgs e)  {}

            private void Button_Click(object sender, RoutedEventArgs e) {
                OrganizationContext _OrganizationContext = new OrganizationContext();
                _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                Employee emp = new Employee();
                emp.NationalIDNumber = "123";
                emp.ContactID = 1234;
                emp.LoginID = "asa";
                emp.ManagerID = 9876;
                emp.Title = "朝が来た";
                emp.BirthDate = new DateTime(2000, 1, 1);
                emp.MaritalStatus = "M";
                emp.Gender = "F";
                emp.HireDate = new DateTime(2010, 1, 1);
                emp.SalariedFlag = false;
                emp.VacationHours = 30;
                emp.SickLeaveHours = 30;
                emp.CurrentFlag = true;
                emp.rowguid = new System.Guid();   // これではだめなようです。
                emp.ModifiedDate = DateTime.Now;
                _OrganizationContext.InsertEmployee(emp);
            }
        }
    }

    ファイル OrganizationService.cs の中身の一部 2行だけ付け加えます。

            [Invoke]                                                                                                                                        // 付け加えます。
            public void InsertEmployee(Employee employee) {
                if ((employee.EntityState != EntityState.Detached))  {
                    this.ObjectContext.ObjectStateManager.ChangeObjectState(employee, EntityState.Added);
                } else {
                    this.ObjectContext.Employee.AddObject(employee);
                    this.ObjectContext.SaveChanges();                                                                                          // 付け加えます。
                }
            }

     

    2010年8月14日 3:16
  • Update がわかりません。

     下記のような Update を行おうとしたのですが
    This EntitySet of type 'HRApp.Web.Employee' does not support the 'Edit' operation.
    と叱られてしまいました。どのように修正すればよいでしょうか。

    OrganizationService.cs の中で
            [Invoke]
            public void UpdateEmployee(Employee currentEmployee) {
                this.ObjectContext.Employee.AttachAsModified(currentEmployee, this.ChangeSet.GetOriginal(currentEmployee));
                this.ObjectContext.SaveChanges();
            }

    Employee.xamls,cs の中で
            private void UpdateButton_Click(object sender, RoutedEventArgs e)  {
                OrganizationContext _OrganizationContext = new OrganizationContext();
                LoadOperation<Employee> operation = _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                operation.Completed += (s, ea) => {
                    LoadOperation<Employee> op = (LoadOperation<Employee>)s;
                    foreach (Employee emp in op.Entities) {
                        emp.Title += " 更新OK";
                        _OrganizationContext.UpdateEmployee(emp);
                    }
                };
                UpdateButton.Content = "完了";
            }

    2010年8月14日 5:00
  • できれば DataGrid も DataForm も使わずに Entity の Edit , Update をやりたかったのですが、ちょっと難しいようです。作成予定の自分のアプリでは、DataForm は元々使う予定がありますので、今回はこれを用いて Edit , Update を行うことにします。非同期の書き戻しなのでややこしくて当たり前なんだと思います。そういえば ASP.NET の DataSet など「書き戻しは、裏で適当なタイミングで自動でやってくれます」という触れ込みでしたが、ほんとのところはどうなっているんですかねえ、、、

    ファイル Employee.xaml の中身の一部

    <navigation:Page xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"  x:Class="HRApp.Views.EmployeeList"
               <!-- 途中省略-->
               xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
               >

        <Grid x:Name="LayoutRoot">
            <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" >
                <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">
                    <TextBlock Text="Employee List" Style="{StaticResource HeaderTextStyle}"/>
                    <riaControls:DomainDataSource Name="employeeDataSource" LoadSize="20" QueryName="GetSalariedEmployee" AutoLoad="True" SubmittedChanges="employeeDataSource_SubmittedChanges">
                        <riaControls:DomainDataSource.DomainContext>
                            <ds:OrganizationContext/>
                        </riaControls:DomainDataSource.DomainContext>
                    </riaControls:DomainDataSource>
                    <!-- DataGrid をコメントアウトしています。
                    <sdk:DataGrid  AutoGenerateColumns="True" IsReadOnly="True" Name="dataGrid1" MinHeight="100" Height="Auto" ItemsSource="{Binding Data, ElementName=employeeDataSource}"/>
                    -->
                    <dataForm:DataForm x:Name="dataForm1" Header="Employee Information" AutoGenerateFields="False" HorizontalAlignment="Left"  AutoEdit="False" AutoCommit="False" Width="400" Margin="0,12,0,0">
                    <!-- 以下省略-->


    ファイル Employee.xaml.cs の中身の一部

    namespace HRApp.Views {
        public partial class EmployeeList : Page {
            OrganizationContext _OrganizationContext = new OrganizationContext();
            public EmployeeList() {
                InitializeComponent();
                _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                dataForm1.ItemsSource = _OrganizationContext.Employees;
            }

            private void submitButton_Click(object sender, RoutedEventArgs e) {
                submitButton.IsEnabled = false;
                _OrganizationContext.SubmitChanges();
            }

            private void employeeDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)  {
                if (e.HasError) {
                    MessageBox.Show(string.Format("Changes were not saved: {0}", e.Error.Message));
                    e.MarkErrorAsHandled();
                }
                submitButton.IsEnabled = true;
            }
        }
    }

    2010年8月16日 0:11
  • Update の部分を変更して、例外処理が有効となるようにしました。その結果、DataGrid が無いだけでなく、DomainDataSource も不必要となりました。この書き方で、他の Database では Update も Delete も期待通りに動くのですが AdventureWorks に適用すると、以下のようなエラーとなります。

    Submit operation failed. An error occurred while updating the entries. See the inner exception for details. Inner exception message: The transaction ended in the trigger. The batch has been aborted.Employees cannot be deleted. They can only be marked as not current.

    どの辺がまずいのでしょうか。

    namespace HRApp.Views {
        public partial class EmployeeList : Page{
            OrganizationContext _OrganizationContext = new OrganizationContext();
            public EmployeeList() {
                InitializeComponent();
                _OrganizationContext.Load(_OrganizationContext.GetEmployeeQuery());
                dataForm1.ItemsSource = _OrganizationContext.Employees;
            }

            private void submitButton_Click(object sender, RoutedEventArgs e) {
                submitButton.IsEnabled = false;
                _OrganizationContext.SubmitChanges(SubmitChangedCallback, null);
            }

            private void deleteButton_Click(object sender, RoutedEventArgs e) {
                _OrganizationContext.Employees.Remove((Employee)dataForm1.CurrentItem);
            }

            private void SubmitChangedCallback(SubmitOperation op) {
                submitButton.IsEnabled = true;
                if (op.HasError) {
                    MessageBox.Show(op.Error.Message);
                }
            }
        }
    }

    2010年8月16日 3:41