I am trying to populate a POCO with the result of a query to my DB. The resultset has multiple rows so I'm just iterating over each result and populating the POCO with the data and I get this error :"There is already an open DataReader associated with this Connection which must be closed first."
Where am I messing up?
it goes like this:
[Controller.cs]
/// <summary>
/// Populates the inner Ads list
/// </summary>
public void FetchAds()
{
_ads = new List<Ad>();
using (var context = (ApartmentDataEntities) DbContextFactory.GetInstance().GetDbContext<ApartmentDataEntities>())
{
foreach (var config in context.Configurations)
{
_ads.Add(new AdModel(_basePath, config));
}
}
AdsReady.SafeTrigger(this, new AdArray { Ads = _ads.ToArray() });
}
[AdModel.cs] (inherits from the POCO)
public AdModel(String baseFolder, Configuration apartment)
{
_baseFolder = baseFolder;
GetAd(apartment);
}
/// <summary>
/// Populates the Ad from the Database
/// </summary>
private void GetAd(Configuration apartment)
{
PropertyId = apartment.Property.id;
PropertyName = apartment.Property.name;
PropertyPhone = apartment.Property.phone;
PropertyAddress = apartment.Property.address;
AreaName = apartment.Property.MapArea.areaName;
RegionName = apartment.Property.MapArea.Region.name;
PropertyZipCode = apartment.Property.zipCode;
ComissionRate = apartment.Property.comissionRate;
Images = apartment.Property.Images.Select(img => img.id).ToArray();
YearBuilt = apartment.Property.yearBuilt;
Features = apartment.Property.features;
Ammenities = apartment.Property.ammenities;
CommunitySpecial = apartment.Property.communitySpecial;
PetPolicy = apartment.Property.petPolicy;
Size = apartment.size;
Bathrooms = apartment.bathrooms;
Bedrooms = apartment.bedrooms;
Price = apartment.price;
PropertyImages = apartment.Property.Images.Select(img => img.imageContents).ToArray();
FloorplanImage = null;
Floorplan = null;
var configFloorplan = apartment.Images.SingleOrDefault();
if (configFloorplan == null) return;
FloorplanImage = configFloorplan.imageContents;
Floorplan = configFloorplan.id;
}
Usually it is a better idea to do the projection to another model in the query itself. Like
_ads = (from apartment in context.Configurations
let configFloorplan = apartment.Images.SingleOrDefault()
select new AdModel
{
PropertyId = apartment.Property.id,
PropertyName = apartment.Property.name,
PropertyPhone = apartment.Property.phone,
...
PropertyImages = apartment.Property.Images
.Select(img => img.imageContents),
FloorplanImage = configFloorplan.imageContents,
Floorplan = configFloorplan.id
}).ToList();
This ensures that everything is executed as one query. The problem with your approach is that while EF is reading context.Configurations other queries get executed to populate additional properties of the model.
This could be solved (maybe) by enabling multiple active result sets (MARS) in the connection string. But that does not fix the fact that you will be executing a potentially large number of queries (two queries for each materialized model).
Are you using EntityFramework? It's probably that the .Select's in your GetAdd are lazy loading and thus trying to query the DB while the outer query is still open. Try adding toArray() after context.Configurations. Seems I had to sprinkle these all over the place to avoid that error.
Related
I currently have a project that I'm working on, which has a database connected to it. In said database I need to query some tables that don't have a relationship. I need to get a specific set of data in order to display it on my user interface. However I need to be able to reference the returned data put it into a list and convert it into json. I have a stored procedure that needs to just be executed against the context because it's retrieving data from many different tables.
I've tried using ExecuteSqlCommand but that doesn't work, because it returns -1 and can't put it into a list.
I've tried using linq to select the columns I want however it's really messy and I cannot retrieve the data as easily.
I've tried using FromSql, however that needs a model to execute against the context which is exactly what I don't want.
public string GetUserSessions(Guid memberId)
{
string sql = $"EXECUTE dbo.GetUserTrackByMemberID #p0";
var session = _context.Database.ExecuteSqlCommand(sql, memberId);
var json = JsonConvert.SerializeObject(session);
return json;
}
This is the ExecuteSqlCommand example, this returns -1 and cannot be put into a list as there will be more than one session.
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.Where(ms => ms.MemberId == memberId).Select(s => new Session() { SessionId =
s.SessionId, EventId = s.Session.EventId, CarCategory = s.Session.CarCategory, AirTemp = s.Session.AirTemp,
TrackTemp = s.Session.TrackTemp, Weather = s.Session.Weather, NumberOfLaps = s.Session.NumberOfLaps, SessionLength = s.Session.SessionLength,
Event = new Event() { EventId = s.Session.Event.EventId, TrackId = s.Session.Event.TrackId, Name = s.Session.Event.Name, NumberOfSessions =
s.Session.Event.NumberOfSessions, DateStart = s.Session.Event.DateStart, DateFinish = s.Session.Event.DateFinish, TyreSet = s.Session.Event.TyreSet,
Track = new Track() { TrackId = s.Session.Event.Track.TrackId, Name = s.Session.Event.Track.Name, Location = s.Session.Event.Track.Location, TrackLength
= s.Session.Event.Track.TrackLength, NumberOfCorners = s.Session.Event.Track.NumberOfCorners} } });
var json = JsonConvert.SerializeObject(session);
return json;
}
This is using Linq, however it's really messy and I feel there's probably a better way to do this, and then when retrieving the data from json it's a lot bigger pain.
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.FromSql($"EXECUTE dbo.GetUserSessionByMemberID {memberId}").ToList();
var json = JsonConvert.SerializeObject(session);
return json;
}
This is the ideal way I would like to do it, however since I'm using the MemberSession model it will only retrieve that data from the stored procedure which is in the MemberSession table, however I want data that is in other tables as well....
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.Where(ms => ms.MemberId == memberId).Include("Session").Include("Event").ToList();
var json = JsonConvert.SerializeObject(session);
return json;
}
I tried this way but because the Event table has no reference / relationship to MemberSession it returns an error.
As I've previously stated in the RawSql example I'm only getting the table data that is in the MemberSession table, no other tables.
There are no error messages.
using (var context = new DBEntities())
{
string query = $"Exec [dbo].[YOUR_SP]";
List<ResponseList> obj = context.Database.SqlQuery<ResponseList>(query).ToList();
string JSONString = JsonConvert.SerializeObject(obj);
}
Here are two examples of methods, the first one uses a foreach statement to populate a data model
public List<TheOrderSummary> GetTheOrderSummaryByID(int id)
{
ExoEntities = new ExoEntities();
List<TheOrderSummary> lst = new List<TheOrderSummary>();
foreach(var a in query)
{
lst.Add(new TheOrderSummary
{
OrderDetailID = a.OrderDetailID,
OrderHeaderID = a.OrderHeaderID,
ItemSeq = a.ItemSeq,
MaterialID = a.MaterialID,
Description = a.Description,
DWidth = a.DWidth,
DLength = a.DLength,
MaterialArea = a.MaterialArea.ToString(),
MaterialDetail = a.MaterialDetail,
DHeight = a.DHeight,
PurchUnitOfMeasure = a.PurchUnitOfMeasure,
SellUnitOfMeasure = a.SellUnitOfMeasure,
MaterialCategory = a.MaterialCategory,
MaterialType = a.MaterialType,
MaterialSubType = a.MaterialSubType,
ColorID = (int)a.ColorID,
Color = a.Color,
MaterialPrice = a.MaterialPrice,
MaterialCost = a.MaterialCost,
MaterialLocationID = (int)a.MaterialLocationID,
MaterialLocation = a.MaterialLocation,
LaborPrice = a.LaborPrice,
LaborCost = a.LaborCost,
VendorID = (int)a.VendorID,
VendorName = a.VendorName,
Size = a.Size,
Height = (decimal)a.Height,
Length = (decimal)a.Length,
Width = (decimal)a.Width,
PurchaseQuantity = (decimal)a.PurchaseQuantity,
SellQuantity = (decimal)a.SellQuantity,
TotalFootage = (decimal)a.TotalFootage,
GeneratedItemInd = (int)a.GeneratedItemInd,
ExtendedMaterialPrice = (decimal)a.ExtendedMaterialPrice,
ExtendedLaborCost = (decimal)a.ExtendedLaborCost,
ExtendedMaterialCost = (decimal)a.ExtendedMaterialCost
});
}
return lst;
}
and this one uses a stored procedure to return an object
public List<usp_GetTheOrderDetails_Result> GetTheOrderSummaryByID(int id)
{
ExoEntities = new ExoEntities();
var query = ExoEntities.usp_GetTheOrderDetails(id);
return query.ToList();
}
Both of these are in a DAL and the method that could call either one of these is a JSONResult, both of these can be used to populate a grid. What ramifications would using the second type be down the road as opposed to the first one? They both return the exact same thing, from the looks of it on a performance level, without doing the numbers, the second one would be faster
The pain that is introduced by returning the result directly is that you get a "hard-wired" dependency on the information contract between the consumer and the underlying data provider. If you make a change to the underlying data structure, you must update the client at the same time (which may come with various degrees of pain).
If you instead go with your first option, you encapsulate the knowledge of the underlying information structure into this layer, and may use that to map between the old and new contracts, thereby decoupling the direct dependency between the client and the data provider.
So yes, the second option might bit a tad faster (even though that really should be marginal), but the first one is likely to give you much less pain when it comes to maintaining code and deployments in the longer run.
How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}
I have a code that gets all products from my DB:
using (var entities = new DataEntities())
{
var products = entities.Products.AsQueryable();
if (!string.IsNullOrEmpty(nameFilter))
{
products = products.Where(o => o.Name.Contains(nameFilter));
}
var result = products.Select(ProductBuilder.CreateProductDto);
return result.ToList();
}
CreateProductDto method:
public static ProductDto CreateProductDto(this Product product)
{
return new ProductDto
{
Id = product.Id,
Name = product.Name,
IsEnabled = product.IsEnabled,
KeyPairDto = new KeyPairDto()
{
Id = product.KeyPair.Id,
EncryptedPrivateExponent = product.KeyPair.EncryptedPrivateExponent,
Modulus = product.KeyPair.Modulus,
PublicExponent = product.KeyPair.PublicExponent,
},
};
}
It works fine on my colleaugue's machine. But I get EntityCommandExecutionException with the folloing inner exception: There is already an open DataReader associated with this Command which must be closed first. when trying to access product.KeyPair.
Interesting thing is that if I refresh product.KeyPair via Debugger - it loads fine.
Add
MultipleActiveResultSets=true
to the provider part of the connection string within your entity framework connection string (i.e. the part the defines the data source, initial catalog, etc)
I have method that looks like this:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = db.Organizations.Select(org => new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
SiteCount = org.Sites.Count(),
DbSecureFileCount = 0,
DbFileCount = 0
});
return results;
}
This is returns results pretty promptly.
However, you'll notice the OrganizationViewModel has to properties which are getting set with "0". There are properties in the Organization model which I added via a partial class and decorated with [NotMapped]: UnsecureFileCount and SecureFileCount.
If I change those 0s to something useful...
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount
... I get the "Only initializers, entity members, and entity navigation properties are supported" exception. I find this a little confusing because I don't feel I'm asking the database about them, I'm only setting properties of the view model.
However, since EF isn't listening to my argument I tried a different approach:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = new List<OrganizationViewModel>();
foreach (var org in db.Organizations)
{
results.Add(new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
});
}
return results;
}
Technically this gives me the correct results without an exception but it takes forever. (By "forever" I mean more than 60 seconds whereas the first version delivers results in under a second.)
Is there a way to optimize the second approach? Or is there a way to get the first approach to work?
Another option would be to load the values back as an anonymous type and the loop through those to load your viewmodel (n+1 is most likely the reason for the slowness).
For example:
var results = db.Organizations.Select(org => new
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
}).ToList();
var viewmodels = results.Select( x=> new OrganizationViewModel
{
Id = x.Id,
Name = x.Name,
DbSecureFileCount = x.DbSecureFileCount,
DbFileCount = x.DbFileCount,
SiteCount = x.SiteCount
});
Sorry about the formatting; I'm typing on a phone.
You are basically lazy loading each object at each iteration of the loop, causing n+1 queries.
What you should do is bring in the entire collection into memory, and use it from there.
Sample code:
var organizationList = db.Organizations.Load();
foreach (var org in organizationList.Local)
{
//Here you are free to do whatever you want
}