Associating entities, not creating, with many-to-many relationships in EF Code First MVC3 - asp.net-mvc-3

In MVC3 Code First EF how do you associate one entity with another without creating a new one (many-to-many)?
So I have a many-to-many relationship between class1 and class2. I need class1 to hold many class2 and vice versa. However, class2 is independent; I have a list of them that I want to edit separately and then associate with a new class1.
When I pass my class2List to the controller( via AJAX and JSON), I checked and all the Ids of the class2s correspond to existing ids in the db, i.e. new class2s are not created.
Model
class
{
[key]
public int Id {set; get;}
}
class1 : class
{
private ICollection<class2> _class2s;
public virtual ICollection<class2> class2s
{
get { return _class2s ?? ( _class2s = new HashSet<class2>()); }
set { _class2s = value; }
}
}
class2 : class
{
private ICollection<class1> _class1s;
public virtual ICollection<class1> class1s
{
get { return _class1s ?? ( _class1s = new HashSet<class1>()); }
set { _class1s = value; }
}
}
Controller
public ActionResult SaveChanges(List<class2> class2List)
{
createNewClass2AndAssociateExistingClass2s(class2List);
SaveChangesToDb();
return View("ProductDetail", Model);
}
createNewClass2AndAssociateExistingClass2s(List<class2> class2List)
{
var newClass1 = newClass1()
{
class2s = class2List;
}
////UnitOfWork allows me to access several DbSets in one transaction
unitOfWork.Create(newClass1)
}
SaveChangesToDb()
{
unitOfWork.Commit();
}
What this does is create a new class1 (as it should) but instead of associating the existing class2s with it, it makes new class2s with new Ids and adds them to the database.
My question:
Does this have to do with how EF is reading my Id property from base class?
How would I be able to associate several existing class2s as a list with a new class1, without creating new class2s in the database?
Cheers

Ok so two things I learned from figuring this out:
I was inheriting from an abstract class when I should have been implementing an interface. This is a great idea if you have several entities that have a similar property such as "Id" and you want to do something like
T FindById<T>(int id) where T : IEntity
When making associations in EF, even if the Id matches an existing entry, it will not update that entry, unless EF is tracking that entry in the context, as it says here. What I needed to do was:
Add a method in the mapping layer that gets the entry by id that I
want from the repository
Copy the attributes of the new entry into that context entry
Return the context entry
Hope this helps someone

Related

DbContext EntitySet null when entities are set to internal access

I want the db set to be internal in order to ensure external packages only have access to and program against the interface not the concrete class
e.g.
namespace Domain
{
public interface IProduct
{
string Description { get; }
int Id { get; }
decimal Price { get; }
}
}
//Separate Person.cs file for custom logic
namespace Domain
{
internal partial class Product :IProduct
{
}
}
internal partial class POS : DbContext
{
public POS()
: base("name=POS")
{
}
internal DbSet<Product> Products { get; set; }
}
//The other Person.cs file is generated by the .tt file
//_context.People is null which caused the dreaded null pointer exception :(
var people = _context.People.ToList();
As soon as I set the access to the Person class and People entity set to public via the Model Browser it works again, but I want to restrict the access to internal for package encapsulation.
It worked with Context in VS2010 EF but not with DbContext in VS2012.
Any help is much appreciated :}
P.S.
For now I have just edited the .tt file as below
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
Products = Set<Product>();
This generates the context class as below which instantiates the set, it would be nice to not have to add this to the .tt file for every entity set in the model.
internal partial class POS : DbContext
{
public POS()
: base("name=POS")
{
Products = Set<Product>();
}
I know this question is old but I just ran into this issue as well. According to a number of other StackOverflow posts, this is still the behavior of EntityFramework and the solution is still to explicitly Set<> the entity sets.
That said, instead of having to manually add each entity name to the .tt file, I created some code that will cause the TT file to automatically generate this code for each entity.
In the *.Context.tt file, you should spot the code for the constructor that looks something like this:
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
#>
}
Modify this so it now looks like:
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
#>
<#
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
#>
<#=codeStringGenerator.SetStatement(entitySet)#>
<#
}
#>
}
Further down in the file you should see a class definition for the CodeStringGenerator class, add a new method (I added mine directly under the DbSet method definition around line 307):
public string SetStatement(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} = Set<{1}>();",
_code.Escape(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType));
}
When you save the template it should regenerate the DbContext class with the Set<> statements for each entity in your model. New entities that are added will re-trigger the template generation and those new entities will also be included in the constructor.

