トップ回答者
WCF から得られた Entities に対して LINQ は発行できるでしょうか?

質問
-
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
回答
-
参考にされたページをもとに、自分も試してみました。
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月8日 4:33
すべての返信
-
参考にされたページをもとに、自分も試してみました。
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月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