I have a class that tracks relations between owner classes. The tree can potentially be infinite--in the DB, each record has a parent ID that is a self-reference to the same table. The class basically looks like this:
public class ObjectRelation
{
public ObjectRelation(GetObjectParentChildList_Result relation)
{
this.ObjectId = relation.Object_ID;
this.ParentObjectId = relation.Parent_Object_ID;
this.ChildObjects = new List<ObjectRelation>();
}
public int ObjectId { get; set; }
public int? ParentObjectId { get; set; }
public List<ObjectRelation> ChildObjects { get; set; }
}
I'd like a way given a reference to a single instance of this class to end up with a list of every unique ID in the tree in one list to ensure that as a user is entering data they don't create a infinite parent/child loop (ie, IDs 1 & 2 being parents of each other) and it looks as though SelectMany is the way to go. Is such a query feasible in LINQ, or am I stuck writing a separate method to recurse down the whole tree and return the calculated list of IDs once I run out of child nodes?
public class ObjectRelation
{
public ObjectRelation(GetObjectParentChildList_Result relation)
{
this.ObjectId = relation.Object_ID;
this.Parent = relation.Parent_Object;
this.ChildObjects = new List<ObjectRelation>();
}
public int ObjectId { get; set; }
public ObjectRelation Parent { get; set; }
public List<ObjectRelation> ChildObjects { get; set; }
}
public static class ObjectRelationExtensions
{
public static IEnumerable<ObjectRelation> Parents(this ObjectRelation obj)
{
while(obj.Parent!=null)
{
obj = obj.Parent;
yield return obj;
}
}
}
Then to check:
if (x.Parents.Any(p=>p==x)) throw Exception();
or
if (x.Parents.Any(p=>p.ObjectId==x.ObjectId)) throw Exception();
Related
I've been playing with various ways of using generics and have hit a road block.
Consider the following classes:
public abstract class DataElement
{
public int Id { get; set; }
}
public class School : DataElement
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Course : DataElement
{
public int Id { get; set; }
public int SchoolId { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Student : DataElement
{
public int Id { get; set; }
public int CourseId { get; set; }
public string Name { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
Considering a hypothetical scenario where none of this data changes, I'm trying to create a DataDictionary class to house those objects in their respective Lists all within one top-level List property. This crazy idea came to me, when I was writing code to load the different data types from JSON files. I was able to write one load method that could read all three types of data using generics, and that sent me down this particular rabbit hole.
public interface IDataDictionary
{
public List<T> GetAllItemsFromList<T>();
public T GetSingleItemFromList<T>(int id);
}
public class DataDictionary : IDataDictionary
{
public List<IList> Data = new List<IList>();
// Return all objects from the list of type T
public List<T> GetAllItemsFromList<T>()
{
return Data.OfType<T>().ToList(); // This works, returning the appropriate list.
}
// Return specific object from the list of type T by Id property value
public T GetSingleItemFromList<T>(int id)
{
List<T> list = Data.OfType<List<T>>().First(); // This works, resolving to the correct list (e.g. Courses).
return list.Where(i => i.Id == id).First(); // This doesn't work. It doesn't appear to know about the Id property in this context.
}
}
GetAllItemsFromList works fine, returning the appropriate list of items
List<School> newList = GetAllItemsFromList<School>();
However, I am unable to figure out how to return a single item by Id from its respective list.
School newSchool = GetSingleItemFromList<School>(1);
It could be that I'm just trying to be too clever with this, but I can't help but think there is a way to do this, and I'm just missing it.
This doesn't work. It doesn't appear to know about the Id property in this context.
Yes, because you have not specified any constraints to the type parameter T, so it can be anything. There are multiple options:
Create an interface IHaveId and constraint T to it (or use DataElement):
public interface IHaveId
{
int Id { get; set; }
}
public interface IDataDictionary
{
public List<T> GetAllItemsFromList<T>();
public T GetSingleItemFromList<T>(int id) where T : IHaveId; // or where T : DataElement
}
public class DataDictionary : IDataDictionary
{
public T GetSingleItemFromList<T>(int id) where T : IHaveId // or where T : DataElement
{
List<T> list = Data.OfType<List<T>>().First();
return list.Where(i => i.Id == id)
.First();
}
}
add additional parameter to select the id:
public T GetSingleItemFromList<T>(int id, Func<T, int> idSelector)
{
List<T> list = Data.OfType<List<T>>().First();
return list.First(i => idSelector(i) == id);
}
use reflection - I would argue the least recommended option
The problem is that T parameter in function GetSingleItemFromList<T>(int id) could be anything. In order of this to work you should add a constraint:
GetSingleItemFromList<T>(int id) where T: DataElement
I have a problem where I create an object containing a list, load it into my database, run a query that returns the object, but find the list null. All other properties of the object are as they should be. I'm using a list called "Ints" that is filled with a few integers but I've tried using other types.
Here's my model:
public class CourseModel
{
public int CourseModelId { get; set; }
public virtual ICollection<int> Ints { get; set; } // the variable in question
public string Name { get; set; }
public string Overview { get; set; }
}
And here's my database population (The database is called LearnYou):
public class LearnYouDbContextInitializer : DropCreateDatabaseAlways<LearnYouDbContext>
{
protected override void Seed(LearnYouDbContext context)
{
context.Courses.Add(new CourseModel()
{
Name = "C# Programming",
Overview = "You'll learn some C#",
Ints = new List<int> { 1, 42, 3 },
});
context.SaveChanges();
}
}
Here's the controller code for querying the object:
// GET: Course/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CourseModel courseModel = db.Courses.Find(id);
// DEBUGGING THE PREVIOUS LINE SHOWS INTS IS NULL
if (courseModel == null)
{
return HttpNotFound();
}
return View(courseModel);
}
The "Ints" property is not null after saving the context in the database population part but is always null when it's queried (I visit the page ~Edit/1 to debug). I just can't figure out why when all other properties are fine. Any ideas? Thanks.
An ICollection in a model indicates a Parent->Child relationship. However, I doubt EF will be able to determine how to create a child table for an ICollection of integers. Here is what I would do.
Create a new model Ints (or whatever it actually represents):
public class Ints {
public int Value { get; set;}
}
Modify your original model to use it:
public class CourseModel
{
public int CourseModelId { get; set; }
public virtual ICollection<Ints> Ints { get; set; } // See the difference?
public string Name { get; set; }
public string Overview { get; set; }
}
That should make it work.
It Is not working because you are mapping directly to a int primitive type of .net and Entity Framework doesn't allow it.
In this case what you can do is create your onw object for example and sql table like
public class Ints {
{
public Course Course { get; set; }
public int IntValue { ger; set ; }
}
And referencing it from CourseModel
public virtual List<Ints> Ints { get; set; }
I have 3 tables,
1. AttributeTypes (Columns: AttributeId (PK), AttributeName, ..)
2. Location (Columns: locationId (PK), LocationName, ...)
3. LocationAttributeType (Columns: locationId (FK), AttributeId (FK))
Whenever I am trying to insert new location record along with its attribute type from GUI, it should create new record for Table- Location and LocationAttributeType. But EF trying to add new record in Table- AttributeTypes as well, which is just used as reference table and should not add new/duplicate records in it. How can I prevent that?
here is my code,
The model which GUI sends is,
public class LocationDataModel
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Code { get; set; }
[DataMember]
public List<AttributeTypeDataModel> AssignedAttributes = new List<AttributeTypeDataModel>();
}
public class AttributeTypeDataModel
{
protected AttributeTypeDataModel() {}
public AttributeTypeDataModel(int id) { this.Id = id; }
public AttributeTypeDataModel(int id, string name)
: this(id)
{
this.Name = name;
}
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<LocationDataModel> Locations { get; set; }
}
The Entities created by EF are,
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<AttributeType>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<AttributeType> AttributeTypes { get; set; }
}
public partial class AttributeType
{
public AttributeType()
{
this.Locations = new List<Location>();
}
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
I have below code to Add these new location to database,
private IEnumerable<TEntity> AddEntities<TModel, TEntity, TIdentityType>
(IEnumerable<TModel> models, Func<TModel, TIdentityType> primaryKey,
IGenericRepository<TEntity, TIdentityType> repository)
{
var results = new List<TEntity>();
foreach (var model in models)
{
var merged = _mapper.Map<TModel, TEntity>(model);
var entity = repository.Upsert(merged);
results.Add(entity);
}
repository.Save();
return results.AsEnumerable();
}
I am using following generic repository to do entity related operations
public TEntity Upsert(TEntity entity)
{
if (Equals(PrimaryKey.Invoke(entity), default(TId)))
{
// New entity
return Context.Set<TEntity>().Add(entity);
}
else
{
// Existing entity
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
public void Save()
{
Context.SaveChanges();
}
Whats wrong I am doing here?
The DbSet<T>.Add() method attaches an entire object graph as added. You need to indicate to EF that the 'reference' entity is actually already present. There are two easy ways to do this:
Don't set the navigation property to an object. Instead, just set the corresponding foreign key property to the right value.
You need to ensure that you don't load multiple instances of the same entity into your object context. After creating the context, load the full list of AttributeType entities into the context and create a Dictionary<> to store them. When you want to add an attribute to a Location retrieve the appropriate attribute from the dictionary. Before calling SaveChanges() iterate through the dictionary and mark each AttributeType as unchanged. Something like this:
using (MyContext c = new MyContext())
{
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Fish", AttributeTypeId = 1 });
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Face", AttributeTypeId = 2 });
c.SaveChanges();
}
using (MyContext c = new MyContext())
{
Dictionary<int, AttributeType> dictionary = new Dictionary<int, AttributeType>();
foreach (var t in c.AttributeTypes)
{
dictionary[t.AttributeTypeId] = t;
}
Location l1 = new Location(1, "Location1") { AttributeTypes = { dictionary[1], dictionary[2] } };
Location l2 = new Location(2, "Location2") { AttributeTypes = { dictionary[1] } };
// Because the LocationType is already attached to the context, it doesn't get re-added.
c.Locations.Add(l1);
c.Locations.Add(l2);
c.SaveChanges();
}
In this specific case you are using a many-to-many relationship, with EF automatically handling the intermediate table. This means that you don't actually have the FK properties exposed in the model, and my first suggestion above won't work.
Therefore, you either need to use the second suggestion, which still ought to work, or you need to forgo the automatic handling of the intermediate table and instead create an entity for it. This would allow you to apply the first suggestion. You would have the following model:
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<LocationAttribute>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<LocationAttribute> AttributeTypes { get; set; }
}
public partial class LocationAttribute
{
[ForeignKey("LocationId")]
public Location Location { get; set; }
public int LocationId { get; set; }
public int AttributeTypeId { get; set; }
}
public partial class AttributeType
{
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
}
With this approach you do lose functionality since you can't navigate from a Location to an AttributeType without making an intermediate lookup. If you really want to do that, you need to control the entity state explicitly instead. (Doing that is not so straightforward when you want to use a generic repository, which is why I've focused on this approach instead.)
Thank you all for your suggestions.
I have to get rid of my generic repository here to save my context changes and do it manually as below,
private IEnumerable<int> AddLocationEntities(IEnumerable<LocationDataModel> locations)
{
var results = new List<int>();
foreach (LocationDataModel l in locations)
{
var entity = _mapper.Map<LocationDataModel, Location>(l);//you can map manually also
var AttributeCode = l.AssignedAttributes.FirstOrDefault().AttributeTypeId;
using (MyContext c = new MyContext())
{
var attr = c.AttributeTypes.Where(a => a.Id == AttributeTypeId ).ToList();
entity.AttributeTypes = attr;
c.Locations.Add(entity);
c.SaveChanges();
var locid = entity.Id;
results.Add(locid);
}
}
return results;
}
In the else statement of yourUpsert you should add
context.TEntity.Attach(entity);
I have 3 classes inheriting from OwnableSpaceObject that has 2 navigation properties to their children via ICollections.
From the OwnableSpaceObject I want to be able to have a class like AddChild which will add one of the child classes to the ICollection and save it to the database.
Here is the OwnableSpaceObject base class:
public abstract class OwnableSpaceObject : SpaceObject
{
public int? UserId { get; set; }
public int? ResourcesId { get; set; }
[ForeignKey("UserId")]
public virtual UserProfile User { get; set; }
[ForeignKey("ResourcesId")]
public virtual Resources Resources { get; set; }
public virtual ICollection<Structure> Structures { get; set; }
public virtual ICollection<Ship> Ships { get; set; }
}
Here is the method I'm trying to use:
public Structure CheckOrAddChild(StructureType _structureType)
{
using (ChronosContext db = new ChronosContext())
{
var structure = Structures != null ? Structures.FirstOrDefault(x => x.StructureTypeId == _structureType.Id) : null;
if (structure == null)
{
Structure newStructure = new Structure(_structureType.Id);
Structures.Add(newStructure); //this should add the Ship to the database and link it to the parent OwnableSpaceObject right? It errors out right here saying that Structures is null
db.SaveChanges();
structure = newStructure;
}
return structure;
}
}
And similarly an overloaded CheckOrAddChild for adding Ships:
public virtual Ship CheckOrAddChild(ShipType _shipType)
{
using (ChronosContext db = new ChronosContext())
{
var ship = Ships != null ? Ships.FirstOrDefault(x => x.ShipTypeId == _shipType.Id) : null;
if (ship == null)
{
Ship newShip = new Ship(_shipType.Id);
Ships.Add(newShip); //this should add the Ship to the database and link it to the parent OwnableSpaceObject right? It errors out right here saying that Ships is null
db.SaveChanges();
ship = newShip;
}
return ship;
}
}
Here is basically what the Ships and Structures class looks like:
public class Ship
{
public int Id { get; set; }
public int CurrentAmount { get; set; }
public int BuildingAmount { get; set; }
public int ShipTypeId { get; set; }
[ForeignKey("ShipTypeId")]
public virtual ShipType ShipType { get; set; }
}
The Ship/Structure class does not have a navigation property to OwnableSpaceObject because it then crate one huge table for all of my Fleets/Asteroids/Planets because they all inherit from OwnableSpaceObject. I want to keep Fleets/Asteroids/Planets separate in the tables but be able to still attach Ships and Structures to them. Currently EF is making 3 columns in the Ships/Structures tables named "Asteroid_Id", "Planet_Id", and "Fleet_Id" respectively. Should I just make a navigation property for each of these and manually link them up myself? I was trying to avoid this in order to avoid repetitive code.
Anyone have any ideas for this? I've been researching for the past 2 days and I'm about to have a nervous breakdown!
I have to classes, Father and Child (by example)
A snippet of my implementation
Class Father.cs
public class Father
{
public int Id { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
public Father()
{
}
}
Class Child.cs
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public Child()
{
}
}
I'am trying to do something like this
Father f = new Father();
f.Children[0]; // ok
f.Children[1]; // ok
f.Children["John"]; // Duh!
I now, its wrong, i need to implement something in Child Class, i tryed this
public Child this[string name]
{
get
{
return this;
}
}
But this doesnt work.
How can i implement this feature for my class Child?
A List<T> doesn't have a string indexer; you could add one to the Father class, but the usage will be:
var child = parent["Fred"];
(no .Children)
For the indexer itself: Try (in the indexer):
return Children.FirstOrDefault(c=>c.Name==name);
To get an indexer on the list itself, you would have to create a custom list type and add the indexer there.
IMO, a method may be clearer (on Father):
public Child GetChildByName(string name) {...}
Or you can set it up like this:
public class Father
{
public int Id { get; set; }
public string Name { get; set; }
public Children Children { get; set; }
public Father()
{
}
}
public class Children : List<Child>
{
public Child this[string name]
{
get
{
return this.FirstOrDefault(tTemp => tTemp.Name == name);
}
}
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public Child()
{
}
}
Then you call it how you want.
In father class, you could write following code:
public Child this[string name]
{
get
{
return Children.Where(c => c.Name == name);
}
}
and use it like:
Father f = new Father();
f.Children[0]; // ok
f.Children[1]; // ok
f["John"]; // ok!
You are treating the List of Children like a Dictionary (Dictionaries can be accessed by key). Just change your List to a Dictionary and set the string to be the Child's name.
You could make the Children List into an OrderedDictionary so that you can reference it by index or key and then add the objects with the name as the key. Just so you know though, any of these options can run into issues if you have multiple children with the same name.