Although I love what I'm learning, I'm finding it a struggle and need some help
I've been using these two tutorials which I think are awesome:
http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx
http://msdn.microsoft.com/en-us/data/gg685467
Currently my main problem/confusion is:
I have a CodeFirst table/entity I don't know how to correctly get data from other tables/entities to show in my views:
public class Car {
public int ID { get; set; }
public string Name { get; set; }
public int EngineID { get; set; }
public virtual Engine { get; set; }
}
public class Engine {
public int ID { get; set; }
public string Name { get; set; }
public string Manufacturer { get; set; }
// (plus a whole lot of other things)
}
Now when I create a View for Cars (using the List type/option) I get a nice autogenerated list
#foreach (var item in Model) {
<tr>
<td>#item.ID</td>
<td>#item.Name</td>
<td>#item.EngineID</td>
</tr>
Perfect... except EngineID is mostly worthless to the viewer, and I want to show Engine.Name instead
So I assumed I could use EF lazy loading:
<td>#item.Engine.Name</td>
Unfortunately when I tried that, it says my ObjectContext has been disposed so can't get any further data requiring a connection
Then I tried going to the controller and including the Engine.Name
var cars = (from c in db.Cars.Include("Engine.Name") select c;
Which tells me: Entities.Engine does not declare a navigation property with the name 'Name'
... ? Lies
Include("Engine") works fine, but all I want is the Name, and Include("Engine") is loading a large amount of things I don't want
Previously in a situation like this I have created a view in the DB for Car that includes EngineName as well. But with CodeFirst and my noobness I haven't found a way to do this
How should I be resolving this issue?
I thought perhaps I could create a Model pretty much identical to the Car entity, but add Engine.Name to it. This would be nice as I could then reuse it in multiple places, but I am at a loss on how to populate it etc
Wanting to learn TDD as well but the above is already frustrating me :p
Ps any other tutorial links or handy things to read will be greatly appreciated
It isn't lies as you are actually trying to include a property that's a 2nd level down withouth giving it a way to navigate. If you let EF generate your DB with this structure, it would likely have made a navigation table called something like Car_Engine and if you include the name without the object it HAS mapped, then it's not got a navigation property in your new object.
The simple way around this is to go:
(from c in db.Cars.Include("Engine") select new { c, EngineName = c.Engine.Name }
If you still get navigation property errors then you might need to make sure your are mapping to your schema correctly. This can be done with EntityTypeConfiguration classes using the fluent API - very powerful.
This of course won't help in strongly typing your car object to show in MVC.
If you'd like to get around this, your gut feeling is right. It's pretty common to use viewmodels that are read only (by design, not necessarily set to readonly) classes that provide simple views of your data.
Personally I keep my model quite clean and then have another project with viewmodels and a presentation project to populate. I'd avoid using overlapping entities in your core model as it might lead to unpredictable behaviour in the data context and at least a peristance nightmare when updating multiple entities (ie who's responsible for updating the engine name?).
Using you viewmodels, you can have a class called CarSummaryView or something with only the data you want on it. This also solves the issue of being vulnerable to overposting or underposting on your site. It can be populated by the above query quite easily.
PS There's a bunch of advantages to using viewmodels beyond just not loading full heirarchies. One of the biggest is the intrinsic benefit it gives you with avoiding over and underposting scenarios.
There's loads of ways to implement viewmodels, but as a simple CarView example:
public class CarView
{
public int ID { get; set; }
public string Name { get; set; }
public string EngineName { get; set; }
}
This should be clearly seperated from your entity model. In a big project, you'd have a single viewmodels project that the presenter can return, but in a smaller one you just need them in the same layer as the service code.
To populate it directly from the query, you can do the following
List<CarView> cars = (from c in db.Cars.Include("Engine.Name") select new CarView() { ID = c.ID, Name = c.Name, EngineName = c.Engine.Name }).ToList();
Related
Let's say we have a table called Car with columns such as ID, Identification, ModelName, OwnerId etc where OwnerId points to the primary key in the Owner table. This is all good, but then we want to add a Driver to the car, since we want to know who drives each car at a given time.
Sounds straight forward, right? Just create a Driver table and add a new nullable (there's no driver if the car is in the garage etc) int column called DriverId to the Car table, connect it with a foreign key and we're good to go.
I did this, and updated the EDMX in the model designer so the new table, column and foreign key showed up. All looks good. The DriverId property and the Driver navigation property are both there in the generated code and the new Driver class is also generated.
Now when I tried to use this new table and connect drivers to cars there's something very wrong. It looks like LINQ doesn't know about the DriverId column or the foreign key (navigation property) to Driver.
If I try getting a car with a given driver:
Car car = (from c in db.Cars.Where(x => x.DriverId == driverId) select c).FirstOrDefault();
I expect to get a car if the driver is currently driving a car or null otherwise.
What I get is this error message:
System.NotSupportedException: The specified type member 'DriverId' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Let's say we want to add a new car with a driver:
Car car = new Car{ blah, blah, etc, DriverId = driverId };
db.Cars.Add(car);
db.SaveChanges();
This seems to work fine. The new car gets inserted into the database. The only thing is the DriverId column is null, so obviously it doesn't work fine...
I'm guessing these things are connected. I just don't see what the issue is. Anyone know why or got some suggestions as to what I can try?
EDIT:
The Car and Driver classes is purely generated code, so I'm sure they look familiar:
[DataContract(IsReference = true)]
[KnownType(typeof(Owner))]
[KnownType(typeof(Driver))]
public partial class Car
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Identification { get; set; }
[DataMember]
public Nullable<int> OwnerId { get; set; }
[DataMember]
public Nullable<int> DriverId { get; set; }
//Navigation properties
[DataMember]
public virtual Owner Owner { get; set; }
[DataMember]
public virtual Driver Driver { get; set; }
}
[DataContract(IsReference = true)]
[KnownType(typeof (Car))]
public partial class Driver
{
//Constructor
public Driver()
{
this.Cars = new HashSet<Car>();
}
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
//Navigation properties
[DataMember]
public virtual HashSet<Car> Cars { get; set; }
}
Since the problem seems to be pinned and the work is in progrees, I'm pasting here trimmed discussion as a temporary "answer", just to leave the trace of work and to be sure that the comments with facts/thoughts don't not evaporate. Feel free to add/trim whatever here.
Eirik: if you manage to narrow it down to exact causes, please write a follow up answer explaining what was wrong and why did it compile silently with no errors. Just don't forget to "accept" it ;) Or if you like, add the info at the bottom of this "chatlog". I communitized this answer, so no points will be awarded.
Me: I too think one of your models went desynchronized. (..) One common thing was duplication of entries or not-updating the identifiers between model spaces. Could you try regenerating the EDMX from scratch, or do you have too many manual changes?
Eirik:
I've experienced bad updates earlier and solved them by removing the tables involved from the model and then running another update to get them readded. As for regenerating the EDMX from scratch...no. There's a bazillion manual changes, so I simply don't have the time to change it all back. I have already tried to remove the tables involved in this issue and readded them without luck. Same error/issue.
Me: Since nav-props are generated too, I assume you have a Driver property? Have you tried using objects instead? I mean, db.Cars.Where(x => x.Driver.Id == driverId) select c and Car car = new Car{ blah, blah, etc, Driver = driverObject };? If that worked, it'd indicate property<->column naming clash somewhere (...)
Eirik:
Changing x.DriverId == driverId to x.Driver.ID == DriverId results in the same error message (only initializers, members and navigation properties allowed). Driver should be recognized as a navigation property, but it isn't. DriverId should be recognized as a member, but it isn't. Insert also silently fails. That is, it inserts but with Car.DriverId being null.
Me: Nav props does not work. Your EDMX is screwed up, (..) I'd now chew through the three EDMX sections and verify all column, props, navs, and so on are correctly referring each other. Can also do another test: create a new edmx that contains only these tables, dont touch it, leave generated names and (..) compare contents of the new edmx with old big edmx and look for difference. (..)
Eirik:
I've created a new test project and generated the EDMX from scratch and stuff works as intended. So I'm guessing this is just a really bad case of update mess in the model designer. I'm currently going through both EDMX files and comparing the content. No solution yet, but I suspect I will find it eventuelly...zzZzzZ..thanks for the input so far!
Question is in the title. Can we programmatically change the database table which an object in the Model class, like one below, refers to and continue to operate on the new table?
public class Word
{
public int ID { get; set; }
public string Text { get; set; }
}
This originally refers to "Words" table automatically in EntityFramework, is there a way to change it before/during runtime? If so, how?
EDIT:
I get all the string used in Views in the project from the database table, "Words", by their ID's. Now, what I want is, a user enters a new language to system, and a new table will be created, for example WordsEnglish. From then, the Word object will refer to WordEnglish, if user selects English as language.
It would be desirable with a use case to better understand what you are trying to accomplish, but here goes...
In the DbContext.OnModelCreating method you can configure the model, e.g.
// Removes pluralization convention for all tables.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
or
// Specific table name for Word Entity.
modelBuilder.Entity<Word>().ToTable("TableContainingWords");
If you are changing your model, Code First Migrations might be what you need.
I havent found a way to truly dynamically extend an EF model at runtime. Given what goes on in DB context inherited class, the use of generated views for performance and a model class approach, avoiding recompilation seems hard. I have generated code, compiled and access this using assembly discovery approaches. But this is all unsatisfactory from my viewpoint , so i have stopped investigating this path. Very clunky outcome.
Ironically the topic you provide as a use case for such a problem, is one that doesnt need dynamic EF in my view.
I have exactly the same use case, language specific look for messages/labels etc Ie a language specific textpool.
Why not add language to the class/table.
Use a table or Enum for supported languages.
Use Language in the Textpool table/s
Use a different model class for presentation. (view model).
So you can present it the way like .
public class Word
{
Guid ID {get;set;} // logical key is WordID + Language
public int WordID { get; set; } // implement with new id or 2 field key
public Language Language {get;set;} // see cultureInfo for more details
public bool IsMaster {get;set;}
public string Text { get; set; } // consider renaming due to reserved word implications
}
public class language
{
int ID,
String Lang
}
}
I am very new to OData (only started on it yesterday) so please excuse me if this question is too dumb :-)
I have built a test project as a Proof of Concept for migrating our current web services to OData. For this test project, I am using Reflection Providers to expose POCO classes via OData. These POCO classes come from in-memory cache. Below is the code so far:
public class DataSource
{
public IQueryable<Category> CategoryList
{
get
{
List<Category> categoryList = GetCategoryListFromCache();
return categoryList.AsQueryable();
}
}
// below method is only required to allow navigation
// from Category to Product via OData urls
// eg: OData.svc/CategoryList(1)/ProductList(2) and so on
public IQueryable<Category> ProductList
{
get
{
return null;
}
}
}
[DataServiceKeyAttribute("CategoryId")]
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public List<Product> ProductList { get; set; }
}
[DataServiceKeyAttribute("ProductId")]
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
}
To the best of my knowledge, OData is going to use LINQ behind the scenes to query these in-memory objects, ie: List in this case if somebody navigates to OData.svc/CategoryList(1)/ProductList(2) and so on.
Here is the problem though: In the real world scenario, I am looking at over 18 million records inside the cache representing over 24 different entities.
The current production web services make very good use of .NET Dictionary and Hashtable collections to ensure very fast look ups and to avoid a lot of looping. So to get to a Product having ProductID 2 under Category having CategoryID 1, the current web services just do 2 look ups, ie: first one to locate the Category and the second one to locate the Product inside the Category. Something like a btree.
I wanted to know how could I follow a similar architecture with OData where I could tell OData and LINQ to use Dictionary or Hashtables for locating records rather than looping over a Generic List?
Is it possible using Reflection Providers or I am left with no other choice but to write my custom provider for OData?
Thanks in advance.
You will need to process expression trees, so you will need at least partial IQueryable implementation over the underlying LINQ to Objects. For this you don't need a full blown custom provider though, just return you IQueryable from the propties on the context class.
In that IQueryable you would have to recognize filters on the "key" properties (.Where(p => p.ProductID = 2)) and translate that into a dictionary/hashtable lookup. Then you can use LINQ to objects to process the rest of the query.
But if the client issues a query with filter which doesn't touch the key property, it will end up doing a full scan. Although, your custom IQueryable could detect that and fail such query if you choose so.
Hi there I am hoping someone can point me in the right direction.
I want to create an mvc applicaton I have worked my way through the music store example and still am not 100% sure the correct way to do things.
Lets say I want to create an application that stores cooking receipes.
I have a 3 tables
RecipeTable
RecipeID
RecipeName
RecipeIngredients
RecipeIngredientID
RecipeID
IngredientID
Measurement
IngredientTable
IngredientID
IngredientName
All have PK & FK mappings very basic, I create a new mvc application and use the entity framework to create a new entity e.g. RecipeDB
My next step is I create a new model for each of the tables and give the properties my desired displaynames and specify required fields extra.
Do I then create a viewmodel e.g. RecipesViewModel that looks something like
public class RecipesViewModel
{
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public List<RecipeIngredients> { get; set; }
}
I now create the controller (Ithink) but I am not really sure how to bind that to database entity.
I know you can call the database by doing something like RecipeEntities db = new recipeEntites(); however binding the results to the vm I am little confussed on how to do that.
Am I heading in the right direction so far?
You could use AutoMapper. It's a great tool allowing you to convert from one type to another and in your case from the model to the view model.
public ActionResult Foo()
{
RecipeDB model = _repository.GetRecipies();
RecipesViewModel viewModel = Mapper.Map<RecipeDB, RecipesViewModel>(model);
return View(viewModel);
}
or you could even define a custom action attribute (like the one I used in my sample MVC project) allowing you to simply write:
[AutoMap(typeof(RecipeDB), typeof(RecipesViewModel))]
public ActionResult Foo()
{
RecipeDB model = _repository.GetRecipies();
return View(model);
}
I currently have a LINQ query that is correctly retrieving all relevant poll questions and their associated responses. In this query, I'm using the .Include() method to retrieve the responses. I like this approach because it makes the code in my View simple -- basically I have a #foreach for the responses nested inside a #foreach for the questions.
Now, I'd like to add response-specific information such as # of votes today, # of votes this week and # of votes overall. Again, these would be retrieved and displayed for each response of each question.
Is there an efficient LINQ solution that would allow me to continue using my .Include() method and my nested #foreach loops or do I need to scrap the .Include() method and use joins to pull everything together?
If it matters for performance reasons, this is being written in .net MVC-3.
Thanks in advance for your opinions/suggestions.
I like this approach because it makes the code in my View simple -- basically I have a #foreach for the responses nested inside a #foreach for the questions.
Personally I wouldn't be satisfied with this. Why writing loops in your view when you can use Display Temapltes? As far as your question about including the # of votes today, # of votes this week and # of votes overall is concerned the answer, as always, is to use a view model which is specifically tailored to the needs of the view:
public class QuestionViewModel
{
public int VotesToday { get; set; }
public int VotesThisWeek { get; set; }
public int TotalVotes { get; set; }
public IEnumerable<ResponseViewModel> { get; set; }
}
then you would pass an IEnumerable<QuestionViewModel> to your view and it will look like this:
#model IEnumerable<AppName.Models.QuestionViewModel>
#Html.DisplayForModel()
and in ~/Views/Shared/DisplayTemplates/QuestionViewModel.cshtml
#model AppName.Models.QuestionViewModel
<div>#Model.VotesToday</div>
<div>#Model.VotesThisWeek</div>
<div>#Model.TotalVotes</div>
#Html.DisplayFor(x => x.ResponseViewModel)
and in ~/Views/Shared/DisplayTemplates/ResponseViewModel.cshtml:
#model AppName.Models.ResponseViewModel
<div>#Model.Body</div>
Now, that's a clean view.
Let's move to the controller now:
public class QuestionsController: Controller
{
private readonly IQuestionsRepository _repository;
public QuestionsController(IQuestionsRepository _repository)
{
_repository = repository;
}
public ActionResult Index()
{
IEnumerable<Question> model = _repository.GetQuestions();
IEnumerable<QuestionViewModel> viewModel = Mapper
.Map<IEnumerable<Question>, IEnumerable<QuestionViewModel>>(model);
return View(viewModel);
}
}
Here we have abstracted the data access away into a repository so that the controller should never know anything about EF or whatever data access technology you are using. A controller should only know about your model, your view model and abstraction of how to manipulate the model (in this case the repository interface).
As far as the conversion between the your model and the view model is concerned you could use AutoMapper (the Mapper.Map<TSource, TDest> part in my example).
As far as the repository is concerned, that's an implementation detail: whether you perform one or three queries to your database it's up to you. All that's needed is that you are capable of aggregating the required information.