Entity Framework 6 TPT, Multiple collections of same type on parent entity - entity-framework

Is there any way to configure this and get it to work in EF? I'd like to use this scenario if possible, but haven't found any way to do this without getting a the error "The DELETE statement conflicted with the REFERENCE constraint"
I have seen suggestions for handling this using inheritance. Such as in this post... However, is this really not possible to configure using the fluent API?
Multiple collections of same type in entity framework
Here is my test case....
public class ToolSet
{
public int Id { get; set; }
public virtual ICollection<Tool> Tools { get; set; }
}
public class Tool
{
public int Id { get; set; }
public virtual ICollection<Fluid> HeavyFluid { get; set; }
public virtual ICollection<Fluid> LightFluid { get; set; }
}
public class Fluid
{
public int Id { get; set; }
public double Density { get; set; }
}
public class ExampleContext : DbContext
{
public DbSet<ToolSet> ToolSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ToolSet>().HasMany(x => x.Tools).WithRequired().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.HeavyFluid).WithOptional().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.LightFluid).WithOptional().WillCascadeOnDelete(false);
}
}
public class SeedDb : DropCreateDatabaseAlways<ExampleContext>
{
public override void InitializeDatabase(ExampleContext context)
{
base.InitializeDatabase(context);
var heavyFluids = new List<Fluid> { new Fluid { Density = 1 }, new Fluid { Density = 2 } };
var lightFluids = new List<Fluid> { new Fluid { Density = .1 }, new Fluid { Density = .2 } };
var toolSet = new ToolSet
{
Tools = new List<Tool>
{
new Tool{HeavyFluid =heavyFluids, LightFluid = lightFluids}
}
};
context.ToolSets.Add(toolSet);
context.SaveChanges();
}
}
[TestClass]
public class UnitTest1
{
[TestInitialize]
public void TestInitialize()
{
Database.SetInitializer(new SeedDb());
}
[TestMethod]
public void TestMethod1()
{
using (var a = new ExampleContext())
{
var toRemove = a.ToolSets.First();
a.ToolSets.Remove(toRemove);
a.SaveChanges();
Assert.IsFalse(a.ToolSets.Any());
}
}
}

am not sure if what you are seeking is possible. you want EF To Deferenciate between two collections of the same type,both optional,one with cascading on delete and the other no.
EntityFramework map Fluid to One Table and only one table,this table has some foreign keys columns to represent the relationship with the the Tool.according to your example you will end up having two foreign keys in the same column to the same type(Tool_Id and Tool_Id1 : one of them will be empty at a time).the Fluent Api is a validation Api ,no more no less.so it can't help accomplishing what you want. I think your best friends are enums and Inheritance as in the link you provided (wich i don't understand why you don't want to use them).
one way i can think now, wich i didn't test and i don't either recommand it even if it works. is having some sort of column that can take one of two values, L or H and work around it in your code to deferentiate between Heavy and Light Tools.
what i said now ,is just an opinion and there might be solutions that i don't know about.let's wait and see.

Related

Is it possible to add foreign key between owned entities in EF Core 6