Get User custom fields without SOQL (like $User in formulas)?

I have some custom fields on my User object that I want to access with APEX code in my VisualForce trigger. When I access it from a Formula field I get to use a nifty $User reference like this:
$User.my_prop__c
From APEX I have to query the User object by UserId like this:
[select my_prop__c from User where id = :UserInfo.getUserId()].my_prop__c;
Is there something baked into APEX already that would let me get at the user properties without the SOQL query? If not, does anyone know of a utility class for lazy loading and caching user properties so the overhead is minimal.
I would use something similar to the following code sample. It uses a singleton pattern to statically store the information in memory for the duration of your transaction. It's similar to the lazy loading that twamley proposed but I feel this is a much simpler approach.
Usage 1: UserUtil.CurrentUser.Email;
Usage 2: User someUser = UserUtil.getUser(someUserId);
This will allow you to access the same information on the current user or other users in the system. Notice the queryUsers method just returns a query result. This makes it easy to add and remove fields from your query as it is isolated in its own method keeping things simple.
Note: that this code pulls in all users when used. Most orgs do not have multiple hundreds of users so heap size shouldn't be a concern. But if it is you can just modify the queryUsers() method to only return active users or filter down based on other criteria.
public class UserUtil {
//Protected Members
private static final UserUtil instance = new UserUtil();
private Map<Id, User> mapUsers;
//Properties
public static User CurrentUser {
get { return getUser(UserInfo.getUserId()); }
}
//Constructor
private UserUtil() {
mapUsers = new Map<Id, User>(queryUsers());
}
//Public Methods
public static User getUser(Id userId) {
if (instance.mapUsers.containsKey(userId)) {
return instance.mapUsers.get(userId);
}
else {
throw new InvalidUserIdException('Unable to locate user id: ' + userId);
}
}
//Private Methods
private List<User> queryUsers() {
return [SELECT
Id
, Name
, UserName
, Email
, Alias
FROM
User];
}
//Internal Classes
public class InvalidUserIdException extends Exception {}
}
I wrote my own utility class. I'm still interested in better techniques though.
This utility class lazy loads when the first property is accessed. Update_Closed_Won_Opportunities__c and Set_Opportunities_to_Closed_Won__c are my custom fields on the User object (visible only to System Administrators so people can't upgrade their permissions).
public with sharing class MyUserInfo {
private Id userId;
private User myUser; // Hold onto the user object once we've loaded it
// Default constructor uses the active user id
public MyUserInfo() {
userId = UserInfo.getUserId();
}
// Secondary constructor accepts a user id as a parameter
public MyUserInfo(Id someOtherUserId) {
userId = someOtherUserId;
}
// Only called one time when we first need it so grab all of the custom fields now
private void LazyLoadUser() {
System.AssertNotEquals(null, userId);
myUser = [
SELECT Update_Closed_Won_Opportunities__c, Set_Opportunities_To_Closed_Won__c
FROM User
WHERE id = :userId
];
System.AssertNotEquals(null, myUser, 'Unable to load user with id ' + userId); // could return defaults instead
}
// Getters (be sure to include each field in the SOQL of LazyLoadUser)
public boolean UpdateClosedWonOpportunities { get {
if (myUser == null) LazyLoadUser();
return myUser.Update_Closed_Won_Opportunities__c;
} }
public boolean SetOpportunitiesToClosedWon { get {
if (myUser == null) LazyLoadUser();
return myUser.Set_Opportunities_To_Closed_Won__c;
} }
}
Here is my trigger utilizing that class. The first line myUserInfo = new MyUserInfo(); doesn't run any SOQL. That won't happen until the first custom get property is used. Subsequent calls don't need SOQL.
trigger LockClosedOpportunity on Opportunity (before update) {
MyUserInfo myUserInfo = new MyUserInfo();
for (Opportunity o : trigger.old)
{
if (!myUserInfo.UpdateClosedWonOpportunities && o.StageName == 'Closed Won')
trigger.newMap.get(o.Id).addError('You do not have permission to change an Opportunity after it has been set to Closed Won.');
}
for (Opportunity o : trigger.new)
{
if ( !myUserInfo.SetOpportunitiesToClosedWon && o.StageName == 'Closed Won' && trigger.oldMap.get(o.Id).StageName != 'Closed Won' )
o.addError('You do not have permission to set an Opportunity to Closed Won.');
}
}
It reads similar to $User in formulas and I don't have to worry about tacking on multiple SOQL calls when one (or zero) suffices.

ASP.NET MVC 3 multiple Models to single Form using DB

I have a question.
My question actually extends from this one:
Shortly - what I want to get: 3 models, and 1 super model for this specific view. This super model fills(properly) IENumerable, IENumerable, IENumerable, to use them in View part. (as far as I understand it, at least...)
In this other topic Dan Revell proposed verry nice and elegant solution, but this solution does not fetch data from DB itself...
Question:
What must be done to get data in this model from DB, not from "new" instance constructors?
While using this approach tried to fetch data from DBContext. And got some problems in it ) I can't understand when (or how) to create my DBContext... Or how to access one that is created by application...
Tried to create it forcefully in Controller, like
using (var Db = new thetaskermvc.Models.TaskerDBContext())
{
var themodel = new thetaskermvc.Models.TotalView();
//Jobbers
themodel.Jobberz = new Dictionary<int, thetaskermvc.Models.Jobbers>();
var jobbers = from Jobbers in Db.Jobbers.OrderBy(g => g.jobb_name) select Jobbers;
foreach (Models.Jobbers ad in jobbers)
{
themodel.Jobberz.Add(ad.jobb_id,
new Models.Jobbers(ad.jobb_id, ad.jobb_name, ad.jobb_from, ad.jobb_carma, ad.jobb_status, ad.jobb_balance, ad.jobb_time));
}
if (themodel.Jobberz.Count == 0)
{
themodel.Jobberz.Add(-1, new Models.Jobbers(0, "NOTHING FOUND",DateTime.Now,0,"",0,0));
}
}
But as created that way Context stops it's existence (?) after passing data away from controller - I can't use it any other way but to get all data inside this controller, and fill data in model by direct add into collections in it (while use of IENumerable would fetch data on-demand, as far as I get it).
So.. If it ain't hard please enlighten me about - is it Ok to use such approach, or there is some other "common" way? Becaus beside it's clumsiness - this approach works...
PS I'm quite new to Asp, yet...
I have one view model per view with data from multiple tables (if required). On my view I have data that needs to be loaded from 2 different database tables. In my grant application controller I have the following:
private readonly IBankService bankService;
private readonly IAccountTypeService accountTypeService;
public GrantApplicationController(IBankService bankService, IAccountTypeService accountTypeService)
{
// Check incoming parameters for null values
this.bankService = bankService;
this.accountTypeService = accountTypeService;
}
In my Create action method I populate my banks and account types (to be used in drop downs) like this (different tables):
public ActionResult Create()
{
GrantApplicationCreateViewModel viewModel = new GrantApplicationCreateViewModel
{
Banks = bankService.FindAll(),
AccountTypes = accountTypeService.FindAll()
}
// Do what ever else you need to get done
return View(viewModel);
}
My partial view model would like this:
public class GrantApplicationCreateViewModel
{
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
public int AccountTypeId { get; set; }
public IEnumerable<AccountType> AccountTypes { get; set; }
// Other properties
}
In my repository class I would use the database context like this (I use Entity Framework code first):
public class BankRepository : IBankRepository
{
HefContext db = new HefContext
public IEnumerable<Bank> FindAll()
{
return db.Banks.OrderBy(x => x.Name);
}
}
In my database context class:
public class HefContext : DbContext
{
public DbSet<Bank> Banks { get; set; }
public DbSet<AccountType> AccountTypes { get; set; }
}
Doing it this way you can have one view model that has data from multiple sources. I hope this answers your question? If you need more explanation please let me know :)
You may want to have a look at this post, it explains (with a sample project) how an ideal MVC application architecture should be.
In your code sample above, your shouldn't have any references to DbContexts in a controller. Controller's job is to control the flow of requests not to connect to the DB and perform Model population.

