Including a Model from different DbContext - entity-framework

I am not sure how to achieve the relation between 2 DbContexts. PurchaseOrderDbContext is a Code first approach & AgencyDbContext is an existing database. How can I include the "Division" from AgencyDbContext based on PurchaseOrder DivisionId?
To start off here is a very simplified version of my code.
Purchase Order Model
namespace Website.Models.PurchaseOrders
{
public class PurchaseOrder
{
public int ID { get; set; }
public DateTime OrderDate { get; set; }
public string Name { get; set; }
public int DivisionId { get; set; }
public int StatusID { get; set; }
public Agency.Division Division { get; set; }
}
}
Division Model (this is in a different DbContext)
namespace Website.Models.Agency
{
public class Division
{
public int DivisionId { get; set; }
public string DivisionName { get; set; }
public string DivisionShortName { get; set; }
public string DivisionAbbrev { get; set; }
public int? DivisionDirectorEmpId { get; set; }
}
}
Agency DbContext
namespace Website.Models.Agency
{
public class AgencyDbContext : DbContext
{
public Agency DbContext(DbContextOptions<AgencyDbContext> options) : base(options)
{
}
public virtual DbSet<Division> Division { get; set; }
public virtual DbSet<Section> Section { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
}
PurchaseOrderDbContext
namespace Website.Models.PurchaseOrders
{
public class PurchaseOrderDbContext : DbContext
{
public PurchaseOrderDbContext(DbContextOptions<PurchaseOrderDbContext> options) : base(options)
{}
public DbSet<Status> Statuses { get; set; }
public DbSet<PurchaseOrder> PurchaseOrder { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
}
I get an the error InvalidOperationException: Lambda expression used inside Include is not valid. This is referring to the Include extension on Division.
var purchaseOrder = _context.PurchaseOrder
.Include(p => p.Division)
.Include(p => p.Status)
.OrderByDescending(p => p.OrderDate);
Thank you in advance!

Probably the only way to resolve is to make a query to the first context for items you are looking for, and then populate Division property with entries from second context
public class PurchaseOrderService
{
private readonly PurchaseOrderDbContext purchaseOrderDbContext;
private readonly AgencyDbContext agencyDbContext;
public PurchaseOrderService(PurchaseOrderDbContext purchaseOrderDbContext,
AgencyDbContext agencyDbContext)
{
this.purchaseOrderDbContext = purchaseOrderDbContext;
this.agencyDbContext = agencyDbContext;
}
public PurchaseOrder Get(int id)
{
var purchaseOrder = purchaseOrderDbContext.PurchaseOrder.FirstOrDefault(x => x.ID == id);
if (purchaseOrder == null)
{
return null;
}
purchaseOrder.Division = agencyDbContext.Division.FirstOrDefault(x => x.DivisionId == purchaseOrder.DivisionId);
return purchaseOrder;
}
}

Related

Entity Framework Multiple Inheritance with several discriminators

I want to realize structure like this:
public enum TreeType {
Product = 1,
User = 2,
Document = 3
}
public enum ProductType {
Service = 1,
Ware = 2
}
public enum DocumentType {
Order = 1,
Invoice = 2
}
public abstract class Tree
{
[Key]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[NotMapped]
public TreeType Type { get; set; }
}
public abstract class Product : Tree
{
[Required]
public string Article { get; set; }
[NotMapped]
public ProductType ProductType { get; set; }
public Tree
{
this.Type = TreeType.Product;
}
}
public class User : Tree
{
[Required]
public string Login { get; set; }
[Required]
public string Password { get; set; }
public User
{
this.Type = TreeType.User;
}
}
public abstract class Document : Tree
{
[Required]
public int PageCount { get; set; }
[Required]
public DateTime Created { get; set; }
[NotMapped]
public DocumentType DocumentType { get; set; }
public Document
{
this.Type = TreeType.Document;
}
}
public class Service : Product
{
[Required]
public int VisitCount { get; set; }
public Service
{
this.ProductType = ProductType.Service;
}
}
public class Ware : Product
{
[Required]
public string StorageName { get; set; }
public Ware
{
this.ProductType = ProductType.Ware;
}
}
public class Order : Document
{
[Required]
public string CustomerName { get; set; }
public Order
{
this.DocumentType = DocumentType.Order;
}
}
public class Invoice : Document
{
[Required]
public string SupplierName { get; set; }
public Invoice
{
this.DocumentType = DocumentType.Invoice;
}
}
public class TreeDbContext : DbContext
{
DbSet<Tree> Trees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Tree>().ToTable("L_TREES");
.Map<Product>(x => x.Requires("Type").HasValue((int)TreeType.Product)).ToTable("L_PRODUCTS");
.Map<User>(x => x.Requires("Type").HasValue((int)TreeType.User)).ToTable("L_USERS");
.Map<Document>(x => x.Requires("Type").HasValue((int)TreeType.Document)).ToTable("L_DOCUMENTS");
modelBuilder.Entity<Product>()
.Map<Service>(x => x.Requires("ProductType").HasValue((int)ProductType.Service)).ToTable("L_SERVICES");
.Map<Ware>(x => x.Requires("ProductType").HasValue((int)ProductType.Ware)).ToTable("L_WARES");
modelBuilder.Entity<Document>()
.Map<Order>(x => x.Requires("ProductType").HasValue((int)DocumentType.Order)).ToTable("L_ORDERS");
.Map<Invoice>(x => x.Requires("ProductType").HasValue((int)DocumentType.Invoice)).ToTable("L_INVOICES");
}
}
In database it looks like this:
enter image description here
Two-level inheritance I can implement both through TPH, and through TPT, but multi-level inheritance, and even with several descriptors, I can not implement.
As an exit, I can use inheritance and combination, but the implementation is cumbersome and requires a lot of manual action to support in the future.
I tried to implement this architecture, but I did not succeed.
Does anyone know how I can do this?

Can we mix new table and existing view in EF code first

I created some classes and a table in db for those classes with migration. I tested my code by adding some data to the classes,saved by EF and saw the data saved properly in db.
Later when I created a view in db and maped that view in code the problems started. When I tried to query this way:
using (var db = new TestDBContext())
{
var listMyViews = db.vwCustomer.ToList();
}
I was getting an error message like
Additional information: The model backing the 'TestDBContext' context has changed since the database was created. Consider using Code First Migrations to update the database
So EF is telling me to migrate features to create view in db but view exists already. So why do I need to recreate it by migration?
my full code with view mapping and class
public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
public class Customer : CustomerBase
{
public virtual List<Addresses> Addresses { get; set; }
}
public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public bool IsDefault { get; set; }
public virtual List<Contacts> Contacts { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
public class Contacts
{
[Key]
public int ContactID { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public bool IsDefault { get; set; }
public int AddressID { get; set; }
public virtual Addresses Customer { get; set; }
}
public class vwCustomer
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
}
public class TestDBContext : DbContext
{
public TestDBContext()
: base("name=TestDBContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new vwCustomerConfiguration());
}
public DbSet<Customer> Customer { get; set; }
public DbSet<Addresses> Addresses { get; set; }
public DbSet<Contacts> Contacts { get; set; }
public DbSet<vwCustomer> vwCustomer { get; set; }
}
public class vwCustomerConfiguration : EntityTypeConfiguration<vwCustomer>
{
public vwCustomerConfiguration()
{
this.HasKey(t => t.CustomerID);
this.ToTable("vwCustomer");
}
}
Later I tried to see if any migration is pending issuing Add-Migration "My_vwCustomer". I saw new migration code being added as below. It seems there is no migration pending.
public partial class My_vwCustomer : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.vwCustomers",
c => new
{
CustomerID = c.Int(nullable: false, identity: true),
FirstName = c.String(),
})
.PrimaryKey(t => t.CustomerID);
}
public override void Down()
{
DropTable("dbo.vwCustomers");
}
}
Another way we can do it and it solve my problem. see the code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<YourDbContext>(null);
base.OnModelCreating(modelBuilder);
}
code taken from here https://stackoverflow.com/a/6143116/6188148
we can follow this approach too.
public partial class AddingvwCustomer : DbMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
i guess this will works too but not tested myself.
we can use the Fluent API to configure it using the Ignore method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<MyClass>();
}
thanks

