WCF RIA Services / Linq-to-SQL: include property from foreign tables - silverlight

Say you have the following related tables (Stores -> Categories -> Products)
Stores
Categories
Products
And I want to create a grid to edit Products. This is straightforward with RIA Services. But what if I also want to show StoreName from Stores and CategoryName from Categories in my Products list? The two extra columns should be readonly.
How can this be implemented?
Update: I'm trying to do this in it's simplest form. That is no ViewModel, only drag'n drop, code (if any) will go in codebehind. I'm using Ling2Sql and returning the default implementation for the GetProducts query.
Regards
Larsi

How do you have this set up? Are you binding to a ViewModel or just using the code behind? Is the web service sending back a list of Product LINQ object or are you doing something else?
There are a variety of options but it really depends on what you're trying to do.

The simplest way to do it is to annotate your metadata file for the products and let the grid generate the columns for you.
For instance, your tables will probably look something like this:
Product
int Id;
string ProductName;
int CategoryId;
Category
int Id;
string CategoryName;
int StoreId;
Store
int Id;
string StoreName;
Now, when you create your service, you can include the 3 tables/entities from your domain model and have it generate the metadata file for you. In that file, annotate the objects correctly like so:
internal sealed class ProductMetadata
{
[Key]
[Bindable(false)]
[Display(AutogenerateField=false)]
public int Id { get; set; }
[Bindable(true, BindingDirection.TwoWay)]
[Display(Name="Product")]
[StringLength(20, MinimumLength=3)]
public string ProductName { get; set; }
[Bindable(false)]
[Display(AuteogenerateField=false)]
public Category Category { get; set; }
[Required]
[Bindable(false)]
[Display(AutogenerateField=false)]
public CategoryId { get; set; }
}
You can do the same to your other objects' metadata.
The only other thing you might have to do is add 2 other columns to your grid, and have them map to Product.Category.CategoryName and Product.Category.Store.StoreName

Related

Why am I getting DbUpdateException: OptimisticConcurrencyException?