How does one do MVC2/EF4 EntityCollection validation w/ data annotations?

I have finally gotten over one hurdle and can now successfully create new model data. Now there's another catch - validation. Most of the validation seems easy enough as a lot of my model data are scalar values. There is a many-to-many relationship I link to, however, so I'm not sure how to go about validating that. My model is (once again):
Game (only listing the relevant columns):
GameID - int (primary key, auto-incr)
Platform:
PlatformID - int (primary key, auto-incr)
Name - string
GamePlatform (not a visible entity):
GameID - int (foreign key from Games)
PlatformID - int (foreign key from Platforms)
And my Create method (yes, I know it's sloppy and amateurish - I am an amateur and trying to learn. I'll definitely add error checking to it. I'm just trying to get the big picture of the view->controller->validation->persist-in-db/show errors process down):
public ActionResult CreateReview([Bind(prefix = "GameData")]Game newGame, int[] PlatformIDs)
{
try
{
foreach(int i in PlatformIDs)
{
Platform plat = _siteDB.Platforms.Single(p => p.PlatformID == i);
newGame.Platforms.Add(plat);
}
newGame.LastModified = Datetime.Now;
_siteDB.Games.AddObject(newGame);
_siteDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The array of PlatformIDs are supplied by a group of checkboxes within my view. For my Game to be valid, it must be associated with at least one Platform. I'm just not sure how to validate that with data annotations, or if it's even possible to do so. Any help would be greatly appreciated.
If I understand your question correctly, your int[] potentially contains ints associated with the ID of a Platform in your DB and you want to make sure your int[] contains at least one valid PlatformID, correct?
Immediately you could do just a simple check prior to going into your logic:
// If there aren't any IDs in Platform that are in PlatformIDs...
if (!_siteDB.Platforms.Any(p => PlatformIDs.Contains(p.PlatformID)))
Return RedirectToAction("Index");
// And probably tell the user to check a box, if they did,
// One of your checkboxes isn't matching up with your PlatformIDs
Ideally what you'd want to do is add the int[] to your model so you can check model validation. Since databased don't typically store int[], add it to your Game model. The EF probably put your DB Entities in your Models folder and if you look at them, you'll see they're partial classes. So add this in your Models folder:
public partial class Game
{
public Dictionary<int, bool> SupportedPlatforms { get; set; }// Edited
}
// Also add this which you'll see why below
public partial class Platform
{
public static bool IsValidPlatformID(int PlatformID)
{
using (SiteDBEntities _siteDB = new SiteDBEntities())
return _siteDB.Platforms.Any(p => p.PlatformID.Equals(PlatformID));
}
}
Then add a custom ValidationAttribute class:
public ContainsValidPlatformIDAttribute : ValidationAttribute
{
public ContainsValidPlatformIDAttribute() { }
public override bool IsValid(object value)
{
Dictionary<int, bool> supportedPlatforms = (Dictionary<int, bool>)value;
if (value == null)
return true;
foreach (int i in values)
{
if (supportedPlatforms.Values.Any(b => b.Equals(true)))// Edited
return false;
}
return true;
}
Now decorate your Property with it in the Game class:
[ContainsValidPlatformID(Error = "You did not select a valid Platform.")]
public Dictionary<int, bool> SupportedPlatforms { get; set; }// Edited
(Edited)Now instead of hard coding a checkbox for each platform, add this instead:
<%: Html.CheckboxFor(model => model.SupportedPlatforms[0]) %>
<%: Html.ValidationMessageFor(model => model.SupportedPlatforms[0]) %>
(Edited)Now your checkboxes are tied to the Model, you can validate the model in the controller, and you can remove the int[] argument from your Action method. This has all been coded from my head into this editor so you may need to tweak some things here and there but this is the direction you should be heading in when working with Models in Views.
Also, check out what Scott Guthrie has written on the topic of MVC Model Validation in his blog. Hopefully with my sample and Scott's blog, you'll be pointed in the right direction.

How to transfer data when using LINQ as DAL?

I'm creating my first linq based project. The DAL consists of LinqToSQL classes. And the logic layer is just another DLL for keeping it simple.
I wanted to know how do I pass the var object (result of select query) from Login Layer to Presentation Layer?
Should I write my own DTO layer between Login layer and Presentation Layer to transfer the from BLL to Presentation layer or should I serialize the data as XML?
Thanks, Vikas
I would avoid serializing whenever you have the opportunity to just pass the data as a strongly typed class. And that is what you're going to have to do. I believe when .Net 4.0 comes out you will be able to pass vars, but until then, try returning your query as an IEnumerable instead of a var when you need to pass it to another function.
IE:
public class myClass
{
public int RecordID { get; set; }
public string Field1 { get; set; }
}
public void GetDataAndSendToOtherLayer()
{
using (DBDataContext db = new DBDataContext)
{
IEnumerable<myClass> Recs =
from tab in db.table
select new myClass
{
RecordID = tab.RecordID,
Field1 = tab.Field1
};
OtherLayer.DoSomething(Recs);
}
}
Load each row result into an object, place the each object into a Collection and then pass the Collection from the DAL to your BOL where it can then be handled by your business rules before the Collection is passed on to your presentation.

Resources