Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Malcolm XML
Aug 8, 2009

I always knew it would end like this.

GrumpyDoctor posted:

This is the problem that generics solved, but the collection you're iterating over doesn't have a generic interface, which is why var's type inference isn't working.

Var's type inference is working correctly; the compiler is unable to deduce the specific type since that information was erased because of collection not exposing it.

Adbot
ADBOT LOVES YOU

raminasi
Jan 25, 2005

a last drink with no ice

Malcolm XML posted:

Var's type inference is working correctly; the compiler is unable to deduce the specific type since that information was erased because of collection not exposing it.

Well, okay, "working" in the "working the way they expect it" sense.

ljw1004
Jan 18, 2005

rum

Newf posted:

I get that, but I don't get why I'm able to declare the type of a variable when the compiler doesn't know it ahead of time. It feels like a stricter version would force me to expect only objects from an IEnumerable and then cast them myself - at least in this case the cast becomes explicit.

Basically the C#/VB languages consider foreach loops to be explicit casts.

The relevant part of the C# spec is $8.4.4

quote:

A foreach statement of the form
code:
foreach (V v in x) embedded-statement
is then expanded to:
code:
{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

The key bit is "(V)(T)e.Current".

Newf
Feb 14, 2006
I appreciate hacky sack on a much deeper level than you.

GrumpyDoctor posted:

http://stackoverflow.com/questions/3917129/foreachderived-obj-in-new-listbase is your exact question with the answer (tldr it's a special thing that foreach does for historial reasons)


ljw1004 posted:

Basically the C#/VB languages consider foreach loops to be explicit casts.

The relevant part of the C# spec is $8.4.4


The key bit is "(V)(T)e.Current".

Thanks both, and all. I think at least that the quality of my confusions is rising.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

A Tartan Tory posted:

I take it that is done in LINQ? Not overly familiar with it, but I'll take a look once I'm back at home later. Is there a way to do it with just basic loops? (edit: answering my own question after looking at it a bit further, I think I understand whats happening there, will update later if I get it to work!)

Thanks for your efforts, I really appreciate it!

Yep, that's LINQ. Take any efforts you can to learn it because the vast majority of loop constructs in your code become obsolete.

That code is using .Zip and .Aggregate, which are more "advanced" LINQ operators, to evaluate the SumProduct. However, .Skip and .Take should be pretty intuitive.

A Tartan Tory
Mar 26, 2010

You call that a shotgun?!

Bognar posted:

Yep, that's LINQ. Take any efforts you can to learn it because the vast majority of loop constructs in your code become obsolete.

That code is using .Zip and .Aggregate, which are more "advanced" LINQ operators, to evaluate the SumProduct. However, .Skip and .Take should be pretty intuitive.

I'll try to learn that after I learn how to clean up my code/methods then, your post really helped me finish what I was wanting to do though (I got it working!) so I really want to thank you and everyone else who was helping me, especially GrumpyDoctor!

If you are interested, here is how I finally managed it.

code:
            for (y = 0; y < Years; y++)
            {
                for (m = 0; m < 12; m++)
                {

                    int current = ((y + 1) * 12) - (12 - m);
                    double sum = 0;

                    if (current < 23)
                    {
                        for (int i = current + 1; i < 24; i++)
                        {
                            sum += SRCFProduct[i] * SRDiscFactProduct[i] * PrudentPProduct[i];
                        }
                    }

                    else
                    {
                        for (int i = 23; i < current + 2; i++)
                        {
                            sum += SRCFProduct[i] * SRDiscFactProduct[i] * PrudentPProduct[i];
                        }
                    }

                    double calc1 = sum / SRDiscFact[y, m];
                    double calc2 = calc1 / PrudentP[y, m];
                    SterlingReserve[y, m] = calc2;
                    Console.WriteLine("Reserve:  " + SterlingReserve[y, m]);
                    Console.WriteLine("y:  " + y);
                    Console.WriteLine("m:  " + m);
                    Console.WriteLine("c:  " + current);
                }
            }

            for (m = 0; m < Months; m++)
            {
                int current = ((y + 1) * 12) - (12 - m);
                double sum = 0;

                if (m == Months - 1)
                {
                    SterlingReserve[y, m] = SterlingReserve[y, m - 1];
                }

                else
                {
                    for (int i = 23; i < current + 2; i++)
                    {
                        sum += SRCFProduct[i] * SRDiscFactProduct[i] * PrudentPProduct[i];
                        double calc1 = sum / SRDiscFact[y, m];
                        double calc2 = calc1 / PrudentP[y, m];
                        SterlingReserve[y, m] = calc2;
                    }
                }

                Console.WriteLine("Reserve:  " + SterlingReserve[y, m]);
                Console.WriteLine("y:  " + y);
                Console.WriteLine("m:  " + m);
                Console.WriteLine("c:  " + current);
            }
And yes, that repeat of the value at the end is quite intentional, if anyone was wondering!

A Tartan Tory
Mar 26, 2010

You call that a shotgun?!
Right, I have one more problem and I'm hoping someone can tell my sleepy idiotic brain why the results are doing this and how to fix it because it's literally the last thing I have to fix and it's bugging me a lot now (apparently I still don't understand how the WPF binding does what it does :sigh:).