I have a Category class:
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
I also have a Subcategory class:
public class Subcategory
{
public int SubcategoryId { get; set; }
public Category Category { get; set; }
public string SubcategoryName { get; set; }
}
And a Flavor class:
public class Flavor
{
public int FlavorId { get; set; }
public Subcategory Subcategory { get; set; }
public string FlavorName { get; set; }
}
Then I also have Filling and Frosting classes just like the Flavor class that also have Category and Subcategory navigation properties.
I have a Product class that has a Flavor navigation property.
An OrderItem class represents each row in an order:
public class OrderItem
{
public int OrderItemId { get; set; }
public string OrderNo { get; set; }
public Product Product { get; set; }
public Frosting Frosting { get; set; }
public Filling Filling { get; set; }
public int Quantity { get; set; }
}
I'm having issues when trying to save an OrderItem object. I keep getting DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. with the Inner Exception being OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. I've stepped through my code several times and I can't find anything that modifies or deletes any entities loaded from the database. I've been able to save the OrderItem, but it creates duplicate entries of Product, Flavor, Subcategory and Category items in the DB. I changed the EntityState of the OrderItem to Modified, but that throws the above exception. I thought it might have been the fact that I have Product, Frosting and Filling objects all referencing the same Subcategory and Category objects, so I tried Detaching Frosting and Filling, saving, attaching, changing OrderItem entity state to Modified and saving again, but that also throws the above exception.
The following statement creates duplicates in the database:
db.OrderItems.Add(orderItem);
Adding any of the following statements after the above line all cause db.SaveChanges(); to throw the mentioned exception (both Modified and Detached states):
db.Entry(item).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory.Category).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory).State = EntityState.Modified;
db.Entry(item.Product.Flavor).State = EntityState.Modified;
db.Entry(item.Product).State = EntityState.Modified;
Can someone please give me some insight? Are my classes badly designed?
The first thing to check would be how the entity relationships are mapped. Generally the navigation properties should be marked as virtual to ensure EF can proxy them. One other optimization is that if the entities reference SubCategory then since SubCats reference a Category, those entities do not need both. You would only need both if sub categories are optional. Having both won't necessarily cause issues, but it can lead to scenarios where a Frosting's Category does not match the category of the Frosting's SubCategory. (Seen more than enough bugs like this depending on whether the code went frosting.CategoryId vs. frosting.SubCategory.CategoryId) Your Flavor definition seemed to only use SubCategory which is good, just something to be cautious of.
The error detail seems to point at EF knowing about the entities but not being told about their relationships. You'll want to ensure that you have mapping details to tell EF about how Frosting and SubCategory are related. EF can deduce some of these automatically but my preference is always to be explicit. (I hate surprises!)
public class FrostingConfiguration : EntityTypeConfiguration<Frosting>
{
public FlavorConfiguration()
{
ToTable("Flavors");
HasKey(x => x.FlavorId)
.Property(x => x.FlavorId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.SubCategory)
.WithMany()
.Map(x => x.MapKey("SubCategoryId");
}
}
Given your Flavor entity didn't appear to have a property for the SubCategoryId, it helps to tell EF about it. EF may be able to deduce this, but with IDs and the automatic naming conventions it looks for, I don't bother trying to remember what works automagically.
Now if this is EF Core, you can replace the .Map() statement with:
.ForeignKey("SubCategoryId");
which will set up a shadow property for the FK.
If SubCats are optional, then replace HasRequired with HasOptional. The WithMany() just denotes that while a Flavor references a sub category, SubCategory does not maintain a list of flavours.
The next point of caution is passing entities outside of the scope of the DBContext that they were loaded. While EF does support detaching entities from one context and reattaching them to another, I would argue that this practice is almost always far more trouble than it is worth. Mapping entities to POCO ViewModels/DTOs, then loading them on demand again when performing updates is simpler, and less error-prone then attempting to reattach them. Data state may have changed between the time they were initially loaded and when you go to re-attach them, so fail-safe code needs to handle that scenario anyways. It also saves the hassle of messing around with modified state in the entity sets. While it may seem efficient to not load the entities a second time, by adopting view models you can optimize reads far more efficiently by only pulling back and transporting the meaningful data rather than entire entity graphs. (Systems generally read far more than they update) Even for update-heavy operations you can utilize bounded contexts to represent large tables as smaller, simple entities to load and update a few key fields more efficiently.

How do I use a composite key for a one to many relationship in Code First EF

I am using EF Code First.
I need two tables, LedgerCategories and LedgerSubCategories with a one-to-many relationship (Categories -> SubCategories), with the keys in each being codes (strings) - i.e. LedgerCategoryCode and LedgerSubCategoryCode respectively. However, I need to allow the SubCategoryCode values to be the same for different Categories.
E.g. CategoryCode = REHAB, SubCategoryCodes = MATL, CONTR, and FEES; and CategoryCode = MAINT, SubCategoryCodes = MATL, CONTR, and FEES.
I'm thinking I need to use a composite key and include both the CategoryCode and SubCategoryCode fields in the LedgerSubCategories table. Currently I have:
public class LedgerCategory
{
[Key]
public string LedgerCategoryCode { get; set; }
public string Description { get; set; }
public List<LedgerSubCategory> LedgerSubCategories { get; set; }
}
public class LedgerSubCategory
{
[Key, Column(Order = 0)]
public string LedgerCategoryCode { get; set; }
[Key, Column(Order = 1)]
public string LedgerSubCategoryCode { get; set; }
public string Description { get; set; }
}
I am seeding these tables using only instances of the LedgerCategory class, having each contain a List of appropriately instantiated LedgerSubCategory classes. This appears to both set up the DB schema correctly (in my perception), and populate both tables appropriately.
But, when I reinstantiate a simple List of LedgerCategory, i.e.
using (var db = new BusinessLedgerDBContext())
{
var LedgerCategories = db.LedgerCategories.ToList();
}
The LedgerCategory instances don't contain their respective List of associated LedgerSubCategory instances.
I am trying to avoid, what seems like a kludge, to introduce a unique number or Guid ID field in LedgerSubCategories as a PK and just index off the other Code fields. I haven't tried this, but I'm not sure it would cause any different results for reinstantiating the LedgerCategories and getting associated LedgerSubCategories.
Any advice on how to do this appropriately and get proper results is appreciated.
To, I suppose, answer my own question, I have found that overriding OnModelCreating() in the respective DbContext with Fluent API to establish the one to many relationship and foreign key when the Code First framework establishes the desired DB Schema. There appears no other way to do this, such as with Attributes. By many accounts of others, including MSDN, Fluent API appears to be what is needed. However, that has led me to a new issue, or set of issues, which I've posed as a question here.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configures the one-many relationship between Categories and
// SubCategories, and established the Foreign Key in SubCategories
modelBuilder.Entity<Category>()
.HasMany<SubCategory>(c => c.SubCategories)
.WithRequired(s => s.Category)
.HasForeignKey<string>(s => s.CategoryCode);
}

