I have a situation where I'd like to eager load the organization property of a user being retrieved from the DB using LINQ-to-SQL (because of my using here attempts to get the property later fail because the context's connection is closed). The problem is that sometimes the user does not have an organization. In that case FetchUserByName returns null. Not what I want at all. Is there a way to get this to work the way I want, which is to return an instance of user whether they have an associated organization or not?
private static void EagerLoad(DataContext ctx)
{
var loadOpt = new DataLoadOptions();
loadOpt.LoadWith<User>(u => u.Organization);
ctx.LoadOptions = loadOpt;
}
public User FetchUserByName(string username)
{
User user = null;
using (var ctx = contextManager.GetSingleDataContext())
{
EagerLoad(ctx);
user = ctx.GetTable<User>().SingleOrDefault(u => u.UserName == username && u.IsActive);
}
return user;
}
Documenting what the fix was for others who may run into this.
LoadWith actually determines whether to use an INNER JOIN or an OUTER LEFT JOIN in the SQL created by LINQ-to-SQL by looking whether the foreign key in the data model class is nullable in the database. If it is nullable it uses the outer join.
[Column]
public int OrganizationId { get; set; } /*** Here is The Problem ***/
private EntityRef<Organization> _organization;
[Association(OtherKey = "OrganizationId", Storage = "_organization", ThisKey = "OrganizationId", IsForeignKey = true)]
public Organization Organization
{
get
{
return this._organization.Entity;
}
}
Changed the one line to:
public? int OrganizationId { get; set; }
And then everything worked as expected.
Related
I have an application where I can choose a database with the help of a combobox and then apply several query operations on this database within application. For this purpose, I have generated 6 different .edmx files which is generated from my databases with the database first approach. Besides that, I want to create a Data Access Layer class where I am managing my data access operations, but I want this class to be generic so that I am not going to need to create 6 different data access classes for each entity. For this purpose, I created such a class;
public class UserDetails<TEntity> where TEntity:class
{
private DbContext _context { get; set; }
private DbSet<TEntity> efDB { get; set; }
public UserDetails(DbContext context)
{
_context = context;
efDB = _context.Set<TEntity>();
}
public List<UserDetailsModel> GetUserDetails(int? roleID, int? titleID, int? managerID, string id = "", string name = "", string surname = "", string mail = "")
{
using (efDB) {
List<UserDetailsModel> query = (from user in efDB.TABLE_USER
join pass in efDB.TABLE_PASSWORD_HISTORY on GetLatestPassword(user.ID) equals pass.ID into passJoined
from passLatest in passJoined.DefaultIfEmpty()
join role in efDB.TABLE_REL_USER_ROLE on user.ID equals role.UserID into roleJoined
from roleRel in roleJoined.DefaultIfEmpty()
join defRole in efDB.TABLE_DEF_ROLE on roleRel.RoleID equals defRole.ID into defRoleJoined
from defRoleRel in defRoleJoined.DefaultIfEmpty()
join title in efDB.TABLE_USER_TITLES on user.TitleID equals title.ID
where user.Name.Contains(name) && user.Surname.Contains(surname) && user.Email.Contains(mail)
select new UserDetailsModel() {
ID = user.ID,
Name = user.Name,
Surname = user.Surname,
Email = user.Email,
TitleID = (int)user.TitleID,
TitleName = title.Description,
Password = passLatest.Password,
RoleID = roleRel.RoleID,
RoleName = defRoleRel.RoleName,
}
).ToList();
return query;
}
As you see, I want to perform a linq query in my GetUserDetails method. Note that, all of the 6 databases have same tables with the same names so that this query is going to work for all of my entities. What I want to do here is that, use the variable efDBgenerically so that it can be any of the 6 database entites. The desired database entity instance will be created by a different class which works like a factory and will be given to my UserDetails data access class. But obvisiouly, my code won't work. How can I implement this generic logic into my system? Thanks in advance.
I think you want something like UnitOfWork. Here are a few links describing and explaining how to set one up.
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
https://www.codeproject.com/Articles/770156/Understanding-Repository-and-Unit-of-Work-Pattern
https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/
These are my model and I am using Entity Framework with a code-first approach.
public class Respondent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RespondentId { get; set; }
public User Requester { get; set; }
public User Provider { get; set; }
public string Role { get; set; }
}
public class User: IUser
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public int UPI { get; set; }
public string Name { get; set; }
}
and this is relationship of both generated tables.
What I want is If user exists in User table then don't create another entry of user.
Issue is when I add a new provider (of type user), every time it creates a new entry in user even if the user exists; what I want is to add a reference of that user in respondent table.
private Context db = new Context();
public async Task<IHttpActionResult> PostRespondent(Respondent respondent)
{
var TempProvidersList = respondent.Providers.ToList();
try
{
var requester = db.Users.Single(s => s.UserId == respondent.Requester.UserId);
requester.IsPublic = false;
var providerIds = respondent.Providers.Select(x => x.UPI).ToList();
foreach (var providerId in providerIds)
{
if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId && x.Provider.UPI == providerId))
{
var provider = respondent.Providers.Single(x => x.UPI == providerId);
provider.IsPublic = true;
db.Respondents.Add(new Respondent() { Requester = requester, Provider = provider, Role = provider.Role });
}
}
db.SaveChanges();
}
catch (Exception ex)
{
}
return CreatedAtRoute("DefaultApi", new { id = respondent.RespondentId }, respondent);
}
I believe the issue is due to passing entities that were loaded by a different DbContext and then adding references to those entities in a new DbContext.
This code here doesn't make much sense:
var res = db.Respondents
.Include(user => user.Requester)
.Include(user => user.Provider)
.Where(o => o.Requester.UserId == requester.UserId ).ToList();
Where you don't use "res" anywhere in the code. You do use "a" which I assume is meant to be the same thing?
Firstly, if you just want to check for the existence of a row, use Any()
if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId))
Rather than loading all available entities just to check if some exist or not.
The problem area I see is this:
tempProvider = TempProvidersList[i];
db.Respondents.Add(new Respondent() { Requester = requester, Provider = tempProvider, Role = TempProvidersList[i].Role });
This is setting your provider reference in a new Respondent to a User (provider) that was probably loaded from a different DbContext instance.
To address this, any references you set should be loaded from the current DbContext:
var providerIds = respondent.Providers.Select(x => x.UPI).ToList();
var providers = db.Users.Where(x => providerIds.Contains(x.UPI)).ToList();
var requester = db.Users.Single(s => s.UserId == respondent.Requester.UserId);
requester.IsPublic = false;
foreach(var providerId in providerIds)
{
if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId))
{
var provider = providers.Single(x => x.UPI == providerId);
provider.IsPublic = true;
db.Respondents.Add(new Respondent() { Requester = requester, Provider = provider, Role = provider.Role });
}
}
db.SaveChanges();
I opted to load all applicable providers in one hit based on the IDs rather than one at a time inside the loop. Within the loop I just attempt to retrieve the one matching the ID from the loaded set.
I would wrap this with a try/catch block and handle the exceptions. The possible exceptions that come to mind is when a User isn't found for a ProviderId. (Can users be deleted?) I did a foreach on the IDs rather than the loaded references because that collection may not have an entity for each ID based on the above condition. (if only 5 out of 6 IDs load a provider user, the ID collection will have 6 elements but the entity collection would just have 5)
Another point here is use Single rather than FirstOrDefault where you expect one record. This enforces that rule and will throw an exception if there are more than one, or no matching record. The "OrDefault" variants should only be used if not finding a row is an expected outcome. FirstOrDefault will hide issues if more than one match makes it into the DB, and without an "OrderBy" clause you cannot rely on which reference would be returned.
I have a very simple EF operation that fails: break the relationship between two entities as shown in the code below:
public async Task RemoveCurrentTeacherOfGroup(int groupId)
{
var group = await _dataContext.Groups.SingleAsync(g => g.Id == groupId);
group.Teacher = null;
await _dataContext.SaveChangesAsync();
}
The database was generated code-first. The entities are defined like this:
public class Teacher
{
public int Id { get; set; }
..
public virtual List<Group> Groups { get; set; }
}
public class Group
{
public int Id { get; set; }
..
public virtual Teacher Teacher { get; set; }
}
However, breaking the relationship doesn't work, Teacher keeps pointing to the same entity. When stepping with the debugger, I see that the Teacher property doesn't become null after .Teacher = null. I tried it with the synchronous alternative, which had the same effect:
public void RemoveCurrentTeacherOfGroup(int groupId)
{
var group = _dataContext.Groups.Single(g => g.Id == groupId);
group.Teacher = null;
_dataContext.SaveChanges();
}
If Teacher is not loaded you can't break the relationship. Either include it (eager-load) on the query:
_dataContext.Groups.Include(g => g.Teacher).Single(g => g.Id == groupId);
Or if lazy loading is enabled, access the property for reading before setting it to null:
var teacher = group.Teacher;
group.Teacher = null;
You see that "Teacher is not null after setting it to null" because the debugger is accessing the property for reading (lazy-loading it) after you have set it to null.
The value was already null before you hit the group.Teacher = null line since you hadn't previously loaded it (you can't however debug this, since accessing the property for reading would cause EF to actually load it if lazy loading is enabled). If you see the property value with the debugger before setting it to null, it'll work as expected and break the relationship, since Teacher would be loaded
This is my first time using Entity Framework 6.1 (code first). I keep running into a problem where my navigation properties are null when I don't expect them to be. I've enabled lazy loading.
My entity looks like this:
public class Ask
{
public Ask()
{
this.quantity = -1;
this.price = -1;
}
public int id { get; set; }
public int quantity { get; set; }
public float price { get; set; }
public int sellerId { get; set; }
public virtual User seller { get; set; }
public int itemId { get; set; }
public virtual Item item { get; set; }
}
It has the following mapper:
class AskMapper : EntityTypeConfiguration<Ask>
{
public AskMapper()
{
this.ToTable("Asks");
this.HasKey(a => a.id);
this.Property(a => a.id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(a => a.id).IsRequired();
this.Property(a => a.quantity).IsRequired();
this.Property(a => a.price).IsRequired();
this.Property(a => a.sellerId).IsRequired();
this.HasRequired(a => a.seller).WithMany(u => u.asks).HasForeignKey(a => a.sellerId).WillCascadeOnDelete(true);
this.Property(a => a.itemId).IsRequired();
this.HasRequired(a => a.item).WithMany(i => i.asks).HasForeignKey(a => a.itemId).WillCascadeOnDelete(true);
}
}
Specifically, the problem is that I have an Ask object with a correctly set itemId (which does correspond to an Item in the database), but the navigation property item is null, and as a result I end up getting a NullReferenceException. The exception is thrown in the code below, when I try to access a.item.name:
List<Ask> asks = repo.GetAsksBySeller(userId).ToList();
List<ReducedAsk> reducedAsks = new List<ReducedAsk>();
foreach (Ask a in asks)
{
ReducedAsk r = new ReducedAsk() { id = a.id, sellerName = a.seller.username, itemId = a.itemId, itemName = a.item.name, price = a.price, quantity = a.quantity };
reducedAsks.Add(r);
}
Confusingly, the seller navigation property is working fine there, and I can't find anything I've done differently in the 'User' entity, nor in its mapper.
I have a test which recreates this, but it passes without any problems:
public void canGetAsk()
{
int quantity = 2;
int price = 10;
//add a seller
User seller = new User() { username = "ted" };
Assert.IsNotNull(seller);
int sellerId = repo.InsertUser(seller);
Assert.AreNotEqual(-1, sellerId);
//add an item
Item item = new Item() { name = "fanta" };
Assert.IsNotNull(item);
int itemId = repo.InsertItem(item);
Assert.AreNotEqual(-1, itemId);
bool success = repo.AddInventory(sellerId, itemId, quantity);
Assert.AreNotEqual(-1, success);
//add an ask
int askId = repo.InsertAsk(new Ask() { sellerId = sellerId, itemId = itemId, quantity = quantity, price = price });
Assert.AreNotEqual(-1, askId);
//retrieve the ask
Ask ask = repo.GetAsk(askId);
Assert.IsNotNull(ask);
//check the ask info
Assert.AreEqual(quantity, ask.quantity);
Assert.AreEqual(price, ask.price);
Assert.AreEqual(sellerId, ask.sellerId);
Assert.AreEqual(sellerId, ask.seller.id);
Assert.AreEqual(itemId, ask.itemId);
Assert.AreEqual(itemId, ask.item.id);
Assert.AreEqual("fanta", ask.item.name);
}
Any help would be extremely appreciated; this has been driving me crazy for days.
EDIT:
The database is SQL Server 2014.
At the moment, I have one shared context, instantiated the level above this (my repository layer for the db). Should I be instantiating a new context for each method? Or instantiating one at the lowest possible level (i.e. for every db access)? For example:
public IQueryable<Ask> GetAsksBySeller(int sellerId)
{
using (MarketContext _ctx = new MarketContext())
{
return _ctx.Asks.Where(s => s.seller.id == sellerId).AsQueryable();
}
}
Some of my methods invoke others in the repo layer. Would it better for each method to take a context, which it can then pass to any methods it calls?
public IQueryable<Transaction> GetTransactionsByUser(MarketContext _ctx, int userId)
{
IQueryable<Transaction> buyTransactions = GetTransactionsByBuyer(_ctx, userId);
IQueryable<Transaction> sellTransactions = GetTransactionsBySeller(_ctx, userId);
return buyTransactions.Concat(sellTransactions);
}
Then I could just instantiate a new context whenever I call anything from the repo layer: repo.GetTransactionsByUser(new MarketContext(), userId);
Again, thanks for the help. I'm new to this, and don't know which approach would be best.
Try to add
Include call in your repository call:
public IQueryable<Ask> GetAsksBySeller(int sellerId)
{
using (MarketContext _ctx = new MarketContext())
{
return _ctx.Asks
.Include("seller")
.Include("item")
.Where(s => s.seller.id == sellerId).AsQueryable();
}
}
Also, there is an extension method Include which accepts lambda expression as parameter and provides you type checks on compile time
http://msdn.microsoft.com/en-us/data/jj574232.aspx
As for the context lifespan, your repositories should share one context per request if this is a web application. Else it's a bit more arbitrary, but it should be something like a context per use case or service call.
So the pattern would be: create a context, pass it to the repositories involved in the call, do the task, and dispose the context. The context can be seen as your unit of work, so no matter how many repositories are involved, in the end one SaveChanges() should normally be enough to commit all changes.
I can't tell if this will solve the lazy loading issue, because from what I see I can't explain why it doesn't occur.
But although if I were in your shoes I'd like to get to the bottom of it, lazy loading is something that should not be relied on too much. Take a look at your (abridged) code:
foreach (Ask a in asks)
{
ReducedAsk r = new ReducedAsk()
{
sellerName = a.seller.username,
itemName = a.item.name
};
If lazy loading would work as expected, this would execute two queries against the database for each iteration of the loop. Of course, that's highly inefficient. That's why using Include (as in Anton's answer) is better anyhow, not only to circumvent your issue.
A further optimization is to do the projection (i.e. the new {) in the query itself:
var reducedAsks = repo.GetAsksBySeller(userId)
.Select(a => new ReducedAsk() { ... })
.ToList();
(Assuming – and requiring – that repo.GetAsksBySeller returns IQueryable).
Now only the data necessary to create ReducedAsk will be fetched from the database and it prevents materialization of entities that you're not using anyway and relatively expensive processes as change tracking and relationship fixup.
This question is like a Part 2 of my previous one here!
Here are things I learned and hoping to be true:
Do not use Data Entity Context as Static
Dispose the data context after used -usually after one time-
Pass parameters into UPDATING Entity method instead of the Entity
(which I'll explain and in here my crossroad lies)
Here is my modified code:
public class ManagerBase
{
private NoxonEntities _entities = null;
public NoxonEntities Entities
{
get
{
if (_entities == null)
_entities = new NoxonEntities();
return _entities;
}
}
}
//Managers uses ManagerBase class as a Base class
MemberManager currentMemberManager = new MemberManager();
currentMemberManager.Save(memberId, username, password, languageId);
//MemberManager class
public Member Save(long memId, string username, string password, int langId)
{
using (var Context = base.Entities)
{
var Data = Context.Member.First(c => c.Id == memId);
Data.Username = username;
Data.Password = password;
Data.Language = Context.Language.First(c => c.Id == langId); //Gets the foreign entity
Context.SaveChanges();
return Data;
}
}
This works without exception. But normally this SAVE method is an implemented method by an interface. So, I'd rather to pass an OBJECT value to the SAVE method than passing all the fields like above.
For example I'd like to use this:
//Member is an EntityFramework Object which was created during EF Model when I added by visual studio
//Filter is the method that returns an EntityFramework Object from EF (and eventually database) with given some arguments
//SomeArguments could be MemberId = 2
Member modifiedMember = currentMemberManager.Filter(someArguments);
modifiedMember.UserName = "newvalue";
currentMemberManager.Save(modifiedMember);
In the SAVE method perhaps I could use like this:
public Member Save(Member modifiedMember)
{
using (var Context = base.Entities)
{
var Data = Context.Member.First(c => c.Id == modifiedMember.Id);
Data.Username = modifiedMember.Username;
Data.Password = modifiedMember.Password;
Data.Language = Context.Language.First(c => c.Id == modifiedMember.LanguageId); //Gets the foreign entity
Context.SaveChanges();
return Data;
}
}
If I am gonna use this just like the one right above, I think I should NOT use the passing object EF Object and instead perhaps I should use some other class that I'll write just to map the parameters to the EF Member Entity.
First question: Is this a good approach?
public class MyMember
{
public long Id { set; get; }
public string Username { set; get; }
public string Password { set; get; }
public int LanguageId { set; get; }
}
MyMember.Username = "NewValue";
MyMember.LanguageId = 4; //4 as in new value
currentMemberManager.Save(MyMember); //As you can see MyMember is not an EF Entity in here
But this will take long time since there is already some written code.
Second question: Can't I use an EF Entity object OUTSIDE and modify it there to save it IN the MemberManager class?
Again, thank you very much for your help in advance.
Here is the answer:
How to update entity?
And I thank you for the ones who helped me.