Essentially, when displaying the results in a data grid, based on a checkbox boolean value of whether or not to display that particular module of the program (see Main Display in Controller), the program displays the values on separate lines like this.



Is there an easy way to get them to display on the same line, depending on what has been clicked and to not display anything that hasn't been chosen? I'm kinda, sorta aware that I could do it with different bindings, but because of the way I have used methods in the code I can't really find a way to get it to work. Any help would be appreciated.

Code (tried to make it as readable as possible given my terrible use of methods) is available here: http://www.filedropper.com/insval_1

A Tartan Tory fucked around with this message at 16:15 on Sep 19, 2014

RICHUNCLEPENNYBAGS
Dec 21, 2010
I'm using EF Code First with a DB my application owns (like there are no other clients and for various reasons I strongly doubt there will be any that don't use my data access library).

I'm running into efficiency problems because in some places I've declared navigation properties in derived types and if you're doing queries on the supertype, you can't use the Include method to include navigation properties that only exist on subtypes. So what happens instead is you fall back to lazy loading and the database is hit every time you hit one of those properties which can be quite a few queries. You can do an OfType<> query and concatenate things together but that's messy and doesn't work too well for a lot of scenarios.

I'm thinking about a couple ways I might ameliorate it, but I'm not entirely sure I'm happy with either. Is there a better way, or, if not, which of these seems better?

1. I could move the actual declaration to the base type, using some sort of naming convention to indicate that these are "private" (not really because they have to be public, but that they should be treated as private) (e.g., _Property instead of the usual Property). I could then, on the derived type, add in a "real" property and just have the getters and setters refer to the base property. I think if I do this right I can even avoid changing the DB schema. My biggest issue with this is it's going to introduce a lot of noisy boilerplate for classes with a lot of descendants.

2. I could switch to serialization, then hook into EF's materialization to deserialize, serializing again on the save event. This certainly makes it simpler (hey, don't need to include anything at all!) and I don't really need to do queries on the contents of the fields in question. But this does go against normal sound DB design so I still have misgivings (although to be fair it's not like my Code First-generated database schema is a paragon of good DB design either).

(There's obviously the option of ditching EF but at that point I'd sooner just live with the problem because it would be a ton of work)

EssOEss
Oct 23, 2006
128-bit approved
Can you lay out an example of the tables & entity classes you have and the queries you are attempting to run? The description is a bit hard to understand.

RICHUNCLEPENNYBAGS
Dec 21, 2010

EssOEss posted:

Can you lay out an example of the tables & entity classes you have and the queries you are attempting to run? The description is a bit hard to understand.
Imagine these classes:

code:
public abstract class BaseClass {}

public class DerivedClass : Baseclass
  {
    public virtual ICollection<ComplexObject> Collection {get;set;}
  }
Basically I want to do this
code:
context.BaseClasses.Where(bc => meetsSomeCondition(bc)).Include(bc => bc.Collection) 
// you can't actually do this
Yes, there is an overload that takes strings, but it doesn't work either.

So because of that I'd like to change things in such a way that an include is unnecessary to avoid another query.

Che Delilas
Nov 23, 2009
FREE TIBET WEED

RICHUNCLEPENNYBAGS posted:

..
So because of that I'd like to change things in such a way that an include is unnecessary to avoid another query.

Eager loading of derived types does not appear to be supported, nor are they even working on it.

http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1249289-include-property-of-derived-classes

