I have the following
public class MainClass {
public int Id { get;set; }
public virtual ICollection<SubClass> SubClasses { get;set; }
}
public class SubClass {
public int MainClassId { get;set; }
public virtual MainClass MainClass { get;set; }
}
and i have setup mapping for one to many. The problem i have is when i do this :
var subClass = new SubClass();
subClass.MainClassId = 1;
_dbset.SaveChanges(subClass);
//subClass.MainClass is null after save
i will need to call my get function with id=1 only i can get the MainClass entity. Anyone has any idea whats the issue causing this?
You should add subClass to the mainClass's collection of SubClasses and then save changes.
So like,
var mainClass = _dbset.MainClasses.Single(x => x.id == mainClassId);
var subClass = new SubClass();
//populate subClass without setting mainclassId.
mainClass.SubClasses.Add(subClass);
_dbset.SaveChanges();
The following would work:
public class MainClass {
public int Id { get;set; }
public virtual ICollection<SubClass> SubClasses { get;set; }
}
public class SubClass {
public int Id { get;set; }
[ForeignKey("MainClass")]
public int MainClassId { get;set; }
public virtual MainClass MainClass { get;set; }
}
If you used code-first approach, you might be missing modelbuilder for 1:M relationship.
In your Context in method OnModelCreating you need to have something like this.
modelBuilder.Entity<MainClass>()
.HasMany(e => e.SubClass)
.WithRequired(e => e.MainClass)
.WillCascadeOnDelete();
Next thing that comes to my mind is that you might want to use include in your get method to specify that you need to load all subclasses for main class
context.MainClass.Include(x => x.SubClass).ToList();
or use some other method to load joined data. Link
I hope that will help you.
Related
In a .NET Core 2.1 library I need to access to a MySQL database organized in multiple schemas with tables that can have the same name across those schemas. I can't make any changes to the DB since it comes from another company.
For most of the tables I need a read-only access and I'd like to use a single EF Core DbContext.
Actually I get this error message during initialization:
InvalidOperationException: Cannot use table 'tbl_panel' for
entity type 'Db2Panels' since it is being used for entity
type 'Db1Panels' and there is no relationship between their
primary keys.
I think that the crux of the matter mainly resides in the configuration methods, which should be called not just once but N times, one for each instance of the entity with different schema (db_machine_1.tbl_panel, db_machine_2.tbl_panel, etc.).
How can I reach my goal?
This is my actual implementation.
Database schemas
// db_machine_1 schema
db_machine_1.tbl_panel
db_machine_1.tbl_basket
db_machine_1.tbl_unit
// db_machine_2 schema
db_machine_2.tbl_panel
db_machine_2.tbl_basket
db_machine_2.tbl_discard
// Other db_machine_X schemas with similar structure...
DbContext configuration
public class MyDbContext : DbContext
{
// Schema: db_machine_1
public DbSet<Panel> Db1Panels { get; set; }
public DbSet<Basket> Db1Baskets { get; set; }
public DbSet<Unit> Db1Units { get; set; }
// Schema: db_machine_2
public DbSet<Panel> Db2Panels { get; set; }
public DbSet<Basket> Db2Baskets { get; set; }
public DbSet<Discard> Db2Discards { get; set; }
// Other schemas DbSet<X> objects...
// Arrays to access the specific DbSet by using the schema number:
// Panels[1] -> Db1Panels, Panels[2] -> Db2Panels, ...
public DbSet<Panel>[] Panels { get; }
public DbSet<Basket>[] Baskets { get; }
// Other arrays for other DbSet<X> objects...
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
// Arrays initialization
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add(Db1Panels);
dbPanelList.Add(Db2Panels);
Panels = dbPanelList.ToArray();
List<DbSet<Basket>> dbBasketList = new List<DbSet<Basket>>();
dbBasketList.Add(Db1Baskets);
dbBasketList.Add(Db2Baskets);
Baskets = dbBasketList.ToArray();
// Initialization for other DbSet<X> objects...
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations<MyDbContext>();
modelBuilder.ApplyAllConversions();
}
}
Objects
public class Panel
{
public long Id { get; set; }
public string SN { get; set; }
// Other properties...
}
public class Basket
{
public long Id { get; set; }
public string Description { get; set; }
// Other properties...
}
Configurations
public class PanelConfiguration : IEntityTypeConfiguration<Panel>
{
public void Configure(EntityTypeBuilder<Panel> builder)
{
builder.ToTable("tbl_panel");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.SN)
.HasColumnName("Serial")
.HasMaxLength(20);
// Other properties configuration...
}
}
public class BasketConfiguration : IEntityTypeConfiguration<Basket>
{
public void Configure(EntityTypeBuilder<Basket> builder)
{
builder.ToTable("tbl_basket");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.Description)
.HasColumnName("Desc")
.HasMaxLength(100);
// Other properties configuration...
}
}
// Other IEntityTypeConfiguration implementations for other tables...
// This extension method is used to automatically load all Configurations
// of the various entities
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(T).Assembly
.GetTypes()
.Select(t => (t, i: t.GetInterfaces().FirstOrDefault(i => i.Name.Equals(typeof(IEntityTypeConfiguration<>).Name, StringComparison.Ordinal))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], cfgObj: Activator.CreateInstance(it.t)))
.Select(it => applyConfigurationMethodInfo.MakeGenericMethod(it.et).Invoke(modelBuilder, new[] { it.cfgObj }))
.ToList();
}
}
UPDATE about base class arrays
After creating base abstract classes and derived ones, I'd like to merge all the derived class objects into a single array to be able to access the specific DbSet by using the schema number. See also above code of DbContext constructor.
I'm having problems with casting...
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add((DbSet<Panel>)Db1Panels.Select(g => g as Panel)); // NOT WORKING! Cast Exception
dbPanelList.Add((DbSet<Panel>)Db2Panels.Cast<DbSet<Panel>>()); // NOT WORKING! Cast Exception
Panels = dbPanelList.ToArray();
Is this possible somehow?
I think you can't get away from having two different EF objects for the different tables, and you probably shouldn't as they may diverge at some point in the future.
At a minimum you need two classes Db1Panel and Db2Panel . I assume that actually the "Db" prefix is meant to meant a different schema, not actually a different database.
However that shouldn't be a big problem as there are other ways within C# of making them behave in similar fashions. Two options that spring to mind are having them inherit from the same base class, or have them implement an interface:
public abstract class PanelBase
{
public long Id { get; set; }
// other properties
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : PanelBase{}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : PanelBase{}
If you chose to implement the interface you would need to repeat the properties in each class, but refactoring tools make this quite easy.
public interface IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : IPanel
{
public long Id { get; set; }
}
Or depending on the size of your application you could consider having another namespace of domain objects and just map the database objects into it:
You should be able to use the Table attribute. There's a parameter Schema that allows you to set the schema name. See here for documentation. In your case you'd get something like
[Table("Table1", Schema="Schema1")]
public class Entity1Schema1
{
public string Property1 {get;set;}
}
[Table("Table1", Schema="Schema2")]
public class Entity1Schema2
{
public string Property1 {get;set;}
}
And then of course you can use interfaces or base classes to refactor your code as #ste-fu already mentioned.
I've a system with several self referencing entities with one-to-many relationship (parent-child). I'd like to use the common base class for all of those entities:
public class SelfReferencing
{
public SelfReferencing Parent {get; set;}
public ICollection<SelfReferencing> Children {get; set;}
}
and inherit the particular entity from SelfReferencing.
Fluent API mapping requires the reference Properties to be of the defining type, when trying to do following:
modelBuilder.Entity<ConcreteSelfReferencing>()
.HasMany(e => e.Children)
.WithOptional(e => e.Parent);
So, can you help me to find a possibility to make use of inheritance and get the entities mapped?
THX
Note: The example below is known as Table-Per-Hierarchy (TPH) - i.e. all contained in one table. Click on this link for Table-Per-Type (TPT), which has different tables for each type.
When using base types and inherited types, you have to tell EF how to determine the association for a specific inherited type.
Taking your code:
public abstract class SelfReferencing
{
public SelfReferencing Parent { get; set; }
public ICollection<SelfReferencing> Children { get; set; }
}
public class ConcreteSelfReferencing : SelfReferencing
{
}
EF now has to work out whether the sub-class is a ConcreteSelfReferencing or any other type of sub-class. This is determined by a discriminator on the table itself, to which the column is not part of your mapping.
To take another example, similar to I've used in the past:
public abstract class Policy
{
public int Id { get; set; }
public string PolicyNumber { get; set; }
}
public class InsurancePolicy : Policy
{
}
public class CarPolicy : Policy
{
}
The table was structured like this:
| Id | PolicyNumber | Type | ..... |
1 CAR0001 C
2 ISN0001 I
To get EF to result them correctly, you would have:
public class MyContext : DbContext
{
public MyContext() : base()
{
}
public DbSet<Policy> Policies { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var policyMap = modelBuilder.Entity<Policy>();
// Set up discriminators
policyMap.Map<InsurancePolicy>(p => o.Requires("Type").HasValue("I"))
.Map<CarPolicy>(p => o.Requires("Type").HasValue("C"));
// Notice that `Type` is only used for the discriminators, not an actual
// mapped property
policyMap.HasKey(x=>x.Id);
policyMap.Property(x=>x.PolicyNumber);
}
}
And from your code, you can either do the filtering yourself, or put the filtering in the DbContext. Here is an example from a separate class.
public class PolicyRepository
{
private MyContext context = new MyContext();
public PolicyRepository()
{
}
public IQueryable<InsurancePolicy> GetInsurancePolicies()
{
return this.context.Policies.OfType<InsurancePolicy>();
}
public IQueryable<CarPolicy> GetCarPolicies()
{
return this.context.Policies.OfType<CarPolicy>();
}
}
The general idea starts from creating a control, which would work with any database object.
The database is accessed via LINQ to SQL, and database objects are auto-generated from the existing database.
To make the control work with any of these objects, I use a base interface, which all the auto-generated objects inherit from. So the control actually works with that interface.
This interface at first should provide:
an ability to get the identifier (primary key) of an object
an expression which would return this identifier, if compiled and called via an instance of an object
So this looks like:
public interface IEntity<TEntity, TEntityId>
where TEntity : IEntity<TEntity, TEntityId>
where TEntityId : IEquatable<TEntityId>
{
Expression<Func<TEntity, TEntityId>> GetIdExpression();
TEntityId EntityId { get; }
}
Then a database object's class definition would look like:
//let it be the auto-generated part:
public partial class Entity1
{
public Guid UId { get; set; }
}
//and the manual part:
public partial class Entity1 : IEntity<Entity1, Guid>
{
public Expression<Func<Entity1, Guid>> GetIdExpression()
{
return (Expression<Func<Entity1, Guid>>)(se => se.UId);
}
public Guid EntityId
{
get { return this.UId; }
}
}
And we can test it:
var e1 = new Entity1();
e1.UId = Guid.NewGuid();
Console.WriteLine(e1.UId);
Console.WriteLine(e1.GetIdExpression().Compile()(e1));
The output of these two lines is equal. It's OK.
The next idea was, that the lion share of all database objects have int identifier with the name Id. And it would be much better to avoid editing every of them, i.e. not to write one and the same code implementing IEntity<TEntity, TEntityId>.
As far as I need to specify the implementation of this interface, I need a class.
For now I ended up with
public class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public int Id { get; set; }
public Expression<Func<TEntity, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
public int EntityId
{
get { return this.Id; }
}
}
Then the database object class would derive from it:
//let it be the auto-generated part:
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
}
Already now it's obvious, that Id in Entity2 does hide the Id in IntEntity<TEntity>. This is also proved by the same test:
var re = new Entity2();
re.Id = 5;
Console.WriteLine(re.Id); //5
Console.WriteLine(re.GetIdExpression().Compile()(re)); //0
So this solution is wrong...
I also tryed to make IntEntity<TEntity> abstract, but obviously failed, as in Entity2 Id is defined in the auto-generated part, and I could not make it override the abstract Id of the base class.
Is there anything I can do to make all database objects with int Id use the Id from the base IntEntity<TEntity> class. Or, probably, vice versa...
The main point here is that the expression returned by GetIdExpression should have the form e => e.Id, as this expresion would later be used in LINQ to SQL queries, so LINQ should be able to translate it to a correct SQL query.
I'm not sure that's exactly what you want, but you can try to do so
public abstract class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public abstract Expression<Func<TEntity, int>> GetIdExpression();
public abstract int EntityId
{
get;
}
}
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
public override int EntityId
{
get { return this.Id; }
}
public override Expression<Func<Entity2, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
}
in fact without override virtual you can't get access to field in derive class from method in base class
in sample above in fact equals your first sample
UPDATE
as workaround you can add method SetId that set id in derive and base class like this
public class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public int Id { get; set; }
public Expression<Func<TEntity, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
public int EntityId
{
get { return this.Id; }
}
}
//let it be the auto-generated part:
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
public void SetId(int id)
{
Id = id;
base.Id = id;
}
}
var re = new Entity2();
re.SetId(5);
Console.WriteLine(re.Id); //5
Console.WriteLine(re.GetIdExpression().Compile()(re)); //5
I'm trying to create a reusable method like this
public static void Order<T> (List<T> filteredList, List<T> fullList)
{
//Getting list of ID from all business entities.
HashSet<long> ids = new HashSet<long>(filteredList.Select(x => x.ID));
//Ordering the list
return fullList.OrderByDescending(x => ids.Contains(x.ID)).ThenBy(x => !ids.Contains(x.ID)).ToList();
}
because I have multiple objects that do the same thing but they are differents collections types. But obviously the problem is on x.ID because ID is a property from the business entity. I mean. Imagine that T is Person and ID is the property. But ID is not recognized from a generic list and I want to do it generic because all my business entities have ID (Person, Employee, etc).
Any help please?
Thanks in advance.
L.I.
You could create an Interface, in this example IBusinessEntity, that states the item must have an ID like this:
public interface IBusinessEntity
{
public int ID { get; set; }
}
So, your Person and Employee classes would then change to:
public class Person : IBusinessEntity
{
public int ID { get; set; }
// ...
}
public class Employee : IBusinessEntity
{
public int ID { get; set; }
// ...
}
and then you would only allow Business Entities (in this example Person and Employee) to be passed in like so:
public static void Order<IBusinessEntity> (List<IBusinessEntity> filteredList, List<IBusinessEntity> fullList)
{
//Getting list of ID from all business entities.
HashSet<long> ids = new HashSet<long>(filteredList.Select(x => x.ID));
//Ordering the list
return fullList.OrderByDescending(x => ids.Contains(x.ID)).ThenBy(x => !ids.Contains(x.ID)).ToList();
}
This of course would also allow you to create Mock IBusinessEntity and Unit Test this method.
Thanks for your quick answer. I'd really appreciate it. Well I saw your code and I think is terrific! I did a little app to test it and it has some changes because an interface does not allow me to define public the property and the type in Order shows me a conflict of type IBusinessEntity so I declared it Order T besides that, it's great. Finally this is the last result.
public interface IEntity
{
int id { get; set; }
}
public class Person: IEntity
{
public int id { get; set; }
}
public class Employee : IEntity
{
public int id { get; set; }
}
public static List<IEntity> Order<T>(List<IEntity> filtered, List<IEntity> full)
{
HashSet<int> ids = new HashSet<int>(filtered.Select(x => x.id));
return full.OrderByDescending(x => ids.Contains(x.id)).ThenBy(x => !ids.Contains(x.id)).ToList();
}
Thank you.
L.I.
Help me, please, solve one problem.
I have project, which uses Nhibernate and Fluent Nhibernate. There I created one base class
(it is not real classes, but they describe my situation):
public class Document
{
public virtual int Id { get; private set; }
public virtual Account Acc { get; private set; }
}
And mapping for it:
public class DocumentMap: ProfileEntityMap<Document>
{
public DocumentMap()
{
Id(m => m.Id);
References(m => m.Acc);
DiscriminateSubClassesOnColumn("Type");
}
}
Then I implemented subclass:
public class PaymentDocument: Document
{
public virtual Card AccountCard { get; set;}
}
Mapping for class PaymentDocument:
public class PaymentDocumentMap : SubclassMap<PaymentDocument>
{
public PaymentDocumentMap()
{
References(t => t.AccountCard);
}
}
And after that I try execute this query:
payments = session.Query<PaymentDocument>()
.Fetch(t => t.Acc)
.Fetch(t => t.AccountCard)
.ToList();
And when I insert first fetch I get next exception:
Object reference not set to an instance of an object.
Can somebody answer me where is a problem?
Actually it was a bug fixed in 3.0.0.Alpha2. Right now it works with the trunk.