MVC 5 Objects in Models and DB Storage

I'm new to MVC and I am trying to work out how to connect different models to display in a view Example
Model Teams
int TeamID
string Team_Name
Model Players
int PlayerID
string PlayerName
Now I want this information stored in a database so how would I go about linking the two?
So in the Players Model I can use either Team Team or int TeamID
Team_ID I can store in a db table but then have to somehow include the team table when I pull a list of players. or TEAM Team which I can then view the team name by modelItem.team.team_name but cant store a TEAM object in the database.
I know this is basic in terms of MVC but im just struggling to get my head round it.
Any suggestions or links to solutions?
Your entity classes should look something like:
public class Team
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
public class Player
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Team")]
public int TeamId { get; set; }
public virtual Team Team { get; set; }
}
With that, with an instance of Player, you can simply do:
player.Team.Name
To get the name of the team. If you have a collection of players you're iterating through, you should eagerly load Team first, so you don't end up with N+1 queries:
var players = db.Players.Include(m => m.Team).ToList();
If you need to go the other way, then, you can load the list of players from an instance of Team as well:
var players = team.Players.ToList();
Likewise, you can eagerly load the players to minimize queries:
var team = db.Teams.Include(m => m.Players).SingleOrDefault(m => m.Id == teamId);
For what it's worth, your class and property names defy conventions. Notice the class and property names in the sample code I provided. An entity class' name should always be singular: Team, not Teams. Property names should be pascal-cased and run together: LikeThis, not likeThis or Like_This. Also, it's an antipattern to include the class name in the property name. The Name property belongs to Team, for example, so of course it's the name of the team. There is zero point in prefixing it with Team (TeamName), and it only makes your code more verbose. For example, which reads better: team.Name or team.TeamName?

RIA Services SP2 Function Complex type not visible in Object Context

