I need to create a method that will take Linq-to-sql entity and return a list of all it's children(only 1st generation) entitysets, without any data that entitysets contain, just names. Is there any way I can do that? Thanks
Well, if I understand your question correctly, you can inspect the meta-model for a data context instance. The meta-model describes the tables, columns and associations in your model. Basically you want to look at associations on a table where the association is 1-to-many.
This doesn't involve retrieving any data, as you are not actually working with entity instances, just the information that describes them.
This code should do it:
public static string[] GetChildEntities<T>(DataContext context, T entity)
{
var mapping = context.Mapping.GetTable(typeof(T));
return mapping.RowType.Associations.Where(a => a.IsMany)
.Select(a => a.ThisMember.Name).ToArray();
}
This will return the names of any properties that expose the EntitySet instances for the given parent entity.
EDIT
This code finds the first 1->* association between the parent and child entities based on the meta-model, retrieves the value of the EntitySet property on the parent entity, and adds the child entity to that set. This should work for most basic LINQ to SQL implementations.
public static void AddChild<P, C>(DataContext context, P parent, C child)
where P : class
where C : class
{
var parentMapping = context.Mapping.GetTable(typeof(P));
var childAssociation =
parentMapping.RowType.Associations
.Where(a => a.IsMany && a.OtherType.Type == typeof(C))
.FirstOrDefault();
if (childAssociation != null)
{
var entitySet = (EntitySet<C>) childAssociation.ThisMember
.MemberAccessor
.GetBoxedValue(parent);
entitySet.Add(child);
}
}
Related
my problem is that i can't map database function to use it in object property like computed column but for different tables with relation. My relation is SizeItem to Review (one to many)
I have IEntityTypeConfiguration
internal class SizeItemConfiguration : IEntityTypeConfiguration<SizeItem>
{
public const string TableName = "size_items";
public void Configure(EntityTypeBuilder<SizeItem> builder)
{
builder.ToTable(TableName);
builder.Property(p => p.Id)
.UseHiLo("size_item_hilo");
}
}
And want to compute an average rating from Review entities related to a SizeItem.
I tried to find examples to use database functions and EF Core functions but nothing works.
How can I get computed SizeItem.AverageRating for every sql command? May be code will looks like:
builder.Property(p => Functions.Average(p.Reviews.Select(x => x.Rating)));
User-defined function mapping doesn't work for me because I'm using Repository pattern.
Table.linkedIndex is related to LinkedIndex.ID. The value of the field LinkedIndex.TableName is either Linked1 or Linked2 and defines which of these tables is related to a row in Table.
Now i want to make a dynamical link with Yii models so that i can easily get from a Table row to the corresponding Linked1 or Linked2 row:
Table.linkedID = [LinkedIndex.TableName].ID
Example
Table values:
LinkedIndex values:
Now I should get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
Model
In the model Table, I tried to make the relation to the table with the name of the value of linkedIndex.TableName:
public function relations()
{
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
'linked' => array(
self::HAS_ONE,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
)
}
But then I get the error:
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
Is there any way to make a dynamic relation Table.linkedID -> [LinkedIndex.TableName].ID with Yii Models?
Per the Yii docs here:
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail
I'd suggest using self::HAS_ONE instead (unless there can be multiple rows in LinkedIndex with the same ID - although from the looks of above, I doubt that's the case).
You can link tables together that have different keys by following the schema:
foreign_key => primary_key
In case you need to specify custom PK->FK association you can define it as array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').
so in your case:
public function relations(){
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
);
}
where LinkedIndex is the class name for the LinkedIndex model (relative to your Table model - i.e. same folder. You could change that, of course) and array('ID' => 'linkedIndex') specifies the relationship as LinkedIndex.ID = Table.linkedIndex.
Edit
Looking at your updated example, I think you're misunderstanding how the relations function works. You're getting the error
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
because you're trying to create another relation here:
'linked' => array(
self::BELONGS_TO,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
This part: linkedIndex.TableName refers to a new model class linkedIndex.TableName, so Yii attempts to load that class' file linkedIndex.TableName.php and throws an error since it doesn't exist.
I think what you're looking for is to be able to access the value TableName within the table LinkedIndex, correct? If so, that's accessible from within the Table model via:
$this->linkedIndex->TableName
This is made possible by the relation we set up above. $this refers to the Table model, linkedIndex refers to the LinkedIndex relation we made above, and TableName is an attribute of that LinkedIndex model.
Edit 2
Per your comments, it looks like you're trying to make a more complex relationship. I'll be honest that this isn't really the way you should be using linking tables (ideally you should have a linking table between two tables, not a linking table that says which 3rd table to link to) but I'll try and answer your question as best as possible within Yii.
Ideally, this relationship should be made from within the LinkedIndex model, since that's where the relationship lies.
Since you're using the table name as the linking factor, you'll need to create a way to dynamically pass in the table you want to use after the record is found.
You can use the LinkedIndex model's afterFind function to create the secondary link after the model is created within Yii, and instantiate the new linked model there.
Something like this for your LinkedIndex model:
class LinkedIndex extends CActiveRecord{
public $linked;
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return 'LinkedIndex';
}
public function afterFind(){
$this->linked = new Linked($this->TableName);
parent::afterFind();
}
//...etc.
}
The afterFind instantiates a new Linked model, and passes in the table name to use. That allows us to do something like this from within the Linked model:
class Linked extends CActiveRecord{
private $table_name;
public function __construct($table_name){
$this->table_name = $table_name;
}
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return $this->table_name;
}
//...etc.
}
which is how we dynamically create a class with interchangeable table names. Of course, this fails of the classes need to have separate operations done per-method, but you could check what the table_name is and act accordingly (that's pretty janky, but would work).
All of this would result in being to access a property of the linked table via (from within the Table model):
$this->linkedIndex->linked->foo;
Because the value of LinkedIndex.TableName and Table.linkedID is needed to get the values, I moved the afterFind, suggested by M Sost, directly into the Table-Class and changed its content accordingly. No more need for a virtual model.
class Table extends CActiveRecord {
public $linked; // Needs to be public, to be accessible
// ...etc.
public function afterFind() {
$model = new $this->linkedIndex->TableName;
$this->linked = $model::model()->findByPk( $this->linkedID );
parent::afterFind();
}
// ...
}
Now I get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
I am using linq to sql and trying to insert new objects. Here's an example of my code:
public class Farm(){
public List<FarmAnimals> FarmAnimals ();
public string FarmName;
}
Public class FarmAnimal(){
public string name;
}
public void Insert(FarmModel farm)
{
using (var context = new FarmDataClassesDataContext())
{
context.Farms.InsertOnSubmit(new Farm { FarmName = farm.FarmName });
foreach (var animal in farm.FarmAnimals)
{
context.Responses.InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = farm.Id });
}
context.SubmitChanges();
}
}
I get a FK constraint error when it tries to insert a farm animal, referencing the farmID (which equals 0). Since the farm hasn't been inserted yet, it doesn't have an ID for the farmanimals to refer to. How do I get the farm submitted so that the farm animals FK can be properly set?
Thanks,
The problem is you are thinking SQL way, and not ORM way.
The SQL way assigns a foreign key:
InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = farm.Id });
The ORM way assigns entities. Notice the part between ** ** in the following code sample.
var myFarm = new Farm { FarmName = farm.FarmName };
Con...InsertOnSubmit(myFarm)
Con...InsertOnSubmit(new FarmAnimal {name = animal.name, **farm = myFarm**});
Because you assign the entity, proper insertions will be handled and as a bonus in one transaction.
You have to submitChanges before inserting the FarmAnimals, and you need to have the column auto creating the key with autoincrement. Also make sure that the column in the table object in the DBML-file auto updated on insert.
public class Farm(){
public List<FarmAnimals> FarmAnimals ();
public string FarmName;
}
Public class FarmAnimal(){
public string name;
}
public void Insert(FarmModel farm)
{
using (var context = new FarmDataClassesDataContext())
{
Farm newFarm = new Farm { FarmName = farm.FarmName }; <--- New
context.Farms.InsertOnSubmit(newFarm); <---Edited
context.SubmitChanges(); <--- New
foreach (var animal in farm.FarmAnimals)
{
context.Responses.InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = newFarm.Id }); <--- Edited
}
context.SubmitChanges();
}
}
To expand on Pleun's answer: You need to assign entities rather than IDs. The property that you're trying to assign to is mapped to a column with a foreign-key constraint, so it won't work for assigning an entity--to do that you instead need a property that maps to the relationship between two tables. How you do that varies by the tool you're using.
For the purposes of this explanation, I'll assume that you have a Farm table with a primary-key column called ID and another column called Name; and a FarmAnimal table with a foreign-key column named FarmFK that points to the Farm table and another column called Name.
Based on the DataContext part of the name I assume you're using the O/R Designer tool built in to Visual Studio, right? If so, go to the O/R Designer by opening your dbml file, select the association (represented as an arrow) between Farm and FarmAnimal (if there's not already an arrow, select the Association tool from the Toolbox and drag from Farm to FarmAnimal), and view the association's properties. You'll see properties called "Child Property" and "Parent Property". (The parent table is the table with the primary key in the relationship.) Expand those to see the "Name" sub-property of each. Those are the property names you'd use in code to access the two ends of the relationship. Typically they have poorly-chosen names based on the automatic generation, so rename them as needed. In this case let's rename the parent property's name to Animals and the child property's name to 'Farm'. You'd then be able to do the following in your code:
public void Insert(FarmModel farmModel)
{
using (var context = new FarmDataClassesDataContext())
{
var farm = new Farm
{
Name = farmModel.FarmName
};
context.Farms.InsertOnSubmit(farm);
foreach (var animalModel in farmModel.FarmAnimals)
{
var critter = new FarmAnimal
{
Name = animalModel.name,
Farm = farm
}
context.Responses.InsertOnSubmit(critter);
}
context.SubmitChanges();
}
}
Does that answer your need?
My understanding is that find only takes the primary key as the parameter. That works great if the value you are looking for is actually the primary key. In my case, I have a class like this:
public class Chamber
{
[Key]
public int Id {get;set;}
public string ChamberName { get; set; }
}
I want to check whether a given ChamberName exists in either my context or the database itself. How can I do that? Do I have to somehow enumerate of the context myself first, then, look it up in the database with a call like db.Chambers.where(a=>a.ChamberName.equals...?
I can see it working well if ChamberName is my primary key, but it is not.
THanks,
There is a property called Local in the DbSet. You can query that first to find entities loaded to the context.
var entity = db.Chambers.Local.Where(/**/).SingleOrDefault();
if (entity == null)
{
entity = db.Chambers.Where(/**/).SingleOrDefault();
}
You can't use the .Find() method - but how about:
public Chamber FindByChamberName(string chamberName)
{
using(MyDbContext ctx = new MyDbContext())
{
Chamber result = ctx.Chambers
.FirstOrDefault(c => string.Compare(c.ChamberName, chamberName, true));
return result;
}
}
You don't have to manually enumerate anything - just retrieve the first occurence of a chamber by that name - or none.
If you just need to know whether a given chamber (specified by its ChamberName) exists or not, you could use the .Any() method in Linq:
using(MyDbContext ctx = new MyDbContext())
{
return ctx.Chambers.Any(c => string.Compare(c.ChamberName, chamberName, true));
}
PetaPoco has introduced Multi-POCO queries in experimental form (for now). As their blog post suggests and the code it provides this looks nice and all in One-to-One relations when we load multi POCOs per row as long as they don't repeat over the records.
What happens when at least one side is many relation? Actually example code is Many-to-One relational data.
Example code is clearly a Many-to-One relation. I haven't tested any PetaPoco code but what does the provided code on the blog post do? Does every Article have their own User object instance even though some may be the same user or do they share the same user object instance?
And what about other Many relation types? How do they work of they work at all?
Usually I map these one-to-many queries myself like the following example.
[TableName("Blogs"), PrimaryKey("BlogId")]
public class Blog {
public int BlogId {get;set;}
public string Title {get;set;}
[Ignore]
public IList<Post> Posts {get;set;}
}
[TableName("Posts"), PrimaryKey("PostId")]
public class Post {
public int PostId {get;set;}
public int BlogId {get;set;}
public string Subject {get;set;}
public string Content {get;set;}
}
public class FlatBlogPost {
public int BlogId {get;set;}
public string Title {get;set;}
public int PostId {get;set;}
public string Subject {get;set;}
public string Content {get;set;}
}
There are two ways I could display a list of posts for one blog or without too much work, all blogs.
1.Two queries -
var Blog = Db.Query<Blog>(1);
var Posts = Db.Query<Post>("where BlogId = #0", 1);
2.One query =
var flat = Db.Query<FlatBlogPost>("select b.blogid, b.title, p.postid, p.subject,
p.content from blogs b inner join posts p on b.blogid = p.blogid where
b.blogid = #0", 1);
var blog = flat
.GroupBy(x=> new { x.BlogId, x.Title })
.Select(x=> new Blog {
BlogId = x.Key.BlogId,
Title = x.Key.Title,
Posts = x.Select(y=> new Post{
PostId = y.PostId,
BlogId = x.Key.BlogId,
Subject = y.Subject,
Content = y.Content
}).ToList()
});
However usually in number 2 I would map directly from the FlatBlogPost object to my viewmodel for which I need to display the data.
Update
Check out these helpers which extend PetaPoco to support basic One-to-Many and Many-to-One queries. schotime.net/blog/index.php/2011/08/21/petapoco-one-to-many-and-many-to-one/ https://schotime.wordpress.com/2011/08/21/petapoco-one-to-many-and-many-to-one/
My 'One to Many' recipe for Petapoco is below. The docs are not clear enough for me. Create a db connection in Linqpad, it will show you all Navigation properties you can add to generated Petapoco poco classes. Execute the same SQL in Linqpad, to make sure it gets the data you expect.
// subclass the generated Parent table pocos, add navigation prop for children
[ResultColumn] public List<DecoratedChild> Child { get; set; }
// subclass the generated Child table pocos, add navigation prop for parent
[ResultColumn] public DecoratedParent Parent { get; set; }
// to get children with parent info
List<DecoratedChild> children = db.Fetch<DecoratedChild, DecoratedParent>(SELECT child.*, parent.* from ...)
// to get children with parent info, using PetapocoRelationExtensions
List<Child> children = db.FetchManyToOne<Child, Parent>(child => child.ID, "select child.*, parent.* from ...
// to get parents with children info, using PetapocoRelationExtensions
List<Parent> parents = db.FetchOneToMany<Parent, Child>(par => par.ID, child => child.ID != int.MinValue, "select parent.*, child.* from ...
SQL select order important, same as in Fetch types list !!!
navigation props will have parent or children data ...
with 3 levels the call will be like:
List<DecoratedGrandChild> grandChildColl = db.Fetch<DecoratedGrandChild, DecoratedChild, DecoratedParent>(SELECT grandch.* , child.*, parent.* from ...)
Personally I don't think you can avoid another database call to get the comments. You could get a list of all comments for the 10 articles (in the same order the articles are stored) by using an IN clause, and loop through them adding them to each article.comments as you go along and the comment.articleid changes. The only way I can see getting this information in a single sql call would be to use a join but then you'd get duplicate article details for each comment, so maybe this isn't a problem with petapoco, just one of those things that'll never be perfect