InvalidOperationException: The entity type 'object' requires a key to be defined

Entity-framework 7.
[Table("Settings")]
public class Setting
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public object DefaultValue { get; set; }
public string Classification { get; set; }
public FacilitySettingOverride FacilitySettingOverride { get; set; }
}
[Table("FacilitySettingOverride")]
public class FacilitySettingOverride
{
[Key]
[ForeignKey("FacilityId")]
public int FacilityId { get; set; }
public object Value { get; set; }
[ForeignKey("SettingId")]
public int SettingId { get; set; }
public virtual ICollection<Setting> Settings { get; set; }
}
[Table("Facilities")]
public class Facility
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<FacilitySettingOverride> FacilitySettingOverrides { get; set; }
}
When run the code
private IEnumerable<FacilitySettingOverride> GetFacilityBandwidthSettings
{
get
{
List<FacilitySettingOverride> settingList;
using (SettingDbContext context = new SettingDbContext())
{
settingList = context.FacilitySettingOverride.ToList();
}
return settingList;
}
}
Then get the exception:
InvalidOperationException: The entity type 'object' requires a key to be defined.
The context is:
public class SettingDbContext : DbContext
{
public DbSet<Setting> Settings { get; set; }
public DbSet<FacilitySettingOverride> FacilitySettingOverride { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=13.53.63.74;Initial Catalog=AAAAA;User ID=sa;password=password);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
However if I change the object to string for Value or DefaultValue in POCO then no exception. But the type in the database is sql_variant. I have to use the type object.
It seems that Data annotations are not yet supported so you will need to use the Fluent API for configuration.
See: https://github.com/aspnet/EntityFramework/issues/1913#issuecomment-86662854

EF projections many to many not loading

I have four classes defined as follows:
public class Operator : Base
{
public string Name { get; set; }
public string URL { get; set; }
public ICollection<Address> Addresses { get; set; }
public ICollection<Contact> Contacts { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Address : Base
{
public String Street{ get; set; }
public int? ParentId { get; set; }
public Operator Parent { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Contact : Base
{
public string Name { get; set; }
public int? ParentId { get; set; }
public Operator Parent { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Application : Base
{
[MaxLength(300)]
public String Name { get; set; }
public ICollection<Operator.Operator> Operators { get; set; }
public ICollection<Operator.Address> Addresses { get; set; }
public ICollection<Operator.Contact> Contacts { get; set; }
}
public class Base
{
public int Id { get; set; }
//public int BaseObjectId { get; set; }
TimeZoneInfo _easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
private DateTime _modifiedDate;
public DateTime ModifiedDate
{
get { return this._modifiedDate; }
set
{
this._modifiedDate = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
this._modifiedDate = TimeZoneInfo.ConvertTimeFromUtc(this._modifiedDate, _easternZone);
}
}
private DateTime _createdDate;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate
{
get { return this._createdDate; }
set
{
this._createdDate = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
this._createdDate = TimeZoneInfo.ConvertTimeFromUtc(this._createdDate, _easternZone);
}
}
public bool Disabled { get; set; }
}
public class DB : DbContext
{
public DbSet<EF.Complaint.Complaint> Complaints { get; set; }
public DbSet<EF.Category.Category> Categories { get; set; }
public DbSet<EF.Action.Action> Actions { get; set; }
public DbSet<EF.Medium.Medium> Mediums { get; set; }
public DbSet<EF.Priority.Priority> Priorities { get; set; }
public DbSet<EF.Complaint.Comment> Comments { get; set; }
public DB()
{
this.Database.Log = s => { System.Diagnostics.Debug.WriteLine(s); };
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
modelBuilder.Configurations.Add(new ComplaintConfig());
modelBuilder.Configurations.Add(new CategoryConfig());
modelBuilder.Configurations.Add(new ActionConfig());
modelBuilder.Configurations.Add(new MediumConfig());
modelBuilder.Configurations.Add(new PriorityConfig());
modelBuilder.Configurations.Add(new CommentConfig());
base.OnModelCreating(modelBuilder);
}
}
Operator, Contact and Address can all belong to a particular application. So you could have a structure like this:
Operator 1 - belongs to App1 and App2
Child contact 1 - belongs to App1
Child Contact 2 - belongs to App2
Child Address 1 - belongs to App2
I am trying to build a method that returns a list of Operators for a particular Application and includes Addresses and Contacts of that operator that also belong to that Application
Here is a query I have concocted so far
public IEnumerable<Operator> GetForApp(string name)
{
return (Context.Operators.Where(x => x.Applications.Any(y => y.Name == name))
.Select(x => new
{
x,
Addresses = x.Addresses.Where(y => y.Applications.Any(z => z.Name == name)),
Contacts = x.Contacts.Where(y => y.Applications.Any(z => z.Name == name)),
Applications = x.Applications
}).AsEnumerable()
.Select(n => n.x));
}
This works in a sense that the basic members of Operator get loaded as well as Addresses and Contacts get loaded and filtered correctly...What doesn't get loaded is Applications and I can't figure out why. They only difference I see is that Addresses/Contacts and Operator is many-to-one and Applications and Operator is many-to-many.
You must use lazy loading feature or include your related object directly in your query.
public class Operator : Base
{
public string Name { get; set; }
public string URL { get; set; }
public ICollection<Address> Addresses { get; set; }
public ICollection<Contact> Contacts { get; set; }
// by adding virtual keyword EF could generate proxy classes and
// fetch actual objects when are needed.
public virtual ICollection<Application.Application> Applications { get; set; }
}
Or in your query directly include Application:
public IEnumerable<Operator> GetForApp(string name)
{
return (Context.Operators.Where(x => x.Applications.Any(y => y.Name == name))
.Include(x=>x.Applications)
.Select(x => new
{
x,
Addresses = x.Addresses.Where(y => y.Applications.Any(z => z.Name == name)),
Contacts = x.Contacts.Where(y => y.Applications.Any(z => z.Name == name)),
Applications = x.Applications
}).AsEnumerable()
.Select(n => n.x));
}

Why can't I do ToList()?

I build a model as below. The relationship between Recycler and Account is 1:1.
public class MyContext : DbContext
{
public DbSet<Quoter> Quoters { get; set; }
public DbSet<Account> Accounts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>()
.HasRequired(a => a.RecyclerRef)
.WithRequiredDependent(r => r.AccountRef);
}
}
public class Quoter
{
public int QuoterId { get; set; }
public string Text { get; set; }
}
public class Recycler : Quoter
{
public string Description { get; set; }
public virtual Account AccountRef { get; set; }
}
public class Account
{
public int AccountId { get; set; }
public Recycler RecyclerRef { get; set; }
}
But, I get exceptions when I do the either of these queries:
var query1 = context.Quoters
.OfType<Recycler>()
.Include(r => r.AccountRef)
.Where(r => r.QuoterId == 1)
.ToList();
var query2 = context.Set<Recycler>()
.Include(r => r.AccountRef)
.Where(r => r.QuoterId == 1)
.ToList();
Exception shows that ResultType is “Transient.reference[POCOFirst.Quoter]”,but recommanded is “Transient.reference[POCOFirst.Recycler]”
If I remove the ToList(), it works well. But I need a list as the return value of method.
Why can't I do ToList()? Thanks
It looks like you have stumble upon this bug in EF. Another reference to the bug.
Workaround would be to remove the Include method.