I am trying to separate my contexts using DDD. I have two domains, Instruments and Advertisements with its aggregate roots (the example is hypothetical). Instrument AR owns many InstrumentPictures and I would like to have that information in the Advertisement domain as well via proxy entity.
To ensure good database integrity it would be better to create foreign key from AdvertisementPicture.Guid to InstrumentPicture.Guid but as far as I know this can be done only through HasOne/HasMany model configuration.
Am I using the owner relationship wrong?
(Note: I do not want to configure the FK with custom sql migration.)
Instrument AR:
public class Instrument
{
protected Instrument()
{
}
public Instrument(string name, IEnumerable<InstrumentPicture> pictures)
{
Name = name;
_instrumentPictures.AddRange(pictures);
}
protected List<InstrumentPicture> _instrumentPictures = new List<InstrumentPicture>();
public IReadOnlyCollection<InstrumentPicture> InstrumentPictures
=> _instrumentPictures.AsReadOnly();
public Guid Guid { get; private set; }
public string Name { get; private set; }
}
InstrumentPicture owned collection:
public class InstrumentPicture
{
protected InstrumentPicture()
{
}
public InstrumentPicture(Guid guid, string url)
{
Guid = guid;
Url = url;
}
public Guid Guid { get; set; }
public string Url { get; set; }
public DateTime Created { get; set; }
}
Advertisiment AR
public class Advertisement
{
protected Advertisement()
{
}
public Advertisement(Guid instrumentGuid, string name, IEnumerable<AdvertisementPicture> pictures)
{
InstrumentGuid = instrumentGuid;
Name = name;
_advertisementPictures.AddRange(pictures);
}
protected List<AdvertisementPicture> _advertisementPictures = new List<AdvertisementPicture>();
public IReadOnlyCollection<AdvertisementPicture> AdvertisementPictures
=> _advertisementPictures.AsReadOnly();
public Guid Guid { get; private set; }
public Guid InstrumentGuid { get; private set; }
public string Name { get; private set; }
}
AdvertisementPicture proxy
public class AdvertisementPicture
{
protected AdvertisementPicture()
{
}
public AdvertisementPicture(Guid guid, string url)
{
Guid = guid;
Url = url;
}
public Guid Guid { get; set; }
public string Url { get; set; }
}
Model configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Instrument>()
.HasKey(e => e.Guid);
modelBuilder.Entity<Instrument>()
.OwnsMany(e => e.InstrumentPictures, pic =>
{
pic.HasKey(e => e.Guid);
});
modelBuilder.Entity<Advertisement>()
.HasKey(e => e.Guid);
modelBuilder.Entity<Advertisement>()
.HasOne<Instrument>()
.WithMany()
.HasForeignKey(e => e.InstrumentGuid);
modelBuilder.Entity<Advertisement>()
.OwnsMany(e => e.AdvertisementPictures, pic =>
{
pic.HasKey(e => e.Guid);
// How can I add a foreign key to original InstrumentPicture for database integrity?
});
}
I've been struggling with this for hours and finding lots of answers on SO saying this isn't possible. Turns out this is possible using EntityFrameworkCore so I'll post what I've found on my Top Google Search for this problem.
As soon as you add a foreign key you will find the migration tool attempting to create the table in the second DBContext (unless you add ModelBuilder.Ignore<>() which will either do nothing or ignore your foreign key depending on your order of operations).
You can however do something like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}
This will allow you to reference tables in other DBContext's but exclude any changes to them from the one you're working in. This is outlined in the MS documentation here.
If you have used Fluent API you may still need to apply those configurations in the referencing DB Context. This is easily achieved if you have used the IEntityTypeConfiguration<T> implementation by an additional call to ModelBuilder.ApplyConfigurationsFromAssembly(typeof(T).Assembly);.
In such a use case as above you may find yourself excluding a lot of different entities from your DB context. If you have these defined in their own library like I have to follow a DDD pattern you may find an extension method useful to exclude all of them at once:
public static class ExcludeEntitiesInAssemblyFromMigrationsExtension
{
public static void ExcludeEntitiesInAssemblyFromMigrations(this ModelBuilder builder, Assembly assembly)
{
var assemblyTypes = assembly.GetExportedTypes().Where(t => t.IsClass && !t.IsAbstract);
foreach (var assemblyType in assemblyTypes)
{
var entityBuilder = builder.Entity(assemblyType);
var entityTablename = entityBuilder.Metadata.GetTableName();
if (entityTablename != null)
{
entityBuilder.ToTable(entityTablename, t => t.ExcludeFromMigrations());
}
}
}
}

EntityFramework is naming my mapping table wrong

I have the following Entity class definition:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
Notice that table name is different than the class name. I also have a ClientUsers table which is a many-to-many mapping for clients and users. Problem is, when I try to access the webUser.Clients property I get the following exception:
"Invalid object name 'dbo.ClientWebUsers'."
Looks like Entity Framework is trying to guess the name of the third table, but it apparently was not smart enough to take into account the table attribute that I have there. How can I tell EF that it is ClientUsers and not ClientWebUsers? Also what rule does it follow to know which table name comes first and which one comes second in the new table name? I think it's not alphabetical order.
I'm using EF 5.0. Thanks!
From the looks of things you're using Code First, so I'll answer accordingly. If this is incorrect, please let me know.
I believe the convention being used to determine the name of the many-to-many table is determined by the order in which they occur as DbSet properties in your SomeContext : DbContext class.
As for forcing EntityFramework to name your table whatever you like, you can use the Fluent API in the OnModelCreating method of your SomeContext : DbContext class as follows:
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("SomeDB")
{
}
public DbSet<WebUser> Users { get; set; }
public DbSet<Client> Clients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WebUser>().HasMany(c => c.Clients)
.WithMany(p => p.WebUsers).Map(
m =>
{
m.MapLeftKey("ClientId");
m.MapRightKey("UserId");
m.ToTable("ClientUsers");
});
}
}
This assumes your classes are something like the following:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
public class Client
{
public int Id { get; set; }
public ICollection<WebUser> WebUsers { get; set; }
// more properties
}
Finally, here's an integration test (NUnit) demonstrating the functionality working. You may need to drop your database before running it as Code First should want to update/migrate/recreate it.
[TestFixture]
public class Test
{
[Test]
public void UseDB()
{
var db = new DatabaseContext();
db.Users.Add(new WebUser { Clients = new List<Client> { new Client() } });
db.SaveChanges();
var webUser = db.Users.First();
var client = webUser.Clients.FirstOrDefault();
Assert.NotNull(client);
}
}
Edit: Link to relevant documentation for the Fluent API
Rowan's answer (adding here for reference):
Here is the information on how to configure a many-to-many table (including specifying the table name). The code you are after is something like:
modelBuilder.Entity<WebUser>()
.HasMany(u => u.Clients)
.WithMany(c => c.WebUsers)
.Map(m => m.ToTable("ClientUsers");
~Rowan

EF Code First With Two DbContexts

This should be a simple one involving EF Code first but I can't wrap my head around the documentation and all the examples I am finding are from older versions. I am working with the latest (4.1).
Anyway I have some models like:
public class Foo
{
public int ID { get; set; }
public Bar Bar { get; set; }
}
public class Bar
{
public int ID { get; set; }
public string Value { get; set; }
}
I used some scaffolding with Asp.Net MVC to create my controllers/repositories and when I create a 'Foo' object, it also creates a 'Bar' object even though I set the 'Bar' property from something stored in the database.
public class FooViewModel
{
public int ID { get; set; }
public int BarID { get; set; }
}
public ActionResult Create(FooViewModel foo)
{
var entity = new Foo()
{
ID = foo.ID,
Bar = _barRepository.Find(foo.BarID)
};
_fooRepository.InsertOrUpdate(entity);
_fooRepository.Save();
// more stuff
}
How can I use fluent syntax for EF in order to stop it from creating a new 'Bar' row in the database?
Update
Here is the generated repository code:
public void InsertOrUpdate(Foo foo)
{
if (foo.ID == default(int)) {
// New entity
context.Foo.Add(foo);
} else {
// Existing entity
context.Foo(foo).State = EntityState.Modified;
}
}
public void Save()
{
context.SaveChanges();
}
your _fooRepository and _barRepository need to share same DB context instance. If the are using two instances the Bar will be in added state.
The problem must be somewhere in your repository layer - using the same model directly with EF 4.1 produces the expected result - a new row in the Foos table with a bar FK column pointing to the existing Bar.

ADO EF Code First Generic Intermediate Class Inheritance mapping

I've got the following requirement that works well in the OO space but I can't seem to get it to map back to the DB using ADO EF code first.
I have numrous products each will have different aspects (attributes but not in the sense of code attributes). For instance ring would have aspects such as mineral type = gold etc whilst a diamond would have an aspec of clarity = VVSI1.
As you can see the products very greatly in thier composition and I want a dynamic way of growing my system.
As such I've created a product class:
public class Product
{
public int id { get; set; }
public string Name { get; set; }
private List<ProductAspect> aspects = new List<ProductAspect>();
public List<ProductAspect> Aspects { get { return aspects; } set { aspects = value; } }
}
It has a list of ProductAspect which is the base class for all aspects moving forward:
public class ProductAspect
{
public int id { get; set; }
public string AspectName { get; set; }
}
I then inherit from the ProductAspect using a generic which alows me to be specific (strongly typed) about my Aspect Value:
public abstract class ProductAspect<T> : ProductAspect
{
public T AspectValue { get; set; }
}
I then create some Aspects that will allow me to decorate my product:
public class StringAspect : ProductAspect<string> { };
public class DecimalAspect : ProductAspect<decimal> { };
public class ImageAspect : ProductAspect<byte[]> { };
I then give the DbContext a try and have tried both TPH and TPC inheritance mappings.
Neither seem to work. The DB model that get's generated doesn't create a foriegn key to the StringAspect or DecimalAspect tables from the Aspect Table.
public class IxamDataContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductAspect> Aspects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
AspectMapping(modelBuilder);
}
private void AspectMapping(DbModelBuilder mb)
{
//TPH
//mb.Entity<ProductAspect>()
// .Map<StringAspect>(m => m.Requires("type").HasValue("sa"))
// .Map<DecimalAspect>(m => m.Requires("type").HasValue("da"));
//TPC
//mb.Entity<StringAspect>().ToTable("StringAspect");
//mb.Entity<DecimalAspect>().ToTable("DecimalAspect");
}
}
Resulting in the following exception for this Seeding code:
Product p = new Product();
p.Name = "Diamond";
p.Aspects.Add(new StringAspect() { AspectName = "History", AspectValue = "Old and long" });
p.Aspects.Add(new DecimalAspect() { AspectName = "Weight", AspectValue= 96.5M });
context.Products.Add(p);
context.SaveChanges();
Excpetion:
EntityType 'StringAspect' does not
exist in the EntitySet
'IxamDataContext.Aspects'. Parameter
name: entity
Any ideas from the EF code first pros out there?
Entity framework doesn't support intermediate non mapped types in inheritance hierarchy. It means that you can't have this inheritance: A (mapped) -> B (not mapped) -> C (mapped). EF also doesn't support mapping generic types. It means that you must remove your generic intermediate class from the hierarchy and move AspectValue to derived types with correct type.
Maybe it's to late, but I would offer you using ComplexType attribute it will allows you to extend your types as you wish.

Using Entity Framework 4.0 with Code-First and POCO: How to Get Parent Object with All its Children?

I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}