I can't think of a solution that isn't a huge pain. Refactor your entity model so that it doesn't use inheritance (I know, this is also a huge pain)?

RICHUNCLEPENNYBAGS
Dec 21, 2010

Che Delilas posted:

Eager loading of derived types does not appear to be supported, nor are they even working on it.

http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1249289-include-property-of-derived-classes

I can't think of a solution that isn't a huge pain. Refactor your entity model so that it doesn't use inheritance (I know, this is also a huge pain)?

Did you see my previous post? I had two possible solutions in mind, one of which was sort of that.

Silvah
Aug 27, 2004
s0me
edit: Nevermind, that didn't really make sense overall.

Does EF work properly with interfaces? So instead of using the abstract BaseClass, you could just compose your derived classes by implementing various interfaces, then maybe use some master interface from the context to add the query conditions?

Silvah fucked around with this message at 04:17 on Sep 21, 2014

EssOEss
Oct 23, 2006
128-bit approved
I tried using a subclassing structure for modeling my database once and I will never try it again. This is just one of the headaches you will encounter. My recommendation is to not try to make any fancy entity model - just treat your entity classes as database rows and you'll have a happy life.

Newf
Feb 14, 2006
I appreciate hacky sack on a much deeper level than you.

Silvah posted:

Does EF work properly with interfaces? So instead of using the abstract BaseClass, you could just compose your derived classes by implementing various interfaces, then maybe use some master interface from the context to add the query conditions?


EssOEss posted:

I tried using a subclassing structure for modeling my database once and I will never try it again. This is just one of the headaches you will encounter. My recommendation is to not try to make any fancy entity model - just treat your entity classes as database rows and you'll have a happy life.

This sounds more or less exactly like what I'm currently attempting to do. I'm working on a math-tutoring app that's got a pile of different question classes which have a shared inheritance structure blah blah. It's code-first with the current MVC5 / EF6 / VS2013. Haven't run into any problems yet but I'm always nervous that I might sneeze in the wrong direction and then my program will never compile again.

RICHUNCLEPENNYBAGS
Dec 21, 2010

EssOEss posted:

I tried using a subclassing structure for modeling my database once and I will never try it again. This is just one of the headaches you will encounter. My recommendation is to not try to make any fancy entity model - just treat your entity classes as database rows and you'll have a happy life.

I don't necessarily disagree with this but I'm on version 3 of this application and I'm not gonna change it now.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Silvah posted:

edit: Nevermind, that didn't really make sense overall.

Does EF work properly with interfaces? So instead of using the abstract BaseClass, you could just compose your derived classes by implementing various interfaces, then maybe use some master interface from the context to add the query conditions?

Using interfaces is actually a decent way to handle this. Your entity model would look something like this:

C# code:
public class Entity : IBase, IDerived, IAnotherDerived
{
    public string BaseProperty { get; set; }
    public List<Foo> ANavigationProperty { get; set; }
    public List<Bar> AnotherNavigationProperty { get; set; }
    public string Discriminator { get; set; }
}

public interface IBase
{
    string BaseProperty { get; }
}

public interface IDerived : IBase
{
    List<Foo> ANavigationProperty { get; set; }
}

public interface IAnotherDerived : IBase
{
    List<Bar> AnotherNavigationProperty { get; set; }
}
You would write your EF code to eager load whatever you need to, then return those objects casted to interfaces. The two downsides are that you have to handle the Discriminator field yourself, and that making modifications on the interfaces to save to the database isn't quite as intuitive.

RangerAce
Feb 25, 2014

EssOEss posted:

I tried using a subclassing structure for modeling my database once and I will never try it again. This is just one of the headaches you will encounter. My recommendation is to not try to make any fancy entity model - just treat your entity classes as database rows and you'll have a happy life.

Inheritance is usually bad and this is one of many reasons why. There are some scenarios where an open or quasi-open schema would work better than straight inheritance. Then, you expose your entities as views. The views give you a little bit more abstraction between your tables and your database code.

I don't really bother with EF (or most other ORM like it) anymore. I feel like EF is a trap that lulls you into using a bunch of weird inheritance, and you end up spending more time tweaking queries and includes than you would've just creating a set of repository classes that wrap SQL calls.

RICHUNCLEPENNYBAGS
Dec 21, 2010

RangerAce posted:

Inheritance is usually bad and this is one of many reasons why. There are some scenarios where an open or quasi-open schema would work better than straight inheritance. Then, you expose your entities as views. The views give you a little bit more abstraction between your tables and your database code.

I don't really bother with EF (or most other ORM like it) anymore. I feel like EF is a trap that lulls you into using a bunch of weird inheritance, and you end up spending more time tweaking queries and includes than you would've just creating a set of repository classes that wrap SQL calls.

Inheritance is "usually bad?" Like, not just in ORMs but in general design? Seems like an overly reductive way of programming to me.

I don't agree on the ORM front. Sure, making it run efficiently takes some effort, but 1) it is very easy to get it running correctly, without worrying about performance at all (which can matter if you had to just get something out the door first) 2) tuning the performance is still much easier than writing all the SQL to do equivalent things for some of the more complex things you can ask for.

Besides that EF (or any provider that implements LINQ) lets you build up an expression dynamically (with the various classes that inherit Expression) and then use that same expression either on a database or on an expression that exists in-memory which is insanely cool and useful if you want to offer, say, a reporting function.

GoodCleanFun
Jan 28, 2004

RICHUNCLEPENNYBAGS posted:

Inheritance is "usually bad?" Like, not just in ORMs but in general design? Seems like an overly reductive way of programming to me.

I don't agree on the ORM front. Sure, making it run efficiently takes some effort, but 1) it is very easy to get it running correctly, without worrying about performance at all (which can matter if you had to just get something out the door first) 2) tuning the performance is still much easier than writing all the SQL to do equivalent things for some of the more complex things you can ask for.

Besides that EF (or any provider that implements LINQ) lets you build up an expression dynamically (with the various classes that inherit Expression) and then use that same expression either on a database or on an expression that exists in-memory which is insanely cool and useful if you want to offer, say, a reporting function.

I find using in-line SQL (with wrapper class as RangerAce stated) to return an IEnumerable<DataRow> with the additional flexibility of LINQ to be incredibly powerful. Each query language is better at something and when combined complex queries become easier to write.

RICHUNCLEPENNYBAGS
Dec 21, 2010
If it works for you I'm not going to knock it but some of the scorn directed at ORMs doesn't make a lot of sense to me. Like, yeah, they're slow, but "my application is so popular and otherwise performant that the speed of the ORM is a real issue" is like, a nice problem to have and one many of us don't.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy
In my opinion the best things about Entity Framework are code-first database generation, migrations, expression/IQueryable support, and change tracking. These make it very easy to work with both in rapid prototyping on greenfield projects and in maintenance on older projects. However, none of these things are unique to EF - many other .NET ORMs have the same feature set. All other things equal, I would stick with EF over other ORMs simply because it's Microsoft's baby right now and they'll be supporting it for at least a while, as well as coordinating releases with framework updates (e.g. await/async support).

Code first database generation with migrations really is wonderful. Most of the time when modifying the structure of a database you're doing stupid simple stuff like adding tables, columns, or indexes - things that are not complicated and are mostly boilerplate that you're having to add to support your object model. With migrations, you just modify your model, tell Visual Studio to diff it to figure out what changed, then generate a simple code file describing the changes. You just type in add-migration MyStupidChanges in the Package Manager Console and all the work is done for you. This makes rapid prototyping a breeze. If you have the need to do something more complex like move data around or apply calculations to new columns, you can always modify the migration and add custom SQL.

Expressions and IQueryable support are par for the course with most ORMs, but the composability of Expressions gives a very nice way of building queries. In our own applications at work, we have used Expressions in numerous powerful ways to generalize common query operations and reduce code complexity. However, with all this power you can easily shoot yourself in the foot (e.g. exposing IQueryables outside of your data layer, you do have a data layer right?).

RangerAce posted:

I don't really bother with EF (or most other ORM like it) anymore. I feel like EF is a trap that lulls you into using a bunch of weird inheritance, and you end up spending more time tweaking queries and includes than you would've just creating a set of repository classes that wrap SQL calls.

Most ORMs tend to fall apart when you start trying to map an inheritance hierarchy onto them. Sure, it's kind of supported but you always run into edge cases. If you just have data objects that closely map to the DB table structure with object navigation properties for foreign key relationships, you can go extremely far with EF. Given that, I don't understand how EF lulls you into using inheritance. It's a minor feature with poor support (as evidenced by the above posts).

When dealing with SQL in .NET it comes to three things:

1. Full-fledged ORM - use this to handle all your CRUD, drop back to SQL as necessary for complex interactions or efficiency. If you choose this, I would always recommend EF for the reasons above.

2. Micro-ORM - write your own SQL and quickly map to and from objects. This is good if you absolutely need the performance, or if you just really like SQL and are scared of the big bad ORM. If you choose this, I would always recommend Dapper (because it's fast, that's why you picked a micro-ORM right?). This can also be a good choice if you are refactoring a legacy application with handwritten/inline SQL.

3. Raw SQL connections - For pure speed and pure speed only. Honestly, I would never recommend this considering how fast libraries like Dapper are. If you're at the point where the performance difference of mapping to an object matters, you should probably consider a different language entirely.


fake edit: This turned into a much longer post than I expected and is probably rambling in places.

RICHUNCLEPENNYBAGS
Dec 21, 2010
OK, I'm feeling in a heretical mood, so I'm going to go ahead and bite on this: is it really morally superior to have a whole separate data layer that looks up an object by ID rather than to have context.SomeDbSet.Find(id)? What's it giving you?

ninjeff
Jan 19, 2004

RICHUNCLEPENNYBAGS posted:

OK, I'm feeling in a heretical mood, so I'm going to go ahead and bite on this: is it really morally superior to have a whole separate data layer that looks up an object by ID rather than to have context.SomeDbSet.Find(id)? What's it giving you?

Assuming this is for a model that doesn't consist only of single-table entities, one advantage is not having to write a litany of .Include(property) calls every time you want to get a complex object without triggering the N+1 select problem.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

RICHUNCLEPENNYBAGS posted:

OK, I'm feeling in a heretical mood, so I'm going to go ahead and bite on this: is it really morally superior to have a whole separate data layer that looks up an object by ID rather than to have context.SomeDbSet.Find(id)? What's it giving you?

Well, for one, it makes it much easier to unit test if you're calling an injected interface method that returns your data rather than calling a method on a concrete ORM DB object. Another benefit is that all of your queries live in one project, so if you have to refactor something in your data, you're not changing things over your entire solution. Also, if for some reason you need to drop down to SQL instead of using the ORM (e.g. for performance), you can do so in that one method without having to touch the calling code.

wwb
Aug 17, 2004

Absolutely -- no matter what data back end we are using (mainly ravendb these days, with some EF and micro orms as required) we keep it sequestered in some sort of data access service layer. The upper layers should not know about anything horribly database specific.

Funking Giblet
Jun 28, 2004

Jiglightful!
I use NHibernates ISession directly in the controllers.

It's awesome!

Opulent Ceremony
Feb 22, 2012

Bognar posted:

In our own applications at work, we have used Expressions in numerous powerful ways to generalize common query operations and reduce code complexity.

Care to share any examples of this? These always interest me.

Knyteguy
Jul 6, 2005

YES to love
NO to shirts


Toilet Rascal
Kind of running tight here, hoping someone can help with the answer.

Using WPF I'm trying to bind a combobox value (hardcoded items) to a property in my viewmodel.model.

XAML:
XML code:
<ComboBox HorizontalAlignment="Left" Margin="0,0,0,0" FontSize="15" VerticalAlignment="Top" Width="290">
    <ComboBoxItem>Foo</ComboBoxItem>
    <ComboBoxItem>Bar</ComboBoxItem>
</ComboBox>
Here's how I bind another property in the viewmodel
XML code:
<TextBox Text="{Binding Item.UOM, Mode=TwoWay}">
How do I bind the combobox value in the same way? I just want my string value in the model to bind to the combobox item.

Thanks in advance.

epswing
Nov 4, 2003

Soiled Meat

Knyteguy posted:

Using WPF I'm trying to bind a combobox value (hardcoded items) to a property in my viewmodel.model.
How do I bind the combobox value in the same way? I just want my string value in the model to bind to the combobox item.

This might be what you're looking for

XML code:
<ComboBox SelectedValue="{Binding SomeValue, Mode=OneWayToSource}">
    <ComboBoxItem>Foo</ComboBoxItem>
    <ComboBoxItem>Bar</ComboBoxItem>
</ComboBox>
Edit: I'm assuming you want the user to pick a value from the combobox, and have that value set in your ViewModel, hence the OneWayToSource mode.

epswing fucked around with this message at 20:40 on Sep 24, 2014

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Opulent Ceremony posted:

Care to share any examples of this? These always interest me.

I'll start with one of the most complicated examples, mainly because I think it's really cool. One of the applications we're developing is very enterprisey in its requirements and as part of that they want every action to be tracked for reporting at multiple levels. For example, consider the following hierarchy:

code:
 Company
    |
   /|\ (has many)
  Team
    |
   /|\
Item Set
    |
   /|\
  Item
If I make a change to an Item, I should be able to view that action in the action history at the Company level, the Team level, the Item Set level, and on the Item itself.

Now, some background on another internal framework is required. One of our developers put together a type-safe system of defining relationships between objects. There is a lot of voodoo compiler magic with generics and extension methods behind the scenes to make this work, but that is all to make using it easier. It allows you to do define a relationship like this:

C# code:
public class CanonicalCompany : Rel<Company>, IFrom<Item>
{
    public Rel<Item, Company> Relation(RelDefiner<Item, Company> def)
    {
        return def.From(i => i.ItemSet.Team.Company);
    }
}
And then you can use it like so:

C# code:
public class CallingCode
{
    public void GetExample()
    {
        var item = new Item();
        var company = item.Get(CanonicalCompany.Relation);

        // .Get is an extension method that will fail to compile if CanonicalCompany does not define a relationship
        // from the base object to the type of the relation, e.g.
        var obj = new object();
        var badCompany = obj.Get(CanonicalCompany.Relation); // this will fail to compile
        // til the day I die
    }

    public void IncludeExample()
    {
        var db = new SomeDbContext();
        var items = db.Items.Include(CanonicalCompany.Relation).ToList();
        // .Include() is an extension method that applies the .From relationship expression to an Entity Framework .Include()
        // Again, this is all type-safe, so Company cannot be included elsewhere until the relationship is implemented
        var firstCompany = items.First().Get(CanonicalCompany.Relation);
    }

    public void IncludeIfSupportedExample()
    {
        var db = new SomeDbContext();
        var items = db.Items.IncludeIfSupported(CanonicalCompany.Relation).ToList(); // this brings back items with companies
        var otherItems = db.OtherItems.IncludeIfSupported(CanonicalCompany.Relation).ToList(); // this does not bring back companies, since the Rel is not defined
    }
}
That last example is the important one. We can choose to include the company object if the relation is supported.

All of our updates pass through a base repository that actually interacts with EF to apply the changes. All of our updates are described before that using a Delta<T> class (not the OWIN one, a custom-rolled version) that has an Id of the object to apply changes to and a list of the changes that should be applied. I can go into this in a separate post if you want details. The base repository that executes the updates loads the object in, but it also uses .IncludeIfSupported() to load in relationships for ChangeTrackedItem.Relation, ChangedTrackedItemSet.Relation, ChangeTrackedTeam.Relation, and ChangeTrackedCompany.Relation. If those relationships exist, then it creates an action item and indicates that whatever entities that were supported in those relations had changes.

How this would play out in the above example, is that we describe a set of changes to Item 1 and call Update. Item 1 is loaded in joined with its Item Set, Team, and Company. In the log of actions taken, it is noted that this action modified an item under that specific Item Set, Team, and Company. When viewing that log, you can just filter on the Id and Type of changes to view everything that happened at each level.

The big benefit of all this is when you add an entity to the model that should have its changes tracked. All you have to do is write one expression saying how to get from that item to the Company/Team/whatever and the changes are tracked automatically.

I was worried when building this, as I constantly am, about the performance implications of using a solution like this that does a significant amount of magically loading in extra entities. However, we have benchmarked it in multiple situations and only seen a 5-15% decrease in performance. I'm sure it could get significantly worse if you are joining on the entirety of your object model, but in the ways we use it I've seen no reason to expect that it will cripple the system.

Bognar fucked around with this message at 23:20 on Sep 24, 2014