I am struggling with returning a complex type from my services layer. It doesnt seem to be accessible from my object context.
This is the query in the service layer. All compiling fine.
public IQueryable<USP_GetPostsByThreadID_Result> uspGetPostsByThreadID(int ThreadID)
{
return this.ObjectContext.USP_GetPostsByThreadID(ThreadID).AsQueryable();
}
When I try and call it from my client, the ForumContext is not seeing it. I checked the client generated file and nothing similar is being generated. Help!!!
The name of your method may not meet the expected convention for queries. Try one or both of the following:
Add the [Query] attribute
Rename the method to GetUspPostsByThreadID
Result:
[System.ServiceModel.DomainServices.Server.Query]
public IQueryable<USP_GetPostsByThreadID_Result> GetUspPostsByThreadID(int ThreadID)
{
return this.ObjectContext.USP_GetPostsByThreadID(ThreadID).AsQueryable();
}
Its very common to have a stored procedure returning data from multiple tables. The return type doesn't fit well under any of the Entity Types(Tables). Therefore if we define Complex Type as the return collection of objects from Stored Procedure invocation, it becomes quite a powerful tool for the developer.
Following these steps I have achieved successfully the configuration of complex type on a sample AdventureWorks database.
1. Refer the picture and ensure the Stored procedure and function import is done.
2. Add the Domain Service name it as AdventureDomainService.
3. Now its time to define the tell the RIA services framework to identify my Complex Type as Entity Type. To be able to do this, we need to identify a [Key] DataAnnotation. Entity types provide data structure to the application's data model and by design, each entity type is required to define a unique entity key. We can define key on one property or a set of properties in metadata class file AdventureDomainService.metadata.cs
First define the class then add MetadatatypeAttribute like :
[MetadataTypeAttribute(typeof(CTEmployeeManagers.CTEmployeeManagersMetadata))]
public partial class CTEmployeeManagers
{
internal sealed class CTEmployeeManagersMetadata
{
private CTEmployeeManagersMetadata() { }
[Key]
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ManagerID { get; set; }
public string ManagerFirstName { get; set; }
public string ManagerLastName { get; set; }
}
}
Define the Domain service method to return the collection of objects/entities for populating the Silverlight Grid or any other data consuming controls.
public IQueryable<CTEmployeeManagers> GetEmployeeManagers(int empId)
{
return this.ObjectContext.GetEmployeeManagers(empId).AsQueryable();
}
We define IQueryable if we are to fetch the records from datasources like SQL, whereas we define IEnumerable if we are to fetch the records from in memory collections,dictionaty,arrays.lists, etc.
Compile the server side to generate the client proxy.
In the Silverlight side open the MainPage.xaml or wherever the datagrid is put, then add following namespaces :
using System.ServiceModel.DomainServices.Client;
using SLBusinessApplication.Web;
using SLBusinessApplication.Web.Services;
..
Load the data and display:
public partial class MyPage : Page
{
AdventureDomainContext ctx = new AdventureDomainContext();
public MyPage()
{
InitializeComponent();
LoadOperation loadOp = this.ctx.Load(this.ctx.GetEmployeeManagersQuery(29));
myGrid.ItemsSource = loadOp.Entities;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
That is all that is needed to do.
It has to be part of an entity. Complex types cannot be returned by themselves

Passing computed values with RIA Services

I'm trying to figure out the a way to get extra data to be passed with the entities returned from a RIA domain service.
For example, let's say I want to display a DataGrid for "Orders" and include a column for the total items in an order.
Order Num. | Cust. Name | *No. of Items Ordered*
4545 | John | 4
1234 | Mike | 7
On the server side, with a Linq query, I could do:
var query =
from o in entities.Orders
select new OrderWithItemCount { Order = o, ItemCount = o.Items.Count() };
... and this will retrieve my orders along with the Items counts all in one go.
The problem is, I can't find anyway to propagate these results thru a domain service to the Silverlight client. I suppose I could use a standard WCF service, but what's the fun in that?
Update
What turned out to be the actual problem...
I had at one point actually already tried the "Easy way" that Nissan Fan and Florian Lim point out. When I tried it, I wasn't getting all my data. (I also need to include the customer Person in the query to get their name.) It turns out that what I thought was a limitation of RIA Services was actually a limitation of EF 4.0, in that saying entities.Orders.Include("Customer") won't work if you select a new type that isn't an Order. The work around is to explicitly select o.Customer in your select statement, and EF will automatically wire the selected Person into the assiocated property on Order.
Easy way:
Just add extra fields to your Order class (could be done in a partial class) and populate that in your DomainService.
Complicated but more flexible way:
Define OrderWithItemCount as an entity (needs to have a [Key] attribute), then transfer that.
public class OrderWithItemCount
{
[Key]
public int Id { get; set; }
// You need this, so that the DomainContext on the client can put them back together.
public int OrderId { get; set; }
[Include]
[Association("OrderReference", "OrderId", "Id")]
public Order Order { get; set; }
public int ItemCount { get; set; }
public Person Customer { get; set; }
}
public IQueryable<OrderWithItemCount> GetOrdersWithItemCount()
{
var query = from o in entities.Orders
select new OrderWithItemCount
{
OrderId = o.Id,
Order = o,
ItemCount = o.Items.Count,
Customer = o.Customer, // Makes sure EF also includes the Customer association.
};
return query;
}
There may be minor errors in the code, since I cannot test this at the moment, but I recently implemented something similar.
If you are using LINQ to SQL to produce your Domain Service you could simply go into the partial class for Orders and add a Property called NumberOfOrders which returns an Int representing the count. This property would carry through to the client without issue.
internal sealed class OrderMetadata
{
// Metadata classes are not meant to be instantiated.
private OrderMetadata()
{
}
... (property stubs auto-generated)
}
public int NumberOfOrders
{
get { return this.Items.Count; }
}
The reason why you cannot do this the way you demonstrated above is because you cannot marshal across anything but conrete classes (anonyous classes are a no-go). By adding this property to the partial class it will effectively be part of its published signature.

Resources