How to get all information aggregated with nhibernate - asp.net-mvc

Take a look the classes below:
public class Produt
{
public virtual int id { get; set; }
public virtual string name { get; set; }
[ScriptIgnore]
public virtual Unit unit { get; set; }
}
public class Unit
{
public virtual int id { get; set; }
public virtual string name { get; set; }
public virtual IList<Produt> produts { get; set; }
}
After this, the mappings:
public partial class ProdutMap : ClassMap<Produt>
{
public ProdutMap()
{
Id(x => x.id).GeneratedBy.Identity();
Map(x => x.name).Length(100).Not.Nullable();
References(x => x.unit, "idUnit").Cascade.All().LazyLoad();
}
}
public partial class UnitMap : ClassMap<Unit>
{
public UnitMap()
{
Id(x => x.id).GeneratedBy.Identity();
Map(x => x.name).Length(100).Not.Nullable();
HasMany(x => x.produts).Cascade.All().KeyColumns.Add("idUnit").LazyLoad();
}
}
Now, imagine that I want to execute this query:
SELECT produt.id, produt.name, unit.name FROM Produt, Unit WHERE produt.idUnit = unit.id
with nhibernate? How to do? Something help?
P.S. The [ScriptIgnore] is because I had problems with circular references. My classes are not only these. This is just an example.

Just fetch Products
The simplest way to do this is to just fetch a list of Products. Product already contains all of the information you need because it has a reference to Unit.
// NOTE: This fetches ALL products. You really should limit this.
var products = session.Query<Product>();
foreach (var product in products)
Console.WriteLine("Product: {0}, Unit: {1}", product.Name, product.Unit.Name);
On each iteration of this loop, if product.Unit points to a unit that NHibernate has not fetched yet, NHibernate will lazily execute another query to fetch that unit.
Sometimes you are not able to use lazy loading - perhaps you need to close the NHibernate session before iterating over the results. Also, for performance it would be better to reduce the number of round-trips to the database. We can fix these problems by changing our query like so:
var products = session.Query<Product>().Fetch(x => x.Unit);
Flatten your results
If for some reason you need flattened result objects where you don't have to dig through nested objects to get the data you need, you can use "projections" to do this. With LINQ, this looks like:
var productInfos = session.Query<Product>().Select(x => new
{
ProductId = x.Id,
ProductName = x.Name,
UnitName = x.Unit.Name
});
This is also useful if you need to limit the columns returned by NHibernate - for example, if one of the column contains huge BLOBs that you want to avoid fetching.
Besides LINQ, NHibernate has several different ways to execute queries: native SQL, HQL, Criteria, and QueryOver. For Criteria or QueryOver, the AliasToBean result transformer will help you when executing these types of queries. See this related question for an example using AliasToBean in a Criteria query: NHibernate - Only retrieve specific columns when using Critera queries?

Related

Add or remove column mapping at runtime for linq2db

I have a scenario where if certain features are deployed then a number of columns will be there in some tables otherwise won't so the mapping of Entities and Columns is not static. I need to add/remove the mapping at runtime. Is there any way?
Prepare a new MappingSchema and pass to DataConnection constructor.
Consider you have the following class:
[Table]
class SampleClass
{
[Column] public int Id { get; set; }
[Column] public int Value { get; set; }
}
To remove column from full object materialization, do the following:
var ms = new MappingSchema();
ms.GetFluentMappingBuilder()
.Entity<SampleClass>().Property(e => e.Value).IsNotColumn();
// cache somewhere this schema
using (var db = new DataConnection(ms))
{
var result = db.GetTable<SampleClass>().ToArray();
}
Remember, better to cache this new MappingSchema and reuse. Otherwise you will never have cache hit and you'll lose performance.

Cannot map LINQ to Entities