Opulent Ceremony
Feb 22, 2012
I appreciate you taking the time to write all that out! Very neat stuff (that I'm going to need to re-read a couple times).

wwb
Aug 17, 2004

Funking Giblet posted:

I use NHibernates ISession directly in the controllers.

It's awesome!

Do you get paid by the hour?

mortarr
Apr 28, 2005

frozen meat at high speed

Bognar posted:

Enterprisey stuff
That's some serious business... Can you go into how you end up building something like that, like how did you choose EF, how long did it take to design and put together, what's your team size etc. Assuming you're allowed to discuss that?

Sagacity
May 2, 2003
Hopefully my epitaph will be funnier than my custom title.
ORMs are kinda nice in that they allow you to do some complex querying using higher-level query syntax, but that's honestly about it. They infiltrate the design of your domain and once you're at any kind of scale at all you'll be looking at a ORM profiling tool to figure out why the hell it's generating N+1 queries. Not to mention the fact that usually you can only map your domain in one way whereas you may want to have different mappings depending on the type of query.

Funking Giblet
Jun 28, 2004

Jiglightful!

wwb posted:

Do you get paid by the hour?

It works quite well, The session is already an abstraction. I use query objects and extension methods to help out here and there, but otherwise I just pull the data I need.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

mortarr posted:

That's some serious business... Can you go into how you end up building something like that, like how did you choose EF, how long did it take to design and put together, what's your team size etc. Assuming you're allowed to discuss that?

Sure. The decision to use Entity Framework didn't involve too much thought. It's kind of a baseline for most projects here and we tend to stick with it unless the project calls for something other than a relational data store. As part of that, we've come up with a standard base repository class that works for most projects. I'll try to re-create it from memory here:

C# code:
public interface IRepository<T>
{
    T Get (int id);
    List<T> GetMultiple(IEnumerable<int> ids);
    List<T> CreateAsync(IEnumerable<T> items);
    void UpdateAsync(IEnumerable<Delta<T>> deltas);
    void DeleteAsync(IEnumerable<int> ids);
}
The interface takes IEnumerables, but we have a set of extension methods to make working with single objects easier that just wrap the object in an array. The implementation of that is provided by a generic BaseRepository<T> class which most Repositories inherit from. If you need additional methods specific to that class, you just add those to that specific interface, e.g.:

C# code:
    public interface IItemRepository : IRepository<Item>
    {
        void MySpecialMethod(Item item);
    }

    internal class ItemRepository : SimpleRepository<Item>, IItemRepository
    {
        public void MySpecialMethod(Item item) { ... }
    }
This is a nice abstraction for 99% of simple CRUD.

All of the above is fairly standard for our projects. The pieces specific to this project are how we applied the Relationship system to implement change tracking. Because we have the BaseRepository<T> class, we can make changes that affect every database interaction in the project. Once we actually had the idea of combining the two systems for change tracking, it wasn't more than a day or two to fully implement, test, and benchmark.

On the other hand, the Relationship system is an ongoing experiment on this project to see how it works out. We brought it in at the beginning and have been slowly shaping it over the course of the past three months as we found new uses or possible features.

Our team sizes in general are small. Usually around 2-3 people per project. Everyone is expected to understand the full stack from the DB to the UI, which is kind of important at those small sizes.

RICHUNCLEPENNYBAGS
Dec 21, 2010

Bognar posted:

Well, for one, it makes it much easier to unit test if you're calling an injected interface method that returns your data rather than calling a method on a concrete ORM DB object. Another benefit is that all of your queries live in one project, so if you have to refactor something in your data, you're not changing things over your entire solution. Also, if for some reason you need to drop down to SQL instead of using the ORM (e.g. for performance), you can do so in that one method without having to touch the calling code.

Why not just mock the repository itself? That's the approach I'm using... an IRepository class that's a very thin layer over DbContext so I can switch it out in tests.

That doesn't answer the other benefits you have mentioned though, and certainly the Include thing is good too (although the problem is what needs to be included is kind of consumer-specific).

RICHUNCLEPENNYBAGS fucked around with this message at 14:11 on Sep 25, 2014

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

RICHUNCLEPENNYBAGS posted:

Why not just mock the repository itself? That's the approach I'm using... an IRepository class that's a very thin layer over DbContext so I can switch it out in tests.

That doesn't answer the other benefits you have mentioned though, and certainly the Include thing is good too.

I've seen both approaches, I prefer Bognar's approach. Basically, making the repository more general shields you from the pain of swapping out ORMs later. Microsoft might scrap EF the way they scrapped LINQ to SQL (although it's admittedly unlikely). It also shields consumers from the inner workings of the ORM.

Adbot
ADBOT LOVES YOU

RICHUNCLEPENNYBAGS
Dec 21, 2010
I suppose that's reasonable although the price you pay is more code upfront.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply