I have two classes, with a 1 - 1..* relationship.
class A
{
public long Id;
public virtual ICollection<B> Bees;
}
class B
{
public long Id;
public A MyA;
}
Now, I'm trying to get my database to behave so that any instance of A has to have at least one B in its ICollection, with no upper limit. Any row of B should have a foreign key reference to an A row. My mapping looks like this:
public AMapping()
{
this.ToTable("A");
this.HasKey(x => x.Id);
}
public BMapping()
{
this.ToTable("B");
this.HasKey(x => x.Id);
this.HasRequired(x => x.MyA)
.WithMany(x => x.Bees);
}
The expected behavior when I do this:
var aaaaah = new A();
aaaaah.Bees = null;
MyDbContext.A.Add(a);
Should be an exception being thrown. But Entity Framework inserts with no complaints. What did I do wrong in my mapping?
Edit: I have made an ugly temporary solution to the problem by putting a [System.ComponentModel.DataAnnotations.Required] annotation over the Bees property. But this only checks if Bees is null or not, not if it's an empty list if it is instantiated.
There's no real native way to accomplish this with EF or SQL, i.e. you can't create an A without a B, and you can't create a B without an A. Stalemate.
You'll need to create some logic in your business layer to enforce the constraint. E.g.
class A
{
...
public A(B b)
{
this.Bees = new List<B>();
this.Bees.Add(b);
}
}
class B
{
...
public B(A a)
{
this.MyA = a;
}
}
N.B. Presuming code-first, if db-first make your customisations in a partial class.
Related
For example, I've implemented two classes like these:
public class A
{
public List<C> Items { get; set; }
}
public class B
{
public IImmutableList<C> Items { get; set; }
}
public class C
{
}
When I try to map A to B and vice versa, I get an exception because List<string> cannot be converted to IImmutable<string>.
Probably I could provide a mapping for A<->B, but since it'll be a very common pattern in my solution, I'd like to avoid to manually mapping each class that may fall into the same case.
Is there anyway I can generalize the whole mapping using generic type definitions from a collection type to another collection type?
This is what I want to avoid
mapperConfig.CreateMap<A, B>()
.ForMember(a => a.Items, opts => opts.Ignore())
.AfterMap
(
(source, target) =>
{
target.Items = source.Items.ToImmutableList();
}
);
I have two database tables, one to hold completed items and another to hold incomplete items. Both tables are identical in structure. There are some cases where I would like to quest just one of these tables, but other cases where I would want to query the concatenation of both tables.
Classes
public abstract class SomeBase
{
public int Id {set;get;}
public string Item1 {set;get;}
public string Item2 {set;get;}
}
public class A : SomeBase
{
}
public class B : SomeBase
{
}
Mapping (Fluent API)
public class SomeDatabase : DbContext
{
public DbSet<A> As {set;get;}
public DbSet<B> Bs {set;get;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ATable", "SomeSchema");
}
modelBuilder.Entity<B>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("BTable", "SomeSchema");
}
}
}
Using this setup, I can query both tables just fine individually like so
var db = new SomeDatabase();
var a = db.As;
var b = db.Bs;
When trying to combine the two, I can't quite figure out the syntax. One solution in the answer below involves casting it using .AsEnumerable but that isn't quite what I'm looking for since it evaluates immediately.
db.As.AsEnumerable().Concat<SomeBase>(db.Bs);
db.As.Cast<SomeBase>().Concat(db.Bs.Cast<SomeBase>());
How can I concatenate two derived classes that are identical on the database site?
EDIT:
At the lowest level, I am getting these errors
db.As.Cast<SomeBase>();
Unable to cast the type 'Test.Models.A' to type 'Test.Models.SomeBase'. LINQ to Entities only supports casting Entity Data Model primitive types.
db.As.OfType<SomeBase>();
'Test.Models.SomeBase' is not a valid metadata type for type filtering operations. Type filtering is only valid on entity types and complex types.
Related question: How to combine 2different IQueryable/List/Collection with same base class? LINQ Union and Covariance issues
Simply define a
DbSet<SomeBase> Bases { get; set;}
property to access all instances of the base class. The framework should combine the query the right way (union) to include the instances from both tables.
For more details check out e.g. this article: http://weblogs.asp.net/manavi/archive/2011/01/03/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-3-table-per-concrete-type-tpc-and-choosing-strategy-guidelines.aspx
(You use the TPC inheritance strategy)
maybe there is some more elegant way, but union should do it i guess:
db.As.Select(x => new { x.Id, x.Item1, x.Item2 } )
.Union(db.Bs.Select(x => new { x.Id, x.Item1, x.Item2 }));
if you want to include some fields from As and some fields from Bs then it should look like:
db.As.Select(x => new { x.Id, x.Item1, x.Item2, x.Afield, Bfield = null } )
.Union(db.Bs.Select(x => new { x.Id, x.Item1, x.Item2, AField = null, x.Bfield }));
How about:
var concatenatedSet = db.As.Local.OfType<SomeBase>().Concat(db.Bs.Local.OfType<SomeBase());
I have a database I created using entity code first
In that DB I have a structure similar to the following
class ClassA
{
public virtual int ID {get;set}
public virtual string some_text {get;set}
public virtual ClassB B {get;set}
public virtual ClassC C {get;set}
...
}
class ClassB
{
public virtual int ID {get;set}
public virtual string some_text {get;set}
public virtual string some_values {get;set}
...
}
class ClassC
{
public virtual int ID {get;set}
public virtual string some_text {get;set}
public virtual string some_values {get;set}
...
}
....
Finally I have a context for those objects with all of the interface to query the DB
public class ClassADb : DBContext, IClassADataSource
{
public DBSet<ClassA> As {get;set}
public DBSet<ClassB> Bs {get;set}
public DBSet<ClassC> Cs {get;set}
...
}
When I create the DB and explore it I can see that it was created what seems to be correctly:
In the ClassA_Table I see foreign keys for ClassB_ID, ClassC_ID, etc as well as all of the primitive types encapsulated in ClassA (ints, strings, bools, dates, etc)
Also when performing something along the lines of:
ClassB MyB = new ClassB();
//some code to initialize B
...
Bs.Add(MyB)
ClassC MyC = new ClassC();
//some code to initialize C
Cs.Add(MyC);
ClassA MyA = new ClassA();
A.B = MyB;
A.C = MyC;
...
db.SaveChanges();
I again explore the DB and see in Table_A a new row with references to those B and C objects (row id's corresponding to those objects in the B_table , C_table)
The Problem I am having is that when I do a select from As container , I can retrieve the A object but the nested B and C objects are null
The primitive types are OK (not empty)
Some fixes I tried
The virtual keyword is lazy , so in the constructor of the class accessing the database i did
db.Configuration.ProxyCreationEnabled = false;
But still when doing something along the lines of
A myA = db.As.Find(1);
A.some_text ; // not null
A.B ; //NULL!!!!
A.C ; // NULL
What is causing the entity framework not to fetch the A and B object?
You have to explicitly load related entities explicitly using Include().
db.As.Include("B").Include("C").Where(a => [some condition]);
In newer version of the Entity Framework there is also a wrapper method around this method accepting a lambda expression avoiding the strings.
db.As.Include(a => a.B).Include(a => a.C).Where(a => [some condition]);
Using SauceDB, how do I turn a one-to-many relationship, between say table "A" and table "B" respectively, into a list property (containing B objects) in the class that corresponds to table A? The relationship is represented by a foreign key in table B referring to table A (so that many B records can belong to one A record).
Sauce does not support Linq2SQL style navigation properties. However, there are two supported ways to work around this depending on your requirements.
1) Just do the join in your code
IDataStore dstore = .GetDataStore();
var query = from i in dstore.Query<MyTable>()
join x in dstore.Query<MyTable>() on i.Name equals x.Name
select new { };
2) Another way to do it is as follows, and gives a more Navigation Property Style use. Modify your object definition to contain a list and use an [AdditionalInit]
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
[IgnoredField]
public List<Bar> Bars { get; set; }
[AdditionalInit]
private void LoadBars(IDataStore dstore)
{
Bars = dstore.Query<Bar>().Where(r=> r.Foo = this.ID).ToList();
}
}
That should do what you seek, if you have any more questions let me know.
I found that I could use the AdditionalInit attribute in order to define a hook which gets called as a database object gets initialized. Since this hook can accept a data store, I can deduce the one-to-many relationship right there.
Here's the relevant excerpt of class A:
public class A
{
...
public List<B> Bs { get; private set; }
[AdditionalInit]
public void OnInit(IDataStore dstore)
{
Bs = dstore.Query<B>().Where(b => b.A.Id == Id).ToList();
}
}
Bear in mind I haven't been able to test this code yet.
I have two simple POCO classes; I'm trying to get the MyY property below hydrated with the an instance of Y. I've tried a number of ways to do this, and think I might be missing something obvious or simple.
public class X
{
public int Id { get; set;}
public virtual Y MyY { get; set; }
}
public class Y
{
public int Id { get; set; }
// ...
}
I've turned lazy loading off via this call in my subclass of DbContext's constructor:
Configuration.LazyLoadingEnabled = false;
When retrieving an X I have tried
context.Set<X>.Include("MyY").FirstOrDefault(x => ....);
which did not work. I tried
var result = context.Set<X>.FirstOrDefault(x => ....);
context.Entry(result).Reference("MyY").Load();
which works, but requires two round-trips to the database. I tried
context.Set<X>.Select(x => new { X = x, Y = x.MyY }).FirstOrDefault(x => ...);
which also works, but "weakens" my model (ordinarily projecting to a new type is not so bad, but the "shape" of these EF POCOs works perfectly for the DTOs I'll be sending through WCF later).
I finally tried removing virtual from the MyY property as suggested in an answer to another question, but that had no effect at all.
Finally, I want to use the generic repository pattern. What I have ended up with is the following design, shown in part, which supports explicit-load (not preferred) and eager-load when modified to work properly. How do I modify it to get the single db round-trip eager-load?
public class EFRepository : IRepository
{
public T Get<T>(Specification<T> specification) where T : class, IEntity
{
var result = ApplyEagerLoading(context.Set<T>()).FirstOrDefault(specification.IsMatch);
ApplyPostQueryLoading(new List<T> { result });
return result;
}
// doesn't really seem to work yet...
private DbSet<T> ApplyEagerLoading<T>(DbSet<T> set) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
foreach (var spec in ls.Where(s => !s.ExplicitLoad))
set.Include(spec.PropertyName);
return set;
}
// works, but wrong on so many levels...
private void ApplyPostQueryLoading<T>(IEnumerable<T> entities) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
foreach (var e in entities)
foreach (var spec in ls.Where(s => s.ExplicitLoad))
if (spec.IsCollection)
context.Entry(e).Collection(spec.PropertyName).Load();
else
context.Entry(e).Reference(spec.PropertyName).Load();
}
private readonly IDictionary<Type, IList<LoadSpec>> loadSpecs = new Dictionary<Type, IList<LoadSpec>>();
private class LoadSpec
{
internal string PropertyName;
internal bool ExplicitLoad;
internal bool IsCollection;
}
}
Example uses:
// add a rule to load MyY explicitly
repository.AddLoadRule<X>(x => x.MyY, explicit:true, isCollection:false)
...
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));
// add a rule to load MyY with X
repository.AddLoadRule<X>(x => x.MyY, explicit:false)
...
// x.MyY will be null! Doesn't work!
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));
An Update Based on The Answer:
It turns out my temp code examples lied (those one-liners above). I had actually cached the result of .Include in a local variable but applied the .FirstOrDefault against the .Set<X> not the result of .Include. Here is the fix to ApplyEagerLoading, which mirrors what others have suggested in related questions:
private IQueryable<T> ApplyEagerLoading<T>(IEnumerable<T> set) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
var query = set.AsQueryable();
return ls.Where(s => !s.ExplicitLoad).Aggregate(query, (current, spec) => current.Include(spec.PropertyName));
}
This should work:
X entity = context.Set<X>().Include(x => x.MyY).FirstOrDefault();
IF it doesn't the problem must be elsewhere.
If you need some eager loading strategy check this answer.