I have a database which contains tables of mapping values for a variety of objects.
For example the Colour table contains BLK > Black, BLU > BLUE, ORA > ORANGE etc.. and the CarType table contains 4DH > Four-door hatchback, 4WD > Four wheel drive etc...
I'm using Entity Framework code-first so I have a context set up something like this.
public class MappingContext : DbContext
{
public MappingContext ()
: base("Mappings")
{
}
public DbSet<Colour> ColourMappings { get; set; }
public DbSet<CarType> CarTypeMappings { get; set; }
}
Every object that relates to each table in my Mapping database inherits from a base class like so:
public class Mapping
{
public int Id { get; set; }
public string OrignalValue { get; set; }
public string MappedValue { get; set; }
}
public class CarType : Mapping{}
public class Colour : Mapping{}
Now what I want to do is read these mappings in from an XML file filled with "Templates" which contain the mappings and insert them in the DB.
I have the following method to do this:
public void InsertMappings(XDocument xml, string templateName, Type typeToInsert)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
var mapppings = mappings.Select(mapping => new Mapping
{
MappedValue = mapping.Value,
OrignalValue = GetCode(mapping)
});
foreach (var mapping in mapppings.ToList())
{
var map = (typeToInsert)mapping // << This line will not compile
repo.Insert(map);
}
repo.Save();
}
}
This will not complie as it doesnt recognise the attempted cast "(typeToInsert)mapping".
So basically what I need to know is how to I cast this Mapping object to a Specific Mapping object when it comes to inserting it into the db? Or any suggestions for a better way of doing this!
From the looks of it you are trying to cast an instance of Mapping as a CarType or Colour which won't work because Mapping doesn't know anything about those types as it's the base class.
Your code would need to create an instance of the concrete type i.e. typeToInsert and cast it as Mapping. You could do something like:
public void InsertMappings(XDocument xml, string templateName, Type typeToInsert)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
foreach (var m in mappings)
{
// XML -> Entity
var mapping = (typeToInsert)Activator.CreateInstance(typeToInsert);
(mapping as Mapping).MappedValue = m.Value;
(mapping as Mapping).OriginalValue = GetCode(m);
// Update database
repo.Insert(mapping);
}
repo.Save();
}
}
You should probably be making use of generics here as well, you could refactor your method to look like:
public void InsertMappings<T>(XDocument xml, string templateName)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
foreach (var m in mappings)
{
// XML -> Entity
var mapping = (T)Activator.CreateInstance(typeof(T));
(mapping as Mapping).MappedValue = m.Value;
(mapping as Mapping).OriginalValue = GetCode(m);
// Update database
repo.Insert(mapping);
}
repo.Save();
}
}
Usage
InsertMappings<CarType>(xmlDoc, "CarTemplate");
InsertMappings<Colour>(xmlDoc, "ColourTemplate");
Related
When mapping back from business logic objects to EF objects, one of the main problems I have is cases where the same instance has 2 parents:
(Objects are yellow, properties are orange)
In the business logic world, there is only one instance of the Tree object here (which appears as a child of multiple parents: Forest and Section)
When I map everything back into EF objects with AutoMapper, EF thinks there are 2 separate instances of tree (despite them having the same ID). It therefore creates a duplicate in the DB.
What is the correct way to manage this scenario so that both Forest and Section point to the same record of Tree in the DB?
Do we have to go through and manually make sure everything is attached which might be considered a duplicate?
Unfortunatelly EF needs to get same instance of a Tree object to consider him as same during saving whole Forest graph (overriding of his equality members doesn't help), which is not how Automapper maps object graphs by default.
But you can set up your Automapper configuration in the way it reuses existing instances during mapping:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Tree, TreeEF>().PreserveReferences();
});
Than if you have in your bussines model Forest and Section having a child reference to the same instance of a Tree, this reference will be preserved and no duplicates will be created.
EDIT
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Forest, ForestEF>().PreserveReferences();
cfg.CreateMap<Section, SectionEF>().PreserveReferences();
cfg.CreateMap<Tree, TreeEF>().PreserveReferences();
});
var mapper = config.CreateMapper();
var forest = new Forest();
var section = new Section();
var tree = new Tree();
forest.Trees.Add(tree);
forest.Sections.Add(section);
section.Trees.Add(tree);
var result = mapper.Map<Forest, ForestEF>(forest);
Console.WriteLine(object.ReferenceEquals(result.Trees[0], result.Sections[0].Trees[0]));
Console.ReadLine();
}
}
public class Forest
{
public IList<Tree> Trees { get; set; } = new List<Tree>();
public IList<Section> Sections { get; set; } = new List<Section>();
}
public class Section
{
public IList<Tree> Trees { get; set; } = new List<Tree>();
}
public class Tree
{
}
public class ForestEF
{
public IList<TreeEF> Trees { get; set; } = new List<TreeEF>();
public IList<SectionEF> Sections { get; set; } = new List<SectionEF>();
}
public class SectionEF
{
public IList<TreeEF> Trees { get; set; } = new List<TreeEF>();
}
public class TreeEF
{
}
I believe that if you don't want duplicates here, both children must not only reference the ID but also the specific instance in memory so EF knows it should be the same record (navigation property). Otherwise, you have to save the parent record first and then assign the key to each child after the fact. If this isn't a GUID but an auto generated id, then you probably need to use the same reference.
I have a data class with nested Lists. A simplified example (not real code):
public class Movie
{
public Int32 TVIndex;
public string MovieName;
public string MovieRating;
public string MovieRuntime;
public List<Actor> MovieActors;
public List<MovieArt> MovieImages;
}
public class Actor
{
public string ActorName;
public string ActorRole;
}
public class MovieArt
{
public string ImagePath;
}
List<Movie> myMovies = new List<Movie>();
This may contain many movies and each movie may contain many Actors and many MovieArt.
I pass this list to a web service that inserts the records into tables using DataEntityModel.
Something Like..
public void InsertMovies(List<Movie> request)
{
using (TransactionScope scope = new TransactionScope())
{
using (MyMovieStorageEntities DbContext = new MyMovieStorageEntities())
{
foreach (Movie m in request)
{
Movies movie = new Movies();
movie.MovieName = m.MovieName;
movie.MovieRating = m.MovieRating;
movie.MovieRuntime = m.MovieRuntime;
DbContext.DBMovies.Add(movie);
foreach (Actor a in m.MovieActors)
{
Actors actor = new Actors();
actor.ActorName = a.ActorName;
actor.ActorRole = a.ActorRole;
DbContext.DBActors.Add(actor);
}
for (MovieArt i in m.MovieImages)
{
MovieArt artwork = new MovieArt();
artwork.FileName = i.FileName;
DbContext.DBMovieArt.Add(artwork);
}
}
DbContext.SaveChanges();
}
}
}
In the Movie table is an Identity column and I need to return the identity for each inserted record for use in the client app.
I am new to DataEntity and not really sure where to start. All the searching I have done hasn't really helped. Any pointers?
Don't lose the references to your entity framework objects (e.g. add them to a list), and after SaveChanges(), the object property containing the primary key will be automatically updated with the ID.
See here for more info
How can I get Id of inserted entity in Entity framework?
EF automatically returns the ID. movie.ID gives you the last inserted ID you can use it or return it from your method to use somewhere else.
I have sets of entities all of them are derived from abstract class
public abstract class NamedEntity : INamedEntity
{
#region Public Properties
public string Description { get; set; }
public string Id { get; set; }
public string Name { get; set; }
#endregion
}
When I persist all entities I want to use Name field as a key, so I override DocumentKeyGenerator and provide such implementation:
store.Conventions.DocumentKeyGenerator = entity =>
{
var namedEntity = entity as NamedEntity;
if (namedEntity != null)
{
return string.Format("{0}/{1}", store.Conventions.GetTypeTagName(entity.GetType()), namedEntity.Name);
}
return string.Format("{0}/", store.Conventions.GetTypeTagName(entity.GetType()));
};
It works fine when I persist the list of entities for the first time, but if I want to persist them again I get an exception
PUT attempted on document 'xxxxx' using a non current etag
I just started using RavenDB, so I cannot understand what I am doing wrong?
Just a guess, but it's probably not with your key generation, but how you are storing them.
On first usage you probably have something like:
var myEntity = new MyEntity(...);
session.Store(myEntity);
...
session.SaveChanges();
That part is fine, but on subsequent usage, you should not be doing the same thing. Instead, it should be more like this:
var myEntity = session.Load<MyEntity>("myentities/foobar");
myEntity.Something = 123;
...
session.SaveChanges();
Note there is no call to .Store() when making changes. This is because the entity is "tracked" by the session, and all changes to it are automatically persisted when you call .SaveChanges()
Short version:
Is it possible in NHibernate to create a mapping for a class that has no corresponding table in the database and specify for each property where the data should come from?
Long version:
I have the following class:
public class TaxAuditorSettings
{
private readonly IList<Month> _allowedMonths = new List<Month>();
private readonly IList<Company> _allowedVgs = new List<Company>();
public IList<Month> AllowedMonths
{
get { return _allowedMonths; }
}
public IList<Company> AllowedVgs
{
get { return _allowedVgs; }
}
}
The class Company is a normal entity that is mapped to a table.
The class Month is a simple class without ID or existing mapping (Constructor and error checking removed for brevity):
public class Month
{
public int MonthNumber { get; set; }
public int Year { get; set; }
}
My database has the following two tables:
Table TAX_AUDITOR_ALLOWED_COMPANIES has only one column COMPANY_ID that is a FK to the table COMPANY and has a UNIQUE index.
Table TAX_AUDITOR_ALLOWED_MONTHS has two columns MONTH_NUMBER and YEAR. There is a UNIQUE index spanning both columns.
I would like to map TaxAuditorSettings such that I can ask my NHibernate session for an object of this type and NHibernate then should put the contents of TAX_AUDITOR_ALLOWED_MONTHS into the list TaxAuditorSettings.AllowedMonths and the companies referenced in TAX_AUDITOR_ALLOWED_COMPANIES into the list TaxAuditorSettings.AllowedCompanies.
Is this even possible? If so, how? If not, how would you do it instead?
Please note: I can change the database if necessary.
not quite what you requested for but here goes
public TaxAuditorSettings GetAuditorSettings(ISession session)
{
// assuming there is a ctor taking the enumerables as parameter
return new TaxAuditorSettings(
session.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>(),
session.CreateCriteria<Company>()
.Add(NHibernate.Criterion.Expression.Sql("Id IN (SELECT COMPANY_ID FROM TAX_AUDITOR_ALLOWED_COMPANIES)"))
.Future<Company>())
}
class MonthResultTransformer : IResultTransformer
{
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
return new Month
{
MonthNumber = (int)tuple[0],
Year = (int)tuple[1],
}
}
}
Update: saving
public void SaveOrUpdate(ISession session, TaxAuditorSettings settings)
{
using (var tx = session.BeginTransaction())
{
// get whats in the database first because we dont have change tracking
var enabledIds = session
.CreateSqlQuery("SELECT * FROM TAX_AUDITOR_ALLOWED_COMPANIES")
.Future<int>();
var savedMonths = session
.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>();
foreach (var id in settings.AllowedVgs.Except(enabledIds))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_COMPANIES Values (:Id)")
.SetParameter("id", id).ExecuteUpdate();
}
foreach (var month in settings.AllowedMonths.Except(savedMonths))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_MONTHS Values (:number, :year)")
.SetParameter("number", month.Number)
.SetParameter("year", month.Year)
.ExecuteUpdate();
}
tx.Commit();
}
}
Note: if you can change the database it would be much easier and performant to sanitise the tables
I would do it this way.
public class MonthMap : ClassMap<Month>{
public MonthMap(){
CompositeId()
.KeyProperty(x=>x.MonthNumber,"MONTH_NUMBER")
.KeyProperty(x=>x.Year);
Table("TAX_AUDITOR_ALLOWED_MONTHS");
}
}
Add a column to the COMPANY table called TaxAuditable and map it to a bool property. Update the column to be 1 where a matching row is found in TAX_AUDITOR_ALLOWED_COMPANIES. Then remove the table TAX_AUDITOR_ALLOWED_COMPANIES as it serves no real purpose.
Now you have a Company with an appropriate property on it you can query Company's where TaxAuditable is true and pass them into a method along with the Months to do your work/calculations etc... Something like this perhaps?
var taxAuditableCompanies = session.QueryOver<Company>()
.Where(x=>x.TaxAuditable==true).Future();
var months=session.QueryOver<Month>().Future();
var myService = new MyService();
myService.DoSomeWork(taxAuditableCompanies, months);
i defined an entity called Variable and derived classes by using Table Per Hierarchy (TPH). The Base class "Variable" contains a collection of PropertyValues:
private ICollection<PropertyValue> propertyValues;
public const string DiscriminatorColumn = "Discriminator";
public const string Table = "Variables";
public VariableType VariableType { get; set; }
public string Name { get; set; }
[NotMapped]
public string Discriminator { get; set; }
public virtual ICollection<PropertyValue> PropertyValues
{
get { return this.propertyValues ?? (this.propertyValues = new ObservableCollection<PropertyValue>()); }
set { SetProperty(ref this.propertyValues, value, () => PropertyValues); }
}
Now, i want to derive a SpecialVariable class (or more than one), which define some SpecialProperties (e.g. HighLimit) which should be mapped to an entry in the PropertyValues (table).
public class MySpecialVariabe : Variable
{
public double HighLimit { get; set; }
}
My OnModelCreating function looks currently like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Variable>().HasKey(x => new { x.Id });
modelBuilder.Entity<Variable>()
.Map<MySpecialVariabe>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe).Name))
.Map<MySpecialVariabe2>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe2).Name)).ToTable(Variable.Table);
}
Can someone give me some tips how to realize this, without writing tons of bad looking code in the derived class. (Performance is not that important.)
best regards,
Chris
You can't map properties to records. That is how I understand your question. You have some PropertyValues table which is most probably some Key/Value pair and you want to map entity properties as records (data) to this table. This is not something which EF do for you. You must provide not mapped properties which will work with correct record in propertyValues collection.
Something like:
[NotMapped]
public double HighLimit
{
get
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
return current != null ? current.Value : 0.0;
}
set
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
if (current != null)
{
current.Value = value;
}
else
{
propertyValues.Add(new PropertyValue { Key = "HighLimit", Value = value });
}
}
}
The problem with this approach is that you can't use HighLimit in Linq-to-entities queries - you must always use PropertyValues.
Moreover TPH in EF requires that properties of derived entity (MySpecialVariable) are mapped to the same table as parent entity (Variable). You can't map properties of derived entity into data stored in other table (PropertyValues).