Is there any way in NHibernate that I can use the following Entities
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Pet> Pets { get; set; }
}
public class Pet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
And not have to create a "special" AddPet method on Person in order to have Child pets saved.
public void AddPet(Pet p)
{
p.Person = this;
Pets.Add(p);
}
_session.SaveOrUpdate(person);
Does not save the Pets because Pet has no Person reference.
If I update Pets to contain this reference.
public class Pet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Person Person { get; set; }
}
On new pets I still have to set Person this seems like overkill to me and also risky as People can still call
person.Pets.Add(new Pet())
The only other option I can think of is a Custom list that sets the parent reference when adding child entities.
I modified your example just a bit (in line with many of the suggestions here):
public class Person
{
private IList<Pet> pets;
protected Person()
{}
public Person(string name)
{
Name = name;
pets = new List<Pet>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IEnumerable<Pet> Pets
{
get { return pets; }
}
public virtual void AddPet(Pet pet)
{
pets.Add(pet);
}
}
public class Pet
{
protected Pet()
{}
public Pet(string name)
{
Name = name;
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
HasMany(x => x.Pets).Cascade.AllDeleteOrphan().Access.AsLowerCaseField();
}
}
public class PetMap : ClassMap<Pet>
{
public PetMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
The following test:
[Test]
public void CanSaveAndRetrievePetAttachedToPerson()
{
Person person = new Person("Joe");
person.AddPet(new Pet("Fido"));
Session.Save(person);
Person retrievedPerson = Session.Get<Person>(person.Id);
Assert.AreEqual("Fido", retrievedPerson.Pets.First().Name);
}
passes.
Note that this is using Fluent NHibernate for the mapping and the Session.
C# Reflection & Generics
Might be able to use this approach?
Wont work for children of children but its pretty good for Parent-Children.
protected static void SetChildReferences(E parent)
{
foreach (var prop in typeof(E).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanRead) continue;
Type listType = null;
foreach (Type type in prop.PropertyType.GetInterfaces())
{
if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ICollection<>))
{
listType = type.GetGenericArguments()[0];
break;
}
}
List<PropertyInfo> propsToSet = new List<PropertyInfo>();
foreach (PropertyInfo childProp in
(listType ?? prop.PropertyType).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (childProp.PropertyType == typeof(E))
propsToSet.Add(childProp);
}
if (propsToSet.Count == 0) continue;
if (listType == null)
{
object child = prop.GetValue(parent, null);
if (child == null) continue;
UpdateProperties(propsToSet, child, parent);
}
else
{
ICollection collection = (ICollection)prop.GetValue(parent, null);
foreach (object child in collection)
{
if (child == null) continue;
UpdateProperties(propsToSet, child, parent);
}
}
}
}
protected static void UpdateProperties(List<PropertyInfo> properties, object target, object value)
{
foreach (PropertyInfo property in properties)
{
property.SetValue(target, value, null);
}
}
How do you know which pets does a person have in the database if you don't store the relation?
I would include the
public void AddPet(Pet p)
in the Person object and have a
IEnumerable<Pets> Pets { get; }
public property. The List property would be protected. This way person.Pets.Add(new Pet()) cannot happen and you're safe.
This is not a NHibernate issue, it is a domain model issue. NHibernate is not responsible to build up your references and back references of you domain.
I agree with gcores, make the list protected or private. Find a way to link your objects together in a consistent way. if it is not as simple as implementing a add method, consider a separate service class or factories. (If it is even more complicated, try something like Spring.Net.)
By the way, NHibernate probably stored the pets, but didn't have the information to write the correct foreign key. So it stored orphaned pets which never appeared in a pets list of any person.
Related
I have the following class
public class School
{
public List<Student> Students { get; set; }
public List<Teacher> Teachers { get; set; }
}
Now i have this method
public bool Evaluate(??)
{
var school = DbContext.Schools.FirstOrDefault();
return school.??.Any(/*some expresions*/)
}
I should be able to pass a value in ?? and use it so that i can use both
return school.Students.Any(/*some expresions*/)
return school.Teachers.Any(/*some expresions*/)
So how can i replace the question marks with Students or Teachers ?
Edit:
public class Student
{
public string FullName { get; set; }
public bool Registered { get; set; }
public bool Passed { get; set; }
}
public class Teacher
{
public string FullName { get; set; }
public bool CanEvaluate { get; set; }
public bool Validator { get; set; }
}
public class DynamicCheckTest
{
public bool MyExpression<T>(List<T> items, string name,
Expression<Func<T, bool>> expression)
{
return items.Any(x => expression.Compile()(x));
}
}
public static bool Check<T>(this List<T> items, Func<T, bool> compiledExp)
{
return items.Any(x => compiledExp(x));
}
Students.Check(x => x.Name == "Mike" && x.Registered); // example
Teachers.Check(x => x.Name == "Jack" && x.CanEvaluate);// example
Now i have to pass the school along which contains both Students and Teachers
But i don't know which one will be called in advance
You could use this method:
public bool Evaluate<T>(Func<School, List<T>> project, Func<T, bool> filter)
{
var school = DbContext.Schools.FirstOrDefault();
return project(school).Any(filter);
}
If we assume that the implementation of Student and Teacher are this:
public class Student
{
public string Name;
}
public class Teacher
{
public string Subject;
}
Then you could do this:
bool hasFred = Evaluate(school => school.Students, student => student.Name == "Fred Nerk");
bool teachArt = Evaluate(school => school.Teachers, teacher => teacher.Subject == "Art");
Addressing the "Pass property name as parameter" request, you could use reflection for that, but I don't think that's a good way to go. Instead, a Func<School, List<TElement>> could be used to select the desired List<> property to evaluate...
public bool Evaluate<TElement>(Func<School, List<TElement>> listSelector)
where TElement : Person
{
School school = DbContext.Schools.FirstOrDefault();
DateTime today = DateTime.Today;
return listSelector(school)
// For example, check if today is the birthday of anyone in the selected list
.Any(person => person.DateOfBirth.Month == today.Month && person.DateOfBirth.Day == today.Day);
}
As #Enigmativity points out, the type constraint is necessary in order to pass much of a meaningful condition to Any(), which also assumes/requires that Student and Teacher have common ancestry, like this...
public abstract class Person
{
public DateTime DateOfBirth
{
get;
}
}
public class Student : Person
{
}
public class Teacher : Person
{
}
You'd then use a lambda expression to specify the desired List<>...
bool isAnyStudentsBirthday = Evaluate(school => school.Students);
bool isAnyTeachersBirthday = Evaluate(school => school.Teachers);
This will work as long as the members you want Any() to consider are available in the constrained type (i.e. Person). If you wanted to filter using members specific to the Student or Teacher class, your best bet would be to use an approach like #Enigmativity's answer, where the filter itself is a parameter and receives the same derived type as the selected List<> stores.
Note that if you ever want to use Evaluate() with some other collection property of School that is not specifically List<>, or just knowing that all Any() needs is an IEnumerable<>, you could change the return type (last type parameter) of the Func<> to something less-restrictive...
Func<School, IList<TElement>>
Func<School, ICollection<TElement>>
Func<School, IEnumerable<TElement>>
I have classes something like this:
public class foo{
public string FooProp1 {get; set;}
public Bar Bar{get; set;}
}
public class Bar{
public string BarProp1 {get; set;}
public string BarProp2 {get; set;}
}
I have some audit setup where If I Update Foo then I can get that property Name and value for all the property apart from the 'Bar'. Is there a way to get property name and value of 'BarProp1'.
private void ProcessModifiedEntries(Guid transactionId) {
foreach (DbEntityEntry entry in ChangeTracker.Entries().Where(t => t.State == EntityState.Modified).ToList()) {
Track audit = CreateAudit(entry, transactionId, "U");
foreach (var propertyName in entry.CurrentValues.PropertyNames) {
string newValue = entry.CurrentValues[propertyName]?.ToString();
string originalValue = entry.OriginalValues[propertyName]?.ToString();
SetAuditProperty(entry, propertyName, originalValue, audit, newValue);
}
}
}
I want to audit BarProp1 when Foo got changed.
You want classes to report additional information to your auditing system. I think the best place to do that is in your CreateAudit method. The question is how.
You could have code in there that does something special for each incoming entry:
var foo = entry.Entity as Foo;
if (foo != null)
{
// do something with foo.Bar
}
var boo = entry.Entity as Boo;
if (boo != null)
{
// do something with boo.Far
}
etc.
Of course that isn't very pretty.
If you have multiple classes that need to report additional info to the auditor I would define an interface and tack that to each of these classes:
public interface IAuditable
{
string AuditInfo { get; }
}
public class Foo : IAuditable
{
public string FooProp1 { get; set; }
public Bar Bar { get; set; }
[NotMapped]
public string AuditInfo
{
get { return Bar?.BarProp1; }
}
}
And then in CreateAudit:
var auditable = entry.Entity as IAuditable;
if (auditable != null)
{
// do something with auditable.AuditInfo
}
And even if there's only one class that needs this behavior I would still use the interface because it makes your code self-explanatory.
I have a Person class which contains a property that lazy loads (custom made lazy loading) the person address data through accessing the Item property. I would want it to be mapped to a POCO class. How could it be done?
In addition, is it possible to be mapped only if it has data (checking the HasData property) and mapped as null if there isn’t data?.
These are the source classes:
public class SourcePerson
{
public string Name { get; set; }
public MyLazyLoadingObject<SourceAddress> Address;
}
public class SourceAddress
{
public string City { get; set; }
public string Country { get; set; }
}
This is the custom lazy loading class (simplified):
public class MyLazyLoadingObject<T>
{
private int? _id;
private T _object;
public T Item
{
get
{
if (!_object.IsReaded)
{
_object.Read();
}
return _object;
}
}
public bool HasData
{
get
{
return _id.HasValue;
}
}
// Other non-relevant properties and methods
}
These are the destination classes:
public class DestinationPerson
{
public string Name { get; set; }
public DestinationAddress Address;
}
public class DestinationAddress
{
public string City { get; set; }
public string Country { get; set; }
}
Couldn't find conventional way of setting up conversion from MyLazyLoadingObject<T> to T and then T to some TDestination without code repetition.
But custom IObjectMapper implementation with some manual expression building does the job.
Here is the class that builds the mapping expression:
public class MyLazyLoadingObjectMapper : IObjectMapper
{
public bool IsMatch(TypePair context)
{
return context.SourceType.IsGenericType && context.SourceType.GetGenericTypeDefinition() == typeof(MyLazyLoadingObject<>);
}
public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var item = Expression.Property(sourceExpression, "Item");
Expression result = item;
if (item.Type != destExpression.Type)
{
var typeMap = configurationProvider.ResolveTypeMap(item.Type, destExpression.Type);
result = Expression.Invoke(typeMap.MapExpression, item, destExpression, contextExpression);
}
// source != null && source.HasData ? result : default(TDestination)
return Expression.Condition(
Expression.AndAlso(
Expression.NotEqual(sourceExpression, Expression.Constant(null)),
Expression.Property(sourceExpression, "HasData")
),
result,
Expression.Default(destExpression.Type)
);
}
}
All you need is to register it to the MapperRegistry:
AutoMapper.Mappers.MapperRegistry.Mappers.Add(new MyLazyLoadingObjectMapper());
and of course create the regular type maps (which I guess you already did):
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourcePerson, DestinationPerson>();
I've achieved it this way:
cfg.CreateMap<SourcePerson, DestinationPerson>().ForMember(t => t.Address, o => o.MapFrom(s => (s.Address.HasData)? s.Address.Item : null));
I'm using EntityFramework as a DataLayer and DTO to transfer data between layer. I develop Windows Forms in N-Tier architecture and when I try to mapping from Entity to DTO in BLL:
public IEnumerable<CategoryDTO> GetCategoriesPaged(int skip, int take, string name)
{
var categories = unitOfWork.CategoryRepository.GetCategoriesPaged(skip, take, name);
var categoriesDTO = Mapper.Map<IEnumerable<Category>, List<CategoryDTO>>(categories);
return categoriesDTO;
}
I've got this error:
http://s810.photobucket.com/user/sky3913/media/AutoMapperError.png.html
The error said that I missing type map configuration or unsupported mapping. I have registered mapping using profile in this way at UI Layer:
[STAThread]
static void Main()
{
AutoMapperBusinessConfiguration.Configure();
AutoMapperWindowsConfiguration.Configure();
...
Application.Run(new frmMain());
}
and AutoMapper configuration is in BLL:
public class AutoMapperBusinessConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<EntityToDTOProfile>();
cfg.AddProfile<DTOToEntityProfile>();
});
}
}
public class EntityToDTOProfile : Profile
{
public override string ProfileName
{
get { return "EntityToDTOMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<Category, CategoryDTO>();
}
}
public class DTOToEntityProfile : Profile
{
public override string ProfileName
{
get { return "DTOToEntityMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<CategoryDTO, Category>();
}
}
I've got the same error too when mapping from DTO to Entity.
category = Mapper.Map<Category>(categoryDTO);
How to solve this?
Its because you are using Mapper.Initialize multiple times. If you look at the source code it calls Mapper.Reset() which means only the last mapping defined will work. so instead simply remove the Initialize calls and replace with Mapper.AddProfile< >
Use AutoMapper.AssertConfigurationIsValid() after the Configure() calls. If anything fails it will throw an exception with a descriptive text. It should give you more info to debug further.
Mapping DTOs to Entities using AutoMapper and EntityFramework
here we have an Entity class Country and an CountryDTO
public class Country
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
CountryDto
public class CountryDTO
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
Create Object of CountryDTO
CountryDTO collection=new CountryDTO();
collection.CountryID =1;
collection.ContryName ="India";
collection.CountryCode ="in";
Country model = Convertor.Convert<Country, CountryDTO>(collection);
dbcontext.Countries.Add(model);
dbcontext.SaveChanges();
this will work fine for a new Country, the above code will map CountryDTO to Country Entity Object and add new entities to the dbcontext and save the changes.
using System.Reflection;
public static TOut Convert<TOut, TIn>(TIn fromRecord) where TOut : new()
{
var toRecord = new TOut();
PropertyInfo[] fromFields = null;
PropertyInfo[] toFields = null;
fromFields = typeof(TIn).GetProperties();
toFields = typeof(TOut).GetProperties();
foreach (var fromField in fromFields)
{
foreach (var toField in toFields)
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
return toRecord;
}
public static List<TOut> Convert<TOut, TIn>(List<TIn> fromRecordList) where TOut : new()
{
return fromRecordList.Count == 0 ? null : fromRecordList.Select(Convert<TOut, TIn>).ToList();
}
http://bhupendrasinghsaini.blogspot.in/2014/09/convert-enity-framwork-data-in-entity.html
I'm using EF4.1 for the first time (so be patient with me) but I just cant get to grips with how I can add new items to a sub collection of an object and then save the object.
For example, with the classes below, I can initially save the TravelTicket (containing multiple People) into my database, but as soon as I add a new person and then try to save the TravelTicket again I get:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Can anyone help?
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual List<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name{ get; set; }
}
EDITED: All relevant code added as requested:
Domain Models:
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
The DB Context:
public class TicketContext : DbContext
{
public TicketContext()
: base("TicketStore")
{ }
public DbSet<TravelTicket> TravelTickets { get; set; }
public DbSet<Person> People { get; set; }
}
The Repository (relevant methods only):
public class TicketRepository : ITicketRepository
{
TicketContext context = new TicketContext();
public void InsertOrUpdate(TravelTicket quoteContainer)
{
if (quoteContainer.Id == default(int))
{
// New entity
context.TravelTickets.Add(quoteContainer);
}
else
{
// Existing entity
context.Entry(quoteContainer).State = EntityState.Modified;
}
}
public void Save()
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
}
public interface ITicketRepository
{
void InsertOrUpdate(TravelTicket travelTicket);
void Save();
}
The consuming (example) MVC Controller code:
public class TicketSaleController : Controller
{
private readonly ITicketRepository ticketRepository;
public TicketSaleController()
: this(new TicketRepository())
{
}
public TicketSaleController(ITicketRepository ticketRepository)
{
this.ticketRepository = ticketRepository;
}
public ActionResult Index()
{
TravelTicket ticket = new TravelTicket();
ticket.Destination = "USA";
List<Person> travellers = new List<Person>();
travellers.Add(new Person { Name = "Tom" });
travellers.Add(new Person { Name = "Dick" });
travellers.Add(new Person { Name = "Harry" });
ticket.Members = travellers;
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Session["Ticket"] = ticket;
return RedirectToAction("Next");
}
public ActionResult Next()
{
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
return View();
}
}
The call "ticketRepository.InsertOrUpdate(ticket);" on the "Next" method causes the exception:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
FURTHER EDIT: If I pull the object back from the database after its been saved instead of pulling the object from the session, adding the 2 new persons works OK:
Works:
TravelTicket ticket = ticketRepository.Find(ticketId);
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Doesn't Work:
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
I'd need to see the code you are using to add items and then persist them. Until that a few generic advice.
It seems like you're using a long-living context to do your stuff. It's a good practice to use short living context, like this:
Instance context
Do a single operation
Dispose the context
Rinse and repeat for every operation you have to do. While following this good practice, you could be indirectly solving your problem.
Again, for more specific help, please post the code you're using ;)
In your mapping class for person, you may need do something like this
Property(p => p.Id)
.StoreGeneratedPattern = StoreGeneratedPattern.Identity;