I'm not very clear with writing linq queries. I write a query to select only certain columns from a table using linq lambda expression and I get the error that linq cannot be constructed to entities. The same query when I write using linq to select all columns I don't get any errors and I get all the columns, which i later filter out in the view. But I want to use the lambda to select only certain columns.
Code snippet:
ViewModel:
public class StaggingInternalCashExceptionViewModel
{
public OutputCash OutputCash { get; set; }
public IEnumerable<StaggingInternalException> StaggingInternalException { get; set; }
//list of results of Stagginginternalcashexception
}
Controller:
public ActionResult Exceptionstest(string dd1, string dd2, string dd3)
{
StaggingInternalExceptionViewModel _app = new StaggingInternalExceptionViewModel();
_app.StaggingInternalException = db2.StaggingInternalExceptions.Where(x => x.Level1 == dd1 && x.Level2 == dd2 ).Select(i => new StaggingInternalException
{
StaggingInternalRowID = i.StaggingInternalRowID,
Category = i.Category,
EnterText1 = i.EnterText1,
InternalAmount = i.InternalAmount,
ExternalAmount = i.ExternalAmount
});
_app.StaggingInternalException = (from p in db2.StaggingInternalExceptions
where p.LoadID==loadid && p.Level1 == dd1 && p.Level2 == dd2 select p);
}
In the above code, the lambda expression throws an error when I'm trying to select only certain columns from the table or if we are speaking in terms of entity classes, only certain properties. But the query returns all the columns. Should I be using DTOS? I'm not sure what the use of data transfer objects is. Some explanation on this would be great. Thanks.
You need to use a DTO.
A dto is just an object that you map your result to. In your case it would be
public class StaggingInternalExceptionViewModel
{
public int StaggingInternalRowID { get; set; }
public int Category { get; set; }
... //rest of properties
}
You need to change your StaggingInternalCashExceptionViewModel to use the StaggingInternalException DTO
public class StaggingInternalCashExceptionViewModel
{
public OutputCash OutputCash { get; set; }
public IEnumerable<StaggingInternalExceptionViewModel> StaggingInternalException { get; set; }
//list of results of Stagginginternalcashexception
}
Then your expression stays the basically the same but you select a new StaggingInternalExceptionViewModel instead of StaggingInternalException
StaggingInternalExceptionViewModel _app = new StaggingInternalCashExceptionViewModel();
_app.StaggingInternalException = db2.StaggingInternalExceptions.Where(x => x.Level1 == dd1 && x.Level2 == dd2 ).Select(i => new StaggingInternalExceptionViewModel
{
StaggingInternalRowID = i.StaggingInternalRowID,
Category = i.Category,
EnterText1 = i.EnterText1,
InternalAmount = i.InternalAmount,
ExternalAmount = i.ExternalAmount
});
Linq to Entities doesn't let you project a query using an entity type because you can end up losing information at loading an entity partially and trying later to save that entity to your DB. So, you must project your queries when you need partial information of an entity whether using a DTO or an anonymous type.
If you need to use the entity type, then don't project using Select method, the only thing is you're going to load all the properties, but I think this is not the case because you don't need all the data ;).

What Can/Cannot be done inside a Generic repository

I am working on an asp.net mvc-5 with Entity framework 6. now currently i am not using any kind on generic repositories , as the ones mentioned here:-
Link-1
&
Link-2
now the generic repository gives you a feeling that you can do everything in a generic way.. but inside these 2 links seems what can be generilzed are the basic operations for get, add, delete & modify which are by defualt provided inside Entity framework. so can anyone adivce on thses question regading using Generic repositories with EF-6 & MVC-5:-
1.is it really a good approach of using Generic repo ? as seems generic repo will just provide what EF already provide !!
2.let say i have two Parent/Child (DataCenter/Zone) objects:-
public class DataCenter
{
public int ID { get; set; }
public string Name { get; set; }
public byte[] timestamp { get; set; }
public virtual ICollection<Zone> Zones { get; set; }
}
public class Zone
{
public int ZoneID { get; set; }
public string Name { get; set; }
public int DataCenterID { get; set; }
public virtual DataCenter DataCenter { get; set; }
}
now using the Generic repository i can Get,Add,Edit,Delete these 2 objects using the same generic repo methods. but let say i want to retrieve all the Zones related to a specific Datacenter as follow:-
var results = entity.DataCenters.SingleOrDefault(a => a.ID == id).Zones.Where(a => a.Name.Contains("1"));
so can the generic repository support such a query , in a way that i can re-use the query with another object types (other than Datacenter & zones). for example to have a generic query :- to get a parent object by ID and for its child to get the childs that have their names contain the word "1" ?? and what if the parent have multiple child types !! will the generic repository support non-generic queries and operations ?
I went through the same question... I first did a specific repository, then I changed to a generic. But I've ended up having to code so much specific queries, that I decide to change for non-generic repositories, returning ToList (I didn't want IQueryable) and using Unit of Work pattern. Now I think I'm happy with the way things are.
Edit:
Query the child by it's property, bringing back the parent too (is that what you want?):
return await _context.Entity.Include(e => e.Parent)
.Where(e => e.SomeProp == someParam)
.ToListAsync();
Or, Querychild, using some property in the parent, bringing back The parent:
return await _context.Entity.Include(e => e.Parent)
.Where(e => e.Parent.SomeProp == someParam)
.ToListAsync();

Updating many-to-many relationship entity framework

I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.

What's the best way to fill POCOs with dummy data?

I have a bunch of POCOs that all relate to each other in a big tree. For example, this is the top-level element:
public class Incident : Entity<Incident>
{
public virtual string Name { get; set; }
public virtual DateTime Date { get; set; }
public virtual IEnumerable<Site> Sites { get; set; }
public Incident()
{
Sites = new HashSet<Site>();
}
}
The tree goes something like Incident -> Sites -> Assessments -> Subsites -> Images. The POCO's don't have any logic, just a bunch of properties. What I want to do is just fill every property with random dummy data so I can write some search code. What's the best way of doing this if I want to create a large number of dummy data?
I would consider using NBuilder. It lets you do just that - create random data for your objects, using a pretty straightforward syntax. For example:
var products = Builder<Product>.CreateListOfSize(100)
.WhereTheFirst(5)
.Have(x=>x.Title = "something")
.AndTheNext(95)
.Have(x => x.Price = generator.Next(0, 10));

Resources