Calling all NHibernate gurus out there!
If any one of you brainy folks could help me with the following conundrum I'd be most grateful:
I have some entities that describe RSS feeds from various sources that are grouped together in an entity called FeedList.
I am trying to select only the distinct SourceFeed entities that are linked with a given FeedList. (i.e "WHERE FeedList.name = 'feedlist1' ".
I've been playing around with JoinQueryOver for a while now but I just can't seem to work out how to get the required results.
The entities are related like this:
FeedList > Feed > FeedSource
So a FeedList contains many Feeds and each Feed belongs to a FeedSource.
Here is the code for the entities:
public class Feed
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual FeedSource FeedSource { get; set; }
public virtual string URL { get; set; }
}
public class FeedList
{
public virtual int Id{get;set;}
public virtual string Name { get; set; }
public virtual IList<Feed> Feeds { get; set; }
}
public class FeedSource
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I am using Fluent Automapping with the following overrides:
public class FeedOverride : IAutoMappingOverride<Feed>
{
public void Override(AutoMapping<Feed> mapping)
{
mapping.References<FeedSource>(map => map.FeedSource).Cascade.All();
}
}
public class FeedListOverride : IAutoMappingOverride<FeedList>
{
public void Override(AutoMapping<FeedList> mapping)
{
mapping.HasManyToMany<Feed> (map => map.Feeds).Cascade.All().Table("FeedList_Feed");
}
}
Here is some code that creates some sample data:
private static void CreateSampleData()
{
using (var session = HibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
//Create source 1 and feeds
FeedSource source1 = new FeedSource() { Name = "BBC" };
IList<Feed> feedsForSource1 = new List<Feed>(){
CreateFeed("Sample Feed1",source1,"http://feed1.xml"),
CreateFeed("Sample Feed2",source1,"http://feed2.xml")
};
FeedList feedList1 = CreateFeedList("FeedList1", feedsForSource1);
//Create source 2 and feeds
FeedSource source2 = new FeedSource() { Name = "Sky" };
IList<Feed> feedsForSource2 = new List<Feed>(){
CreateFeed("Sample Feed3",source2,"http://feed3.xml"),
CreateFeed("Sample Feed4",source2,"http://feed4.xml"),
CreateFeed("Sample Feed5",source2,"http://feed5.xml")
};
FeedList feedList2 = CreateFeedList("FeedList2", feedsForSource2);
session.SaveOrUpdate(feedList1);
session.SaveOrUpdate(feedList2);
transaction.Commit();
}
}
}
What am I trying to achieve?
If I were to describe it (very poorly) in SQL, I'm looking to do something like this:
select distinct *
from FeedList as list
LEFT JOIN FeedList_Feed as list_feed
ON list.Id = list_feed.FeedList_id
LEFT JOIN Feed as feed
ON feed.FeedSource_id = list_feed.Feed_id
LEFT JOIN FeedSource as src
ON src.Id = list_feed.Feed_id
WHERE list.Name="a feedlist name"
Thanks very much for your time :)
Ok so I've managed to work this out finally for anyone that's interested:
Entities
public class Feed
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual FeedSource FeedSource { get; set; }
public virtual string URL { get; set; }
public virtual FeedList FeedList { get; set; }
}
public class FeedList
{
public virtual int Id{get;set;}
public virtual string Name { get; set; }
public virtual IList<Feed> Feeds { get; set; }
}
public class FeedSource
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
Overrides
public class FeedOverride : IAutoMappingOverride<Feed>
{
public void Override(AutoMapping<Feed> mapping)
{
mapping.References<FeedList>(map => map.FeedList).Cascade.All();
mapping.References<FeedSource>(map => map.FeedSource).Cascade.All();
}
}
public class FeedListOverride : IAutoMappingOverride<FeedList>
{
public void Override(AutoMapping<FeedList> mapping)
{
mapping.HasManyToMany<Feed>(map => map.Feeds).Cascade.All().Table("FeedList_Feed");
//mapping.HasMany<FeedListFeed>(map => map.Feeds).Cascade.All();
}
}
And finally:
The Query
session.QueryOver<FeedList>()
.Inner.JoinAlias(f => f.Feeds, () => feedAlias)
.Where(fl => fl.Name == name)
.Select(x => feedAlias.FeedSource)
.List<FeedSource>().Distinct().ToList();
Related
I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.
These are my classes:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Dashboard : BaseEntity
{
public int Order { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Submenu> Submenu { get; set; }
}
public class Submenu : BaseEntity
{
public int Order { get; set; }
public bool Enabled { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public string Descriptions { get; set; }
public virtual ICollection<Action> Actions { get; set; }
public int DashboardId { get; set; }
public virtual Dashboard Dashboard { get; set; }
}
public class Action : BaseEntity
{
public string Type { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Extension { get; set; }
public virtual Submenu Submenu { get; set; }
public int SubmenuId { get; set; }
}
The one I am using to update is Dashboard, which contains the rest of the classes.
I'm trying to do it using a generic service layer and a generic repository that are defined this way:
public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
private readonly IBaseRepository<T> baseRepository;
public GenericService(IBaseRepository<T> baseRepository)
{
this.baseRepository = baseRepository;
}
public async Task Update(T entity, T attachedEntity)
{
await baseRepository.Update(entity, attachedEntity);
}
}
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly PortalContext dataContext;
private DbSet<T> DbSet { get; set; }
public BaseRepository(PortalContext context)
{
dataContext = context;
DbSet = dataContext.Set<T>();
}
public async Task Update(T entity, T attachedEntity)
{
dataContext.Entry(attachedEntity).State = EntityState.Detached;
DbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
await dataContext.SaveChangesAsync();
}
}
And, at last but no least, this is the way I am configuring everything at Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortalContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
);
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
{
return (controllerName, claimsPrincipal) =>
new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
controllerName, claimsPrincipal);
});
}
What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.
When I call the update function at the controller
public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
{
var currentDashboard = await service.Get(id);
if (currentDashboard == null)
{
return NotFound();
}
await service.Update(dashboard, currentDashboard);
return Ok();
}
I always receive the next error at the repository:
error
Is there something I am doing wrong? I have been stuck with this for a week now...
Thanks in advance and sorry for the long text, but I wanted it to be clear.
I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:
public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
IQueryable <T> query = DbSet.AsNoTracking();
if (includes != null)
{
query = includes(query);
}
return await query.FirstOrDefaultAsync(m => m.Id == id);
}
When I call the object model variable ,straight from the database, I receive all the needed data about the dependent entity: MenuItem.
When i use an automapper it displays the mapped MenuItem object as null.
Automapping for getAll and getAllById works.
Principle Entity: ItemCategory
DependentEntity: MenuItem
Please help.
Mapping Profile:
public class MappingProfile: Profile
{
public MappingProfile()
{
CreateMap<ItemCategory, ItemCategoryDto>();
CreateMap<ItemType, ItemTypeDto>();
CreateMap<ItemStatus, ItemStatusDto>();
CreateMap<MenuItem, MenuItemDto>();
//CreateMap<ItemCategory, ItemCategoryDto>();
//CreateMap<ItemType, ItemTypeDto>();
//CreateMap<ItemStatus, ItemStatusDto>();
}
}
ItemCategory Model class:
public partial class ItemCategory
{
public ItemCategory()
{
MenuItem = new HashSet<MenuItem>();
}
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
MenuItem Model Class:
public partial class MenuItem
{
public MenuItem()
{
Menu = new HashSet<Menu>();
MenuItemAllergy = new HashSet<MenuItemAllergy>();
MenuItemIngredient = new HashSet<MenuItemIngredient>();
MenuItemPrice = new HashSet<MenuItemPrice>();
MenuItemSpecial = new HashSet<MenuItemSpecial>();
OrderMenuItem = new HashSet<OrderMenuItem>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid ItemCategoryId { get; set; }
public Guid ItemTypeId { get; set; }
public Guid ItemStatusId { get; set; }
public virtual ItemCategory ItemCategory { get; set; }
public virtual ItemStatus ItemStatus { get; set; }
public virtual ItemType ItemType { get; set; }
public virtual ICollection<Menu> Menu { get; set; }
public virtual ICollection<MenuItemAllergy> MenuItemAllergy { get; set; }
public virtual ICollection<MenuItemIngredient> MenuItemIngredient { get; set; }
public virtual ICollection<MenuItemPrice> MenuItemPrice { get; set; }
public virtual ICollection<MenuItemSpecial> MenuItemSpecial { get; set; }
public virtual ICollection<OrderMenuItem> OrderMenuItem { get; set; }
}
ItemCategoryDTO:
public class ItemCategoryDto
{
public Guid Id { get; set; }
public string Description { get; set; }
public IEnumerable<MenuItemDto> MenuItems { get; set; }
}
MenuItemDTO:
public class MenuItemDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
ItemCategory Interface:
public interface IItemCategory: IRepositoryBase<ItemCategory>
{
IEnumerable<ItemCategory> GetAllItemCategories();
ItemCategory GetItemCategoryById(Guid categoryId);
ItemCategory GetItemCategoryWithDetails(Guid categoryId);
}
ItemCategory Repository:
public class ItemCategoryRepository: RepositoryBase<ItemCategory>, IItemCategory
{
public ItemCategoryRepository(eWaiterTestContext repositoryContext)
: base(repositoryContext)
{
}
public IEnumerable<ItemCategory> GetAllItemCategories()
{
return FindAll()
.OrderBy(ic => ic.Description)
.ToList();
}
//Can Add Find By description same way
public ItemCategory GetItemCategoryById(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.FirstOrDefault();
}
public ItemCategory GetItemCategoryWithDetails(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.Include(z=>z.MenuItem)
.FirstOrDefault();
}
}
ItemCategory Controller to return ItemCategory with related menuItems:
[HttpGet("{id}/menuItem")]
public IActionResult GetItemCategoryWithDetails(Guid id)
{
try
{
var category = _repository.ItemCategory.GetItemCategoryWithDetails(id);
if (category == null)
{
_logger.LogError($"Category with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_logger.LogInfo($"Returned owner with details for id: {id}");
var result = _mapper.Map<ItemCategoryDto>(category);
return Ok(result);
}
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetItemCategoryWithDetails action:
straight from the database{ex.Message}");
return StatusCode(500, "Internal server error");
}
}
Names of the properties must be the same, but you just skipped 's' for ItemCategory model
ItemCategory Model class:
public partial class ItemCategory {
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
ItemCategoryDto Model class:
public partial class ItemCategoryDto {
public virtual ICollection<MenuItem> MenuItems { get; set; }
}
Or you can just setup AutoMapper like this :)
public class CategoryMapper: Profile
{
public CategoryMapper()
{
CreateMap<ItemCategory, ItemCategoryDto>()
.ForMember(x => x.MenuItems, x => x.MapFrom(src => src.MenuItem));
}
}
and inject it in Startup.cs
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new GemlemMapper());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
Hope it will help, also be aware that's all properties which you need to use should be Included by the Include().
I have two tables "recall" and "service" and I need many to many between them.
I use fluent NHibernate mapping but it creates additional table with name "servicetorecall"
public class Recall : BaseDomain
{
public virtual string Name { get; set; }
public virtual string PersonPosition { get; set; }
public virtual string RecallText { get; set; }
private ICollection<Service> _services = new List<Service>();
public virtual ICollection<Service> Services
{
get { return _services; }
set { _services = value; }
}
}
public class Service : BaseDomain
{
public virtual string Name { get; set; }
public virtual string Url { get; set; }
public virtual string ImgPath { get; set; }
public virtual string ShortContent { get; set; }
public virtual string Content { get; set; }
public virtual bool ServiceIsVisible { get; set; }
ICollection<Recall> _recalls = new List<Recall>();
public virtual ICollection<Recall> Recalls
{
get { return _recalls; }
set { _recalls = value; }
}
}
Mappings :
class RecallMappingOverride : IAutoMappingOverride<Recall>
{
public void Override(AutoMapping<Recall> mapping)
{
mapping.Cache.ReadWrite();
mapping.HasManyToMany(q => q.Services).Table(MappingNames.RECALLS_RELATIONS)
.ParentKeyColumn(MappingNames.RECALL_ID)
.ChildKeyColumn(MappingNames.SERVICE_ID).Inverse().Cascade.All();
}
}
public class ServiceMappingOverride : IAutoMappingOverride<Service>
{
public void Override(AutoMapping<Service> mapping)
{
mapping.Cache.ReadWrite();
mapping.HasManyToMany(q => q.Recalls).Table(MappingNames.RECALLS_RELATIONS) .ParentKeyColumn(MappingNames.SERVICE_ID).ChildKeyColumn(MappingNames.RECALL_ID)
.Inverse().Cascade.All();
}
}
I tried to change cascades but this didn't help. Also I did the same with other entities and it works correctly what type of magic is it?
How do you define "correct", what do you want to achieve?
I never heard of any clean solution for many to many relations which doesn't use pivot table.
[quick glimpse at your mappings]: only one of the "ManyToMany" should be Inverse
I've created the following domain classes:
public class Car
{
public virtual int Id { get; set; }
public virtual string Registration { get; set; }
public virtual User ResponsibleContact { get; set; }
protected Car()
{}
public Fahrzeug(User responsibleContact, string registration)
{
ResponsibleContact = responsibleContact;
Registration = registration;
ResponsibleContact.Cars.Add(this);
}
}
public class User
{
public virtual int Id { get; set; }
public virtual byte[] EncryptedUsername { get; set; }
public virtual byte[] EncryptedPassword { get; set; }
public virtual IList<Car> Cars { get; private set; }
public virtual string Username
{
get
{
var decrypter = UnityContainerProvider.GetInstance().UnityContainer.Resolve<IRijndaelCrypting>();
return decrypter.DecryptString(EncryptedUsername);
}
}
protected User()
{ }
public User(byte[] encryptedUser, byte[] encryptedPassword)
{
Cars = new List<Car>();
EncryptedUsername = encryptedUser;
EncryptedPassword = encryptedPassword;
}
}
and the mapping classes:
public class CarMap : ClassMap<Car>
{
public CarMap()
{
Id(c => c.Id).GeneratedBy.Native();
Map(c => c.Registration);
References(c => c.ResponsibleContact).Not.Nullable();
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(st => st.Id).GeneratedBy.Native();
Map(st => st.EncryptedUsername).Column("Username");
Map(st => st.EncryptedPassword).Column("Password");
HasMany(st => st.Cars).Inverse().AsBag();
}
}
If I query some Member objects, I get the members, but the cars collection is empty!
If I query some Cars I got all the cars with the right Member. But within the member, the cars collection is also empty!
Is there anybody who has an Idea of what can happened?
you have to make sure the foreign key column of the collection and the reference is the same otherwise there is a mismatch.
References(c => c.ResponsibleContact, "ResponsibleContact_id").Not.Nullable();
and
HasMany(st => st.Cars).Inverse().KeyColumn("ResponsibleContact_id");
I'm using ASP.NET MVC with NHibernate and Fluent.NHibernate Maps.
I would like to know how to map the classes on Fluent and to create the database tables on my MySQL:
public class AgenteDeViagem {
public virtual int Id { get; set; }
public virtual string Email { get; set; }
public virtual AgentePessoa AgentePessoa { get; set; }
}
public interface AgentePessoa {
}
public class AgenteDeViagemPJ:AgentePessoa {
public virtual int Id { get; set; }
public virtual AgenteDeViagem AgenteDeViagem { get; set; }
public virtual string Razao { get; set; }
}
public class AgenteDeViagemPF:AgentePessoa {
public virtual int Id { get; set; }
public virtual AgenteDeViagem AgenteDeViagem { get; set; }
public virtual string Nome { get; set; }
}
Thank you very much!
Looks to me like you're halfway there. You're already using virtual and relations are set, so using the Automapping strategy, you only need to build the session factory:
private static ISessionFactory InitializeNHibernate()
{
var cfg = Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString(c =>
c.Database("agente").Server("localhost")
.Username("user").Password("password")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<AgenteDeViagem>())
.ExposeConfiguration(configuration =>
{
// Comment to disable schema generation
BuildDatabaseSchema(configuration);
});
return cfg.BuildSessionFactory;
}
private static void BuildDatabaseSchema(Configuration configuration)
{
var schemaExport = new SchemaExport(configuration);
schemaExport.SetOutputFile("mysql_script.sql");
schemaExport.Create(